Published on 24.5.2019
React’s custom hooks allow you to extract and reuse logic across components without changing the component hierarchy. As React developers, we’re all familiar with the built-in hooks like useState
, useEffect
, and useContext
. But sometimes, we need to create our own hooks to encapsulate logic that needs to be shared across multiple components.
In this post, we’ll learn how to create a custom hook called useLocalStorage
that automatically syncs a component’s state with the browser’s local storage. This is particularly useful for persisting state between page refreshes.
What Are Custom Hooks?
Custom hooks are JavaScript functions that allow you to reuse stateful logic in React. Unlike regular JavaScript functions, custom hooks follow a specific naming convention: they start with the word “use” (e.g., useLocalStorage
, useFormState
). This lets React know that the function uses hooks and should be treated in a special way.
Custom hooks don’t return JSX—they typically return values or functions that encapsulate specific logic, and you can use them just like built-in hooks inside functional components.
Why Use Custom Hooks?
Custom hooks are perfect for scenarios where:
- You need to reuse state logic across multiple components.
- You want to encapsulate complex behavior, like interacting with external APIs or managing side effects.
- You want to keep your components clean and concise by abstracting away repetitive logic.
One great example of when a custom hook is beneficial is when you want to persist a component’s state to localStorage
, so that the data survives across page reloads. Instead of manually handling local storage in every component, we can create a custom hook to handle it for us.
Creating the useLocalStorage
Hook
Let’s build a custom hook called useLocalStorage
. This hook will behave similarly to useState
, but with the added feature of saving and retrieving the value from localStorage
.
Step 1: Define the Hook
First, we’ll define the useLocalStorage
hook. The goal of this hook is to:
- Read a value from
localStorage
when the component mounts. - Provide a way to update that value and automatically save it back to
localStorage
.
Here’s how we can do that:
import { useState } from 'react';
function useLocalStorage(key, initialValue) {
// Get the stored value from localStorage or use the initial value
const storedValue = localStorage.getItem(key);
const initial = storedValue ? JSON.parse(storedValue) : initialValue;
// Set up state
const [value, setValue] = useState(initial);
// Update localStorage whenever the value changes
const setStoredValue = (newValue) => {
// If the new value is a function, we pass the current value to it
const valueToStore = newValue instanceof Function ? newValue(value) : newValue;
// Save the new value to localStorage
localStorage.setItem(key, JSON.stringify(valueToStore));
// Update the state
setValue(valueToStore);
};
return [value, setStoredValue];
}
export default useLocalStorage;
How It Works
- Retrieving the Stored Value: We first check if there’s a value already stored in
localStorage
using thekey
passed to the hook. If the value exists, we parse it and use that as the initial value. If not, we use the providedinitialValue
. - Setting the Value: When the state changes, we update both the
localStorage
and the component state. If the new value is a function (like when using the previous state to compute the next state), we pass the current state to it. - Returning the State and Setter: The hook returns the current value and a setter function (
setStoredValue
) that updates both the state andlocalStorage
.
Using the useLocalStorage
Hook in a Component
Now that we’ve built the hook, let’s see it in action. Below is an example of how you can use the useLocalStorage
hook in a React component to persist the state of a counter across page reloads.
import React from 'react';
import useLocalStorage from './useLocalStorage';
function Counter() {
// Use the useLocalStorage hook to manage the counter state
const [count, setCount] = useLocalStorage('count', 0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
In this example, the counter value will be saved to localStorage
under the key "count"
. Every time the page is reloaded, the counter will be restored to its last value, providing a persistent experience for the user.
Testing the useLocalStorage
Hook
To test this out:
- Start the app and click the increment button a few times.
- Open the browser’s developer tools, go to the Application tab, and check the Local Storage section. You should see the key
"count"
and the value reflecting the current count. - Reload the page, and you’ll see that the count remains the same because the value is being read from
localStorage
on page load.
Conclusion
Custom hooks are a powerful feature in React that allow us to abstract away complex logic and reuse it across components. The useLocalStorage
hook is a perfect example of how custom hooks can be used to manage state with persistence across page reloads.
By building this custom hook, we’ve shown how you can use hooks to work with browser storage in a clean, reusable way. If you ever need to persist any state in your React apps, custom hooks like useLocalStorage
are a great tool to have in your toolkit.