简体   繁体   中英

Use parent component to re-render child in React

I'm creating a simple app and I'm having trouble re-rendering a child component. When setState is called in the parent component (App), the props passed down to the child component (CardContainer) don't cause it to re-render. I know I need to somehow use a function in App to call setState in CardContainer, but I'm stumped about how to do that. The state in App definitely changes (it shows up in console.logs), but I need CardContainer to re-render the array it maps over once the App state is changed . Any ideas? Thanks!

Here is the App code (parent component):

import React, { Component } from 'react';
import Header from './Header';
import AddButton from './AddButton';
import CardContainer from './CardContainer';
import style from '../style/App.css';



class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      cards: [{id: 1, title: 'first', text: 'Random stuff that is on the paage.'},
        {id: 2, title: 'second', text: 'More text goes here'},
        {id: 3, title: 'third', text: 'to doooo'}],
      cardSelected: false,
      selectedCard: []
    };
    this.addCard = this.addCard.bind(this);
    this.deleteCard = this.deleteCard.bind(this);
    this.openSelectedCard = this.openSelectedCard.bind(this);
    this.closeSelectedCard = this.closeSelectedCard.bind(this);
    this.editExistingCard = this.editExistingCard.bind(this);
  }

  openSelectedCard(card) {
    this.setState({
      cardSelected: true,
      selectedCard: card
    })
  }

  closeSelectedCard() {
    this.setState({
      cardSelected: false
    })
  }

  addCard() {
    let newId = this.state.cards.length + 2;
    this.openSelectedCard({id: newId, title: 'Untitled', text: 'Just start typing here.'});
    // this.state.cards.push('New Post It');
    // this.setState({
    //   cards: this.state.cards
    // })
  }

  editExistingCard(cardToEdit) {
    for (let i = 0; i < this.state.cards.length; i++) {
      if(this.state.cards[i].id === cardToEdit.id) {
        this.state.cards.splice(i, 1);
        this.state.cards.splice(i, 0, cardToEdit);
      }
    }
    this.setState({
      cards: this.state.cards
    }, () => {this.closeSelectedCard()})
  }

  deleteCard(index) {
    console.log(this.state.cards[index])
    this.state.cards.splice(index, 1);
    this.setState({
      cards: this.state.cards
    })
  }

  render() {
    return (
        <div className={style.appContainer}>
          <Header addCard={this.addCard}/>
          <CardContainer openSelectedCard={this.openSelectedCard} closeSelectedCard={this.closeSelectedCard} cards={this.state.cards} deleteCard={this.deleteCard} selectedCard={this.state.selectedCard} cardSelected={this.state.cardSelected} editExistingCard={this.editExistingCard}/>
        </div>
    );
  }
}

export default App;

Here is the CardContainer code (child component):

import React, { Component } from 'react';
import Card from './Card';
import SelectedCard from './SelectedCard';
import style from '../style/CardContainer.css';



class CardContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selecedCard: [],
      cards: []
    };
  }

  componentWillMount() {
    this.setState({
      cards: this.props.cards
    })
  }

  render() {
    return (
        <div className={style.cardContainer}>
          {this.state.cards.map((card, index) => (
            <Card card={card} key={card.id} index={index} title={card.title} text={card.text} openSelectedCard={() => this.props.openSelectedCard(card)} deleteCard={this.props.deleteCard}/>
          ))}
          {this.props.cardSelected &&
            <div className={style.selectedCardComponentContainer}>
              <SelectedCard card={this.props.selectedCard} closeSelectedCard={this.props.closeSelectedCard} editExistingCard={this.props.editExistingCard}/>
            </div>
          }
        </div>
    );
  }
}

export default CardContainer;

Don't use cards from state in child component. user this.props.cards instead whcih will re-render your component whenever props will change. If still it does not work, use component.forceUpdate()

 class CardContainer extends Component { render() { return ( <div className={style.cardContainer}> {this.props.cards.map((card, index) => ( <Card card={card} key={card.id} index={index} title={card.title} text={card.text} openSelectedCard={() => this.props.openSelectedCard(card)} deleteCard={this.props.deleteCard}/> ))} {this.props.cardSelected && <div className={style.selectedCardComponentContainer}> <SelectedCard card={this.props.selectedCard} closeSelectedCard={this.props.closeSelectedCard} editExistingCard={this.props.editExistingCard}/> </div> } </div> ); } } 

You are calling setState in componentWillMount this way the component won't re-render because you're calling a method inside a component that is not even render yet. You should setState always on componentDidMount that's on the React Docs...

Hope helps :)

This is an issue with the CardContainer lifecycle. The cards variable that is in the component state is only being set after the component will be mounted for the first time. You need to set it also whenever the component props are updated.

The fix: You need to add something like this inside you CardContainer component:

 componentWillReceiveProps(nextProps) { this.setState({ cards: nextProps.cards }); } 

Edit: Another fix would be to just use the cards you already have in the component CardContainer props.

The child is not re-rendering because componentWillMount (which is deprecated so stop using it) and componentDidMount only fires on initial launch. Also I see that you've declared an empty array in the CardContainer component even though you're bringing in props as the constructor's parameters. Also, the SelectedCard component is receiving props.selectedCard. It looks like your cards array in state isn't used at all.

I hope you don't mind that I didn't hand you answer, but I think you should look at these points a little and re-evaluate your code structure.

Thanks for all the help! I figured out that I actually need to call setState in the CardContainer's child, the Card component. The card was the actual component I wanted re-rendered, so I should have thought to make sure it was getting a setState as well. I thought updating the array that was populating the Cards would be enough.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM