When building web applications, it’s common to store data that should persist even after the user refreshes the page or closes the browser. That’s where localStorage
becomes useful.
However, using localStorage
manually inside React components can get messy—especially when managing multiple keys. So, let’s simplify our code by building a custom hook called useLocalStorage
.
This hook will:
-
Get the value from
localStorage
on load -
Store new values when the state updates
-
Sync state between component and
localStorage
seamlessly
🚀 Why Use a Custom Hook for localStorage
?
Benefits include:
-
Cleaner code
-
Reusability across components
-
Built-in state management with
useState
-
Persistence with
localStorage
📁 Folder Setup
If you already have a React app, skip to the next step. Otherwise, create one using:
npx create-react-app localstorage-hook-app
cd localstorage-hook-app
npm start
🛠️ Step 1: Create the useLocalStorage
Hook
Create a new file named useLocalStorage.js
inside your src/
directory:
// src/useLocalStorage.js
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const localValue = window.localStorage.getItem(key);
return localValue ? JSON.parse(localValue) : initialValue;
} catch (err) {
console.error('Error reading localStorage:', err);
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (err) {
console.error('Error writing to localStorage:', err);
}
}, [key, value]);
return [value, setValue];
}
export default useLocalStorage;
Explanation
Line | Code Concept | |
---|---|---|
useState() | Initializes state from localStorage or initialValue |
|
useEffect() | Watches for changes in state and saves them to localStorage |
|
JSON.parse() / JSON.stringify() |
|
Why use a function in useState()
?
It ensures the code runs only once when the component mounts (lazy initialization).
🧪 Step 2: Use It in a Component
Let’s build a simple app that stores the user's name.
Create a new component App.js
or replace its content with:
import React from 'react';
import useLocalStorage from './useLocalStorage';
import './App.css';
function App() {
const [name, setName] = useLocalStorage('username', '');
return (
<div className="App">
<h1>Hello {name || 'Guest'}!</h1>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
</div>
);
}
export default App;
🔍 What’s Happening Here?
-
The
useLocalStorage
hook is used just likeuseState
-
The value is persisted in the browser's storage
-
On refresh or revisit, the name stays the same
🎨 Optional Styling (App.css
)
.App {
font-family: sans-serif;
text-align: center;
margin-top: 100px;
}
input {
padding: 10px;
font-size: 1rem;
margin-top: 10px;
}
🧼 Step 3: Benefits of This Hook
Feature | Benefit |
---|---|
✅ Reusable | Use for theme, tokens, preferences |
✅ Persistent | Remembers user input |
✅ Compact | Less repetitive code |
✅ JSON Support | Handles arrays/objects easily |
💎 Bonus Features You Can Add
Here are some great ideas to enhance the hook:
1. Remove Item Function
Let users clear the stored value:
window.localStorage.removeItem('username');
You can even return this as part of the hook:
return [value, setValue, () => localStorage.removeItem(key)];
2. Sync Between Tabs
Use the storage
event:
useEffect(() => {
const sync = (e) => {
if (e.key === key) {
setValue(JSON.parse(e.newValue));
}
};
window.addEventListener('storage', sync);
return () => window.removeEventListener('storage', sync);
}, []);
3. Custom Serialization
For advanced cases, pass your own parse and stringify methods:
function useLocalStorage(key, initialValue, parseFn = JSON.parse, stringifyFn = JSON.stringify) {
// Use parseFn and stringifyFn
}
🧑🎓 Learning Outcomes
By now, you’ve learned:
Concept | Application |
---|---|
React custom hooks | Reusable logic in function components |
localStorage API | Persistent data storage in browser |
JSON parsing | Handling structured data safely |
useEffect |
React side effects (writing to storage) |
Hook parameters | Making hooks flexible (e.g., keys, default values) |
🔄 Full Code Recap
useLocalStorage.js
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.log(error);
}
}, [key, value]);
return [value, setValue];
}
export default useLocalStorage;
App.js
import React from 'react';
import useLocalStorage from './useLocalStorage';
import './App.css';
function App() {
const [name, setName] = useLocalStorage('username', '');
return (
<div className="App">
<h1>Hello {name || 'Guest'}!</h1>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
</div>
);
}
export default App;
🚀 Conclusion
Creating a custom useLocalStorage
hook in React is a powerful way to make your applications smarter, faster, and user-friendly. With just a few lines of reusable code, you unlock the ability to persist user preferences, form inputs, UI themes, and more—even after page refresh.
✅ Clean.
✅ Flexible.
✅ Perfect for real-world apps.
Use this hook in every project where you want to remember user data. It’ll save you time—and your users will love the experience.