Lesson 12. Custom hooks to use the browser’s APIs 1. Access the local storage
The browser’s local storage is a place you can save some values, so they are persisted for when you leave the page and go back. It’s key-value storage and its API is quite straightforward:
// returns null if no value exists for the given keyconst value = localStorage.getItem('key')localStorage.setItem('key', 'value')
If you are not familiar with it, you can play with it just by using the console in your browser. Try to create some values, refresh the page, and get them back. Note that you can only store string values.
Here, we’ll write an improved version of the useState hook that persists the
value in the local storage. If the user refreshes the page, the state will be
initialized with the stored value.
We want our hook to be used almost the same way as useState, so we will make
it return the same kind of array, with the current value and a setter. It will
accept as parameters the initial state value and the key used to store the value
in the local storage.
Let’s start by just using a classic state provided by useState:
const usePersistedState = (key, initialValue) => {const [value, setValue] = useState(initialValue)return [value, setValue]}
First thing, when setting a new value, we want to store this new value in the
local storage using localStorage.setItem. Let’s create a function doing this
operation just after calling the original setValue, and return this function
in place of setValue:
const setAndPersistValue = (newValue) => {setValue(newValue)localStorage.setItem(key, newValue)}return [value, setAndPersistValue]
Then, when the component is mounted, we want to get the currently stored value from the state, and if it exists, update our state’s value with it.
At that point, we have a choice to make: what value do we want to return before we get the value from the local storage? Two solutions:
- We return the provided
initialValueand replace it with the existing value if it exists; - We return
nullorundefined, then the current value if it exists, the providedinitialValueotherwise.
There is no absolute best choice here; it depends on your need. But if you intend to distribute this hook to other people, your documentation should mention the choice you made.
Here I chose the first way to do it and kept using the initialValue.
const [value, setValue] = useState(initialValue)useEffect(() => {const existingValue = localStorage.getItem(key)if (existingValue !== null) {setValue(existingValue)}}, [key])
Here is how you can do the other way:
const [value, setValue] = useState(null)useEffect(() => {const existingValue = localStorage.getItem(key)if (existingValue !== null) {setValue(existingValue)} else {setValue(initialValue)}}, [key])
Our hook is complete. To use it, we’ll create a component with an input, and use our hook to persist the value entered in the input in the local storage:
Result
Don’t you find it pleasant to use our custom hook almost the same way we would
use useState? And that we hid in our hook most of the complexity to access the
local storage so the developers using it won’t even be aware of it?