A Custom Hook for Local Storage: `useLocalStorage` (Part 2) - Handling Objects and Arrays #87
📖 Introduction
Following our creation of a basic useLocalStorage
hook, this article explores how to make it more robust. We will learn how to handle objects and arrays, and how to synchronize state between different tabs.
📚 Prerequisites
Before we begin, please ensure you have a solid grasp of the following concepts:
- All concepts from Part 1 of this series.
- The
storage
event.
🎯 Article Outline: What You'll Master
In this article, you will learn:
- ✅ The Problem: Understanding the limitations of our basic
useLocalStorage
hook when it comes to complex data and multi-tab applications. - ✅ Core Implementation: How to handle objects and arrays in local storage.
- ✅ Advanced Techniques: How to synchronize state between different tabs using the
storage
event.
🧠 Section 1: The Core Concepts of an Advanced useLocalStorage
Hook
Our basic useLocalStorage
hook works well for simple values like strings and numbers, but what about objects and arrays? localStorage
can only store strings, so we need to use JSON.stringify()
to store complex data and JSON.parse()
to retrieve it. Our current hook already does this, so it can handle objects and arrays.
A bigger challenge is synchronizing state between different tabs. If a user has our application open in two tabs and changes a value in one, the other tab will not be aware of the change. We can solve this by listening to the storage
event, which is fired whenever a change is made to localStorage
from another tab.
💻 Section 2: Deep Dive - Implementation and Walkthrough
Let's enhance our useLocalStorage
hook to handle state synchronization.
2.1 - The Enhanced useLocalStorage
Hook
// useLocalStorage.js
import { useState, useEffect, useCallback } from 'react';
function useLocalStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
try {
const saved = localStorage.getItem(key);
if (saved !== null) {
return JSON.parse(saved);
}
return defaultValue;
} catch {
return defaultValue;
}
});
useEffect(() => {
const handleStorageChange = (e) => {
if (e.key === key) {
setValue(JSON.parse(e.newValue));
}
};
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, [key]);
const setStoredValue = useCallback((newValue) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
}, [key]);
return [value, setStoredValue];
}
export default useLocalStorage;
Step-by-Step Code Breakdown:
useState
with a try-catch block: We've made our initial state retrieval more robust by wrapping it in atry-catch
block.useEffect
for thestorage
event: We use a newuseEffect
hook to add an event listener for thestorage
event.- When the event fires, we check if the
key
that changed is the same as the key our hook is managing. - If it is, we update our state with the new value from the event.
- The cleanup function removes the event listener when the component unmounts.
- When the event fires, we check if the
useCallback
forsetStoredValue
: We wrap oursetValue
function inuseCallback
for performance optimization.
🛠️ Section 3: Project-Based Example: A Synchronized Theme Switcher
Now, let's use our enhanced useLocalStorage
hook to create a theme switcher that syncs between tabs.
// ThemeSwitcher.js
import React from 'react';
import useLocalStorage from './useLocalStorage';
function ThemeSwitcher() {
const [theme, setTheme] = useLocalStorage('my-app-theme', 'light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<div className={`app ${theme}`}>
<h1>Current Theme: {theme}</h1>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
export default ThemeSwitcher;
Now, if you open this component in two different tabs and click the "Toggle Theme" button in one, you will see the theme change in the other tab as well.
💡 Conclusion & Key Takeaways
In this article, we've made our useLocalStorage
hook much more powerful. It can now handle complex data types and synchronize state between different tabs, making it a truly robust solution for managing persistent state in React applications.
Let's summarize the key takeaways:
JSON.stringify()
andJSON.parse()
are essential for storing and retrieving complex data inlocalStorage
.- The
storage
event allows us to synchronize state between different tabs. - By creating a robust
useLocalStorage
hook, we can simplify state management and improve the user experience in our applications.
Challenge Yourself:
To solidify your understanding, try to build a simple to-do list application that uses the useLocalStorage
hook to persist the list of to-dos and syncs them between tabs.
➡️ Next Steps
You now have a solid understanding of how to create a robust and reusable useLocalStorage
hook. In the next article, "Rules of Hooks and Best Practices", we will review the rules of hooks and discuss some best practices for writing your own custom hooks.
Thank you for your dedication. Stay curious, and happy coding!
glossary
storage
event: A browser event that is fired on a window when a storage area (localStorage
orsessionStorage
) has been changed in the context of another document.- State Synchronization: The process of keeping the state of multiple components or applications consistent.