I'm using the useReducer hook to save some global state. Because I would like to save some settings when the browser is closed, I save those settings to local storage.
At the moment I use dispatch to save the setting and a separate function to save this to local storage, but it would be nice if the setting is automatically saved after dispatch. (sometimes I forget to save to local storage and have a difference between state/local storage)
Reading the state from local storage is not a problem. I use the initialState parameter in the useReducer hook for this.
I think the answer is to not do it, but what is the alternative? (without using redux)
Edit: inspired by this nice concise approach to locally persisting a useState
hook, this is a better approach to locally persisting a useReducer
hook (untested, but should be roughly correct):
import { useReducer, useEffect } from 'react'
function useLocallyPersistedReducer(reducer, defaultState, storageKey, init = null) {
const hookVars = useReducer(reducer, defaultState, (defaultState) => {
const persisted = JSON.parse(localStorage.getItem(storageKey))
return persisted !== null
? persisted
: init !== null ? init(defaultState) : defaultState
})
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(hookVars[0]))
}, [storageKey, hookVars[0]])
return hookVars
}
This gets rid of the need for the extra ref, and adds support for the optional init
function. More importantly, it also only updates local storage whenever the state is updated, rather than on every render.
I was just playing around with doing something similar, and wanted to try and achieve it using a custom hook that could be used in place of useReducer. Came up with the following rough hook (I did add an extra parameter so I can specify the key the data gets stored under):
// DO NOT USE THIS; see the updated implementation above instead
import { useRef, useReducer } from 'react'
function useLocallyPersistedReducer(reducer, defaultState, storageKey) {
const isLoading = useRef(true)
if (isLoading.current) {
try {
const persisted = JSON.parse(localStorage.getItem(storageKey))
defaultState = persisted
} catch (e) {
console.warn(`Failed to load state '${storageKey}' from localStorage; using default state instead`)
}
isLoading.current = false
}
const [ state, dispatch ] = useReducer(reducer, defaultState)
try {
localStorage.setItem(storageKey, JSON.stringify(state))
} catch (e) {
console.warn(`Failed to store updated state '${storageKey}'`)
}
return [ state, dispatch ]
}
As a rough job, it works, but there are probably some nuances that I've missed. Hope that gives you an idea of how you could approach it though!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With