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.
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.
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;
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done