Last active
June 4, 2020 16:43
-
-
Save okapies/67a3f1daa09d70806c4e70cdaeaea549 to your computer and use it in GitHub Desktop.
A custom React Context persisting its state to localStorage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The original idea comes from a post by Alex Krush. | |
// https://medium.com/@akrush95/global-cached-state-in-react-using-hooks-context-and-local-storage-166eacf8ab46 | |
import React, { useCallback, useEffect, useReducer, useRef } from 'react'; | |
export const createCachedContext = ({ storageKey, defaultValue }) => { | |
const initializer = (initialState) => { | |
const localState = localStorage.getItem(storageKey); | |
if (localState) { | |
try { | |
return JSON.parse(localState); | |
} catch { | |
// localState is not a object, so return the raw string | |
return localState; | |
} | |
} else if (typeof initialState === 'string' || typeof initialState === 'object') { | |
return initialState; | |
} else { | |
throw new Error('defaultValue must be object or string'); | |
} | |
}; | |
const reducer = (value, newValue) => { | |
const newValueType = typeof newValue; | |
if (newValueType === 'undefined' || newValue === null) { | |
return defaultValue; | |
} else if (typeof value === newValueType) { | |
if (newValueType === 'string') { | |
return newValue; | |
} else if (newValueType === 'object') { | |
return { ...value, ...newValue }; | |
} | |
} | |
throw new Error(`${newValueType} value cannot be set to the context`); | |
}; | |
const Context = React.createContext(); | |
const Provider = (props) => { | |
const [value, setValue] = useReducer(reducer, defaultValue, initializer); | |
const initialized = useRef(false); | |
// save the updated value as a side-effect | |
useEffect(() => { | |
if (initialized.current) { | |
const valueType = typeof value; | |
if (valueType === 'string') { | |
localStorage.setItem(storageKey, value); | |
} else if (valueType === 'object') { | |
localStorage.setItem(storageKey, JSON.stringify(value)); | |
} else { | |
// I believe reducer will prevent from reaching this line... | |
throw new Error('value must be object or string'); | |
} | |
} else { | |
// skip saving for the first time | |
initialized.current = true; | |
} | |
}, [value]); | |
// re-initialize when the storage has been modified by another window | |
const handleStorageEvent = useCallback((e) => { | |
if (e.key === null || e.key === storageKey) { | |
// do not set `initialized` to false because I'm not sure it is safe | |
setValue(initializer(defaultValue)); | |
} | |
}, []); | |
useEffect(() => { | |
if (typeof window !== 'undefined') { | |
window.addEventListener('storage', handleStorageEvent); | |
return () => { | |
window.removeEventListener('storage', handleStorageEvent); | |
}; | |
} | |
}, []); | |
return ( | |
<Context.Provider value={[value, setValue]}> | |
{props.children} | |
</Context.Provider> | |
); | |
}; | |
return [Context, Provider]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useState } from "react"; | |
import { createCachedContext } from "./CachedContext"; | |
export const [ProfileContext, ProfileProvider] = createCachedContext({ | |
storageKey: 'profile', | |
defaultValue: { | |
name: null, | |
email: null, | |
} | |
}); | |
const App = (props) => { | |
return <ProfileProvider><Hello /></ProfileProvider>; | |
} | |
const Hello = (props) => { | |
const [name, setName] = useState(null); | |
const [profile, setProfile] = useContext(ProfileContext); | |
useEffect(() => { | |
setProfile({name: name}); | |
}, [name]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See https://stackoverflow.com/a/62197608/1014818