sara lance
sara lance

Reputation: 603

state is not updating when the component re renders?

I'm making a Nextjs flashcard app. I'm passing a deck structure like this:

const deck = {
  title: 'React 101',
  flashcards: [flashcardOne, flashcardTwo],
};

as props to the Deck component. This component shows the first card in flashcards and a "next" button to increment the index and showing the next card in flashcards. The Card component is very simple and shows the front and the back of the card depending of the state front.

This is what I got so far and it's working but if I click "next" when the card is showing the answer (flashcard.back), the next card is going to appear with the answer. And I'm not sure why, isn't the Card component re rendering when I click "next"? And if the component re renders, front is going to be set to true?

export default function Deck({ deck }) {
  const [cardIndex, setCardIndex] = useState(0);
  const { title, flashcards } = deck;
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <h1 className={styles.title}>{title}</h1>

        {cardIndex < flashcards.length ? (
          <>
            <div className={styles.grid}>
              <Card flashcard={flashcards[cardIndex]} />
            </div>
            <button onClick={() => setCardIndex((cardIndex) => cardIndex + 1)}>
              Next
            </button>
          </>
        ) : (
          <>
            <div>End</div>
            <button>
              <Link href='/'>
                <a>Go to Home</a>
              </Link>
            </button>
            <button onClick={() => setCardIndex(0)}>Play again</button>
          </>
        )}
      </main>
    </div>
  );
}

export function Card({ flashcard }) {
  const [front, setFront] = useState(true);
  return (
    <>
      {front ? (
        <div
          className={`${globalStyles.card} ${styles.card}`}
          onClick={() => setFront(false)}
        >
          <p className={styles.front}>{flashcard.front}</p>
        </div>
      ) : (
        <div
          className={`${globalStyles.card} ${styles.card}`}
          onClick={() => setFront(true)}
        >
          <p className={styles.back}>{flashcard.back}</p>
        </div>
      )}
    </>
  );
}

Upvotes: 0

Views: 47

Answers (2)

CertainPerformance
CertainPerformance

Reputation: 371138

When state changes, the card will re-render, but it will not re-mount. So, existing state will not be reset.

Call setFront(true) when the flashcard prop has changed:

const [front, setFront] = useState(true);
useLayoutEffect(() => {
  setFront(true);
}, [flashcard]);

I'm using useLayoutEffect instead of useEffect to ensure front gets set ASAP, rather than after a paint cycle (which could cause flickering).

You can also significantly slim down the Card JSX:

export function Card({ flashcard }) {
  const [front, setFront] = useState(true);
  const face = front ? 'front' : 'back';
  return (
    <div
      className={`${globalStyles.card} ${styles.card}`}
      onClick={() => setFront(!front)}
    >
      <p className={styles[face]}>{flashcard[face]}</p>
    </div>
  );
}

Upvotes: 2

Praveen Kumar Purushothaman
Praveen Kumar Purushothaman

Reputation: 167220

Okay, I guess I had the same issue. Since you're using functional components, and you're re-using the same component or in better words, you're not unmounting and remounting the component really, you're just changing the props, this happens. For this, you need to do useEffect() and then setFront(true).

Here's the code I used in my App.

  useEffect(() => {
    setFront(true);
  }, [flashcard]);

This is what I have used in my Word.js file.

Upvotes: 1

Related Questions