React Local Storage - How to Synchronize State and Local Storage in React
React Local Storage - How to Synchronize State and Local Storage in React

It can be really difficult to synchronize your state inside React with Localstorage. This is why in this post we will create a custom hook in order to work with local storage efficiently.

Initial project

Here is an application that I prepared. This is just an input with text inside and we can change this input.

const App = () => {
  const [name, setName] = useState("Jack");
  console.log(name);

  return (
    <div>
      <h1>Hello monsterlessons</h1>
      <div>
        <input
          type="text"
          placeholder="Enter your name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
    </div>
  );
};

Here is how it look like. We have here a useState hook with default name and it is bind to our input.

How to sync?

Now the question is how we can synchronize our state with name in the component with localstorage? We must do it inside useEffect because this is how we must do side effect inside React and working with localStorage is a side effect.

const App = () => {
  ...
  useEffect(() => {
    localStorage.setItem('name', name)
  }, name)
  ...

};

Here we created a useEffect which sets a localStorage every time when we change our name in state.

Use effect

Now when we are changing the name our localStorage is updated automatically.

This solution is just fine for the small applications. But every single time when you are working with localStorage you are writing this useEffect code. And with every usage of useEffect in your components they become less and less understandable and more difficult to support.

Custom hook

This is why it is a good idea to create a custom hook to implement exactly the same stuff which will eliminate usage of useEffect inside every single component.

So this is how our usage must look like

const App = () => {
  const [name, setName] = useLocalStorage("name", "Jack");
  ...
};

And we can remove useEffect completely as this should be our new state which inside synchronize value with localStorage.

Now let's create our new hook

// useLocalStorage.jsx
const useLocalStorage = (key, initialValue) => {
  return [storedValue, setValue];
};

export default useLocalStorage;

We get key and initialValue as parameters and our hook must return a value and a setter just like useState hook.

const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  return [storedValue, setValue];
};

export default useLocalStorage;

Here we used useState but we provided a function inside to set our initial value. First we check if we have window it allows our code to work in NodeJS if we need to. Then we try to get a value from localStorage. If it is there we try to parse it but if not then we simple return an initialValue.

But here is an important point. We return setValue function and this is not setStoredValue which we have as a second parameter. This is correct because we want an additional function which set a value to localStorage.

const useLocalStorage = (key, initialValue) => {
  ...

  const setValue = (value) => {
    try {
      setStoredValue(value);

      if (typeof window !== "undefined") {
        localStorage.setItem(key, JSON.stringify(value));
      }
    } catch (error) {
      console.log(error);
    }
  };
  return [storedValue, setValue];
};

export default useLocalStorage;

Our setValue function updates not only state but also localStorage. Again we check for window here to make this code working ouside browser.

So this is the whole code of our finished useLocalStorage hook.

const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);

      if (typeof window !== "undefined") {
        localStorage.setItem(key, JSON.stringify(value));
      }
    } catch (error) {
      console.log(error);
    }
  };
  return [storedValue, setValue];
};

export default useLocalStorage;

Use effect

As you can see in browser it works exactly like before but now all localStorage logic is isolated inside our custom hook and we don't have a useEffect in our component.

And actually if you are interested to learn how to build real React project from start to the end make sure to check my React hooks course.

📚 Source code of what we've done