Keeping state of data within localStorage in React

kebin20

kebin20

Posted on April 25, 2023

Keeping state of data within localStorage in React

So I was working on a feature that would allow users to save their progress with flashcards. However, I ran into a problem where refreshing the page or navigating away from it would reset the number of revised cards to its original state. As you can imagine, this was frustrating and took me nearly a week to figure out.

After trying a few different solutions, I decided to use localStorage to store the modified state of the deck array within each flashcard component. This way, the component would use the stored data instead of reusing the original data that was passed down. This approach worked well for the "revise all cards" section, but I still had some trouble with individual sets.

For one of my sets, my initial solution was:

  useEffect(() => {
   localStorage.setItem(`cardDeckSet`, JSON.stringify(cardDeck));
   }, []);
Enter fullscreen mode Exit fullscreen mode

However, I ran into a bug. When I entered another flashcard set (for example, going from Set 1 to Set 2) and then went back to Set 1, the same updated flashcard set from Set 1 would be shown again. It was really frustrating because I couldn't figure out what was causing the problem.

Eventually, I realized that since there were multiple instances of the same component, storing the current state data in just one item in localStorage meant that ALL of the other flashcard components would get the same state. That was a bit of a facepalm moment for me!

After spending a lot of time brainstorming a solution, I finally came up with an idea. I decided to make multiple cardDeckSet items in local storage, like so:

  useEffect(() => {
   localStorage.setItem(`cardDeckSet${setNumber}`, JSON.stringify(cardDeck));
   }, []);
Enter fullscreen mode Exit fullscreen mode

This solution worked but the next problem I encountered was that there was a discrepancy in setting the updated state in localStorage.

useState is asynchronous

After some debugging, I realized that the issue was related to the asynchronous nature of useState in React.

When I tried calling setCardDeck to update the state of cardDeck in my component, the change doesn't happen immediately. Instead, it schedules a state update and re-renders the component on the next cycle. This means that when I called localStorage.setItem right after setCardDeck, it was still using the old value of cardDeck from the previous render cycle.

To fix this issue, I needed to find a way to ensure that localStorage was updated with the most recent state of cardDeck. After some experimentation, I found a solution by inserting the code for saving the state of the cards within the vocabLearnt() and reviseVocab() functions of my custom hook:


//For reference
const [cardDeck, setCardDeck] = useState(incomingDeck);

function vocabLearnt(storageItem: string) {
    //The current position of the card at this time
    const cardToRemove = cardDeck[cardIndex];
    const newDeck = cardDeck.filter((card) => card.id !== cardToRemove.id);
    setCardDeck(newDeck);
    // To store current state of deck
    localStorage.setItem(storageItem, JSON.stringify(newDeck));
  }

  function reviseVocab(storageItem: string) {
    const cardToRemove = cardDeck[cardIndex];
    const newDeck = cardDeck.filter(
      (card: { id: string }) => card.id !== cardToRemove.id
    );
    // To store current state of deck
    localStorage.setItem(storageItem, JSON.stringify(newDeck));

    //To make sure same cards are not being added
    const vocabToLearnArr = vocabToLearn.find(
      (card) => card.id === cardToRemove.id
    )
      ? [...vocabToLearn]
      : [...vocabToLearn, cardToRemove];
    setCardDeck(newDeck);
    setVocabToLearn(vocabToLearnArr);
  }

Enter fullscreen mode Exit fullscreen mode

In conclusion, after exploring different options to keep track of the user's progress with the flashcards, I decided that the best solution for my app is to use localStorage.

The app will mainly be used within my school, so it's important to keep it as accessible as possible. Using localStorage allows me to store the state of the flashcards even if the user refreshes the page or moves to another page within the app.

I did consider sessionStorage as an alternative, but I need the data to persist even after the user closes the app or the browser. So for my purposes, localStorage is the most suitable solution.

Of course, as my app grows and evolves, I may need to explore other options in the future. But for now, I'm happy with the decision to use localStorage to keep track of the user's progress with the flashcards.

Thanks for the read!

💖 💪 🙅 🚩
kebin20
kebin20

Posted on April 25, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related