DEV Community

JorensM
JorensM

Posted on

How to keep state between page refreshes in React

Hello dear reader!

In this article I would like to show you how to keep state between page refreshes in React. Check it out in action:

Image description

This sort of thing is called state persistance, and it allows you to keep your state intact even through page refreshes or closing of the browser.

State persistance has many use cases, for example you might want to keep the filled fields in a form stay even after the user has left the page and then came back later.

Making state persistant is actually quite easy. We will be utilizing local storage for this.

This project will be using TypeScript, but you can use JavaScript too, in which case just omit the types.

So without further ado, let's get started!

Project Structure

We will be creating a hook called usePersistState() that will be used just like a regular state hook, with the added bonus of being persistant.

This article assumes that you already know how to set up a project, so I will skip that part. Feel free to use any tool such as CRA, Vite, NextJS, etc. for setting up the project.

Our project structure will look like this

  • index.tsx - page where we will test our hook
  • usePersistState.ts - file where we will hold our hook

So now that we have laid out our project structure, we can start coding!

usePresistState.ts

First of all, let's scaffold our hook function:



export default function usePersistState<T>(initial_value: T, id: string): [T, (new_state: T) => void] {

}


Enter fullscreen mode Exit fullscreen mode

So we have declared a function usePersistState that has a generic type T. As arguments you can specify the state's initial value, as well as an unique id to identify our state within local storage. Finally we specify it's return type as an array containing the state and the state setter function (just like a regular useState()).

Next let's create a new _initial_state variable that will determine whether to use the value from local storage or the value passed to the hook.



// Set initial value
const _initial_value = useMemo(() => {
    const local_storage_value_str = localStorage.getItem('state:' + id);
    // If there is a value stored in localStorage, use that
    if(local_storage_value_str) {
        return JSON.parse(local_storage_value_str);
    } 
    // Otherwise use initial_value that was passed to the function
    return initial_value;
}, []);


Enter fullscreen mode Exit fullscreen mode

What we do here is check if localStorage has an item already stored for our state. If yes, then we use the localStorage's value for our initial state. Otherwise we use the initial_value that was passed as an argument to our hook. We wrap this in useMemo so the _initial_value gets set only once, upon mount (because we only need this upon mount).

Next let's add the state itself that uses the _initial_value we defined in the previous code snippet.



const [state, setState] = useState(_initial_value);


Enter fullscreen mode Exit fullscreen mode

Now let's add add an useEffect that runs every time our state changes, in order to save the state into local storage:



useEffect(() => {
    const state_str = JSON.stringify(state); // Stringified state
    localStorage.setItem('state:' + id, state_str) // Set stringified state as item in localStorage
}, [state]);


Enter fullscreen mode Exit fullscreen mode

What this does is simply, whenever our state changes, store the state value in localStorage.

Finally, let's return our state and setState().



return [state, setState];


Enter fullscreen mode Exit fullscreen mode

Aaaand we're done! Our hook is ready, now we can test it out!

index.tsx

In index.tsx, paste the following code:



import { usePersistState } from '@printy/react-persist-state/src/index';
import React from 'react'

export default function PersistStateExample() {

    const [counter, setCounter] = usePersistState(0, 'counter');

    return (
        <div>
            <button
                onClick={() => setCounter(counter + 1)}
            >
                {counter}
            </button>
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode

What we do here is create a counter state using our newly made hook and a button that shows the counter value as well as increments the counter whenever it is clicked. Try clicking it a few times, then refreshing the page. You will see that the state has persisted. You can even completely close your browser and repoen the page, and the state will have remained!

Conclusion

In this article we learned how to create persistant state in React that doesn't reset when we refresh the page. I hope this article was useful to you and that you will find a way to use this method.

If you have any questions or feedback, feel free to leave a comment down below!

Thanks for reading, and have a great day!

Top comments (8)

Collapse
 
nathan_tarbert profile image
Nathan Tarbert • Edited

Hey nice tips!

BTW, DEV has its own syntax that will bring out the color in your code if that's something you want to add to your article.

It's 3 backticks followed by javascript and then close it with 3 backticks.

I'm not trying to be critical but hope this helps.

Cheers

export default function usePersistState<T>(initial_value: T, id: string): [T, (new_state: T) => void] {

}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jorensm profile image
JorensM

That's awesome, didn't know about this! Thanks!

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Cool, no problem :)

Collapse
 
brigantipost profile image
brigantipost

Hi,
Nice tips. How do I update states in this way?

I have this situation in my root component:

const [currentStep, setCurrentStep] = usePersistState(0, 'step')
  const [currentProduct, setCurrentProduct] = usePersistState<number|null>(null, 'product')

  const set = (step: number, product?: number|null) => {
    setCurrentStep(step)
    product && setCurrentProduct(product)
    console.log(product)
  }
Enter fullscreen mode Exit fullscreen mode

The "set" function is called by child components but when hit, the state doesn't update, it remains the one saved in localStorage.

Collapse
 
jorensm profile image
JorensM

Hey, thanks! Could you share the code for your implementation of usePresistsState(), and also the code where the set function is being used? Thank you

Collapse
 
mannguyen profile image
nguyenlyminhman

Nice !!! Thank bro !!!

Collapse
 
jorensm profile image
JorensM

Thanks and you're welcome!

Collapse
 
leiffrederiksen profile image
Leif Frederiksen

Nice and simple - exactly what I just needed :-)