Storing and Retrieving Data in Next.js Using LocalStorage and TypeScript

Written by rockyessel | Published 2023/04/18
Tech Story Tags: nextjs | localstorage | typescript | tailwindcss | react-hooks | web-development | javascript-frameworks | data-storage

TLDRPersisting 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. In my use case, I needed the localStorage to store the user preference without creating a modal and making API requests to POST and GET. We'll create a colour selector with a dropdown menu, so we can select the colour they want, and store whatever option we select. After the page refreshes, the data still persist.via the TL;DR App

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.

Let’s get into using localStorage with Next.js

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.


Written by rockyessel | Web Developer | Full-Stack Developer | Javascript | Typescript | React | NextJs | Node
Published by HackerNoon on 2023/04/18