Persisting data in Next.js using localStorage is very frustrating, and I have read many posts where other developers use other libraries or cookies to accomplish the same effect as localStorage.
For my particular use case, I found it necessary to utilize localStorage to store user preferences instead of implementing a modal and making API requests for every website visit. By using localStorage, I was able to avoid these additional steps and streamline the process.
So, to use and persist data in Next.js, we are not doing it the normal way.
Let’s get started!
npx create-next-app@latest next_localstorage
To install TypeScript, once you run the above command in your terminal, you have options to choose, TypeScript, and EsLint.
Then, install tailwindCSS, here
So what are we creating?
We'll create a color selector with a dropdown menu so that users can select the color they want, and store whatever option we select in our localStorage in a way that after the page refreshes the data still persist.
/pages/index.ts
import React from 'react';
const Home = () => {
const [selected, setSelected] = React.useState('red');
const [selectedState, setSelectedState] = React.useState(false);
return (
<main className='bg-slate-900 text-white w-full h-screen p-10'>
<button
onClick={() => setSelectedState((prev) => !prev)}
className='relative px-4 py-4 rounded-md text-black font-semibold bg-gray-400'
>
Select Your Code
{selectedState && (
<ul className='bg-[#3d3d3d] text-[1rem] text-white w-[10rem] z-[1] drop-shadow-lg absolute top-12 left-0 px-2 flex flex-col items-center py-2 rounded-md divide-y divide-white/20'>
<li onClick={() => setSelected('red')} className='w-full py-2'>
Red
</li>
<li onClick={() => setSelected('blue')} className='w-full py-2'>
Blue
</li>
<li onClick={() => setSelected('yellow')} className='w-full py-2'>
Yellow
</li>
<li onClick={() => setSelected('gray')} className='w-full py-2'>
Gray
</li>
<li onClick={() => setSelected('green')} className='w-full py-2'>
Green
</li>
</ul>
)}
</button>
<p>This is the color you picked: {selected}</p>
<div
className={`w-64 h-32 rounded-md`}
style={{ backgroundColor: `${selected}` }}
></div>
</main>
);
};
export default Home;
So, with the const [selected, setSelected] =React.useState('red')
what this state does is change the default value for `red` to any color of the user's choosing. Then we use the selected
value to change the background color of the box we created. And of course, we used Tailwind for the styling. Learn more about this CSS framework here.
Typically, when using a hook to track a value, the value will reset to its original state after a page refresh. To avoid this issue, a specific logic must be created to utilize localStorage without encountering errors.
So here is the modification to the const [selected, setSelected] =React.useState('red')
But first, let's set localStorage in useEffect()
hook.
React.useEffect(()=>{
window.localStorage.setItem('user_selected_colour', selected)
},[selected])
Then for the selected State.
const [selected, setSelected] = React.useState(():string =>{
if (typeof window !== 'undefined'){
const from_localStorage = window.localStorage.getItem('user_selected_colour')
if (from_localStorage === null || from_localStorage === undefined){
return 'Red'
}
return `${from_localStorage}` ? from_localStorage : 'Red'
}
return ''
});
Now with this, we can now persist our data, and not have to rely on any third parties apart from our browser localStorage.
Let me explain this further:
First, we want to make sure that window is defined, and if it is, then we get the item from our localStorage and then store it in from_localStorage
. If the user opens the web app for the first time, nothing is saved to localStorage, so by default, we get undefined
or null
. In the next line of code, we check to see if the from_localStorage
returns null
or undefined
which will be a one-time event(when the user opens the web for the first time), then we set it to the default value that we want, and in this case, it is red
.
Once we do that from_localStorage
is not null
or undefined
, so if the user should choose another option like blue
, then from_localStorage
is defined and we move to the next line which is: return `${from_localStorage}` ? from_localStorag : 'red'
. Summarily, all we are saying is that, if a value exists, then return that value or else return red
our default value.
But, we face a problem with hydration. Note that the localStorage is working perfectly fine after you close the hydration error.
The reason we get this error is that HTML that is rendered on the server side, doesn’t match the value that was rendered on the client side. Suppose a user selects a different color, such as blue, but the server still has the value red. This discrepancy between the server and the client can cause a hydration error. To avoid this issue, we can use a useState
hook to store every option that the user selects. This new state will reflect the value selected by the user and can be displayed on the client side.
You should understand it more, after reading the code.
const [local] = React.useState(():string =>{
if (typeof window !== 'undefined'){
const from_localStorage = window.localStorage.getItem('user_selected_colour')
if (from_localStorage === null || from_localStorage === undefined){
return 'red'
}
return `${from_localStorage}` ? from_localStorage : 'red'
}
return ''
});
const [selected, setSelected] = React.useState<string>(local);
const [selectedOption, setSelectedOption] = React.useState<string>();
const [selectedState, setSelectedState] = React.useState(false);
React.useEffect(()=>{
window.localStorage.setItem('user_selected_colour', `${selected}`)
setSelectedOption(`${selected}`);
},[local, selected])
So, we use this const [selected, setSelected] = React.useState<string>(local);
to select the state and use this const [selectedOption, setSelectedOption] = React.useState<string>();
to display the state, so that way we don’t get any errors. And this can also work on objects also.
So this is what the whole code would look like:
import React from 'react';
const Home = () => {
const [local] = React.useState(():string =>{
if (typeof window !== 'undefined'){
const from_localStorage = window.localStorage.getItem('user_selected_colour')
if (from_localStorage === null || from_localStorage === undefined){
return 'red'
}
return `${from_localStorage}` ? from_localStorage : 'red'
}
return ''
});
const [selected, setSelected] = React.useState<string>(local);
const [selectedOption, setSelectedOption] = React.useState<string>();
const [selectedState, setSelectedState] = React.useState(false);
React.useEffect(()=>{
window.localStorage.setItem('user_selected_colour', `${selected}`)
setSelectedOption(`${selected}`);
},[local, selected])
return (
<main className='bg-slate-900 text-white w-full h-screen p-10'>
<button
onClick={() => setSelectedState((prev) => !prev)}
className='relative px-4 py-4 rounded-md text-black font-semibold bg-gray-400'
>
Select Your Code
{selectedState && (
<ul className='bg-[#3d3d3d] text-[1rem] text-white w-[10rem] z-[1] drop-shadow-lg absolute top-12 left-0 px-2 flex flex-col items-center py-2 rounded-md divide-y divide-white/20'>
<li onClick={() => setSelected('red')} className='w-full py-2'>
Red
</li>
<li onClick={() => setSelected('blue')} className='w-full py-2'>
Blue
</li>
<li onClick={() => setSelected('yellow')} className='w-full py-2'>
Yellow
</li>
<li onClick={() => setSelected('gray')} className='w-full py-2'>
Gray
</li>
<li onClick={() => setSelected('green')} className='w-full py-2'>
Green
</li>
</ul>
)}
</button>
<p>This is the color you picked: {selectedOption}</p>
<div
className={`w-64 h-32 rounded-md`}
style={{ backgroundColor: `${selectedOption}` }}
></div>
</main>
);
};
export default Home;
Now I hope you now know how to persist data in Next.js. For the source code for this project see and here is a live demo
Also published here.