Search code examples
reactjsrenderreact-props

React child not re-rendered when parents props change


I'm having some issues with child re-rendering, I pass methods to children to see if a button should be displayed or not but when the state of the parent changes, the children are not re-rendered.

I tried with the disabled attribute for the button but didn't work either.

Here's my code (I removed unnecessary part):

function Cards(props) {
  const isCardInDeck = (translationKey) => {
    return props.deck.some(
      (card) => !!card && card.translationKey === translationKey
    );
  };

  const addToDeck = (card) => {
    if (!isCardInDeck(card.translationKey) && !!card) {
      props.deck.push(card);
    }
  };

  const removeFromDeck = (card) => {
    if (isCardInDeck(card.translationKey) && !!card) {
      var index = props.deck.findIndex(
        (c) => c.translationKey === card.translationKey
      );
      props.deck.splice(index, 1);
    }
  };

  return (
    <div className="cardsContent">
      <div className="cards">
        {cardList.length > 0 ? (
          cardList.map((item, index) => {
            return (
              <Card key={index} card={item} addToDeckDisabled={isCardInDeck(item.translationKey)} addToDeckClick={addToDeck} removeFromDeckClick={removeFromDeck} />
            );
          })
        ) : (
          <span>
            <FormattedMessage id="app.cards.label.no.card.found" defaultMessage="No card found with filter."/>
          </span>
        )}
      </div>
    </div>
    );
}

function Card(props) {

  const toggleShowDescription = () => {
    if (!showDescription) {
      setShowDescription(!showDescription);
    }
  };

  return (
    <div onClick={toggleShowDescription} onBlur={toggleShowDescription} >
      <img src={"../images/cards/" + props.card.image} alt={props.card.image + " not found"} />
      {showDescription ? (
        <div className="customCardDetail">
          <div className="cardName"></div>
          <div className="cardType">
            {props.addToDeckDisabled ? (
              <Button onClick={() => { props.removeFromDeckClick(props.card);}} startIcon={<RemoveIcon />}>
                Remove from deck
              </Button>
            ) : (
              <Button onClick={() => { props.addToDeckClick(props.card); }} startIcon={<AddIcon />}>
                Add to deck
              </Button>
            )}
          </div>
          <div className="cardDescription">
            <span>
              <FormattedMessage id={props.card.description} defaultMessage={props.card.description} />
            </span>
          </div>
        </div>
      ) : (
        ""
      )}
    </div>
  );
}

Solution

  • You code does not update state. Cards mutates the props that it is receiving. To use state in a functional component in React you should use the useState hook.

    Cards would then look something like this:

    function Cards(props) {
      const [deck, setDeck] = useState(props.initialDeck)
    
      const isCardInDeck = (translationKey) => {
        return deck.some(
          (card) => !!card && card.translationKey === translationKey
        );
      };
    
      const addToDeck = (card) => {
        if (!isCardInDeck(card.translationKey) && !!card) {
          setDeck([...deck, card])
        }
      };
    
      const removeFromDeck = (card) => {
        if (isCardInDeck(card.translationKey) && !!card) {
          setDeck(deck.filter(deckItem => deckItem.translationKey !== card.translationKey))
        }
      };
    
      return (
        <div className="cardsContent">
          <div className="cards">
            {cardList.length > 0 ? (
              cardList.map((item, index) => {
                return (
                  <Card key={index} card={item} addToDeckDisabled={isCardInDeck(item.translationKey)} addToDeckClick={addToDeck} removeFromDeckClick={removeFromDeck} />
                );
              })
            ) : (
              <span>
                <FormattedMessage id="app.cards.label.no.card.found" defaultMessage="No card found with filter."/>
              </span>
            )}
          </div>
        </div>
        );
    }