A hook is checking, if there are impersonation information in storage persisted, when the page renders, and if so, sets those information in the global AppState context.
const impersonateStorageKey = `impersonation_${config.environment}`
// signature const impersonate: (empScope: number) => void
const { currentImpersonation, impersonate } = useAppContext()
useEffect(() => {
if (!window || !window.localStorage) return;
const storageString = localStorage.getItem(impersonateStorageKey)
if (!storageString) return;
const data = JSON.parse(storageString)
impersonate(data.currentImpersonation)
}, [impersonateStorageKey])
With a second hook, that is persisting a change in the current impersonated identity to the storage:
useEffect(() => {
if (!window || !window.localStorage) return;
localStorage.setItem(impersonateStorageKey, JSON.stringify({ /* ... */}))
}, [currentImpersonation, impersonateStorageKey])
Plus the relevant bits from useAppContext
const useAppContext = () => {
const { state, dispatch } = useContext(AppContext)
if (!state) {
throw new Error("useAppContext must be used within within AppContextProvider")
}
const impersonate = (employeeScope: string | number) => dispatch({ type: 'IMPERSONATE', value: employeeScope })
const currentImpersonation = state.currentImpersonation
return {
impersonate,
currentImpersonation,
}
}
This is working as it should, but the linter is complaining, that the dependency impersonate is missing from the first useEffect hook
When I add impersonate
to the dependency array, this will cause a constant update loop and make the application unresponsive.
I know what is causing this behaviour, but I am failing to see a solution (except ignoring the rule) on how to break the loop and make the linter happy.
What approaches can I take here?
CodePudding user response:
You can memoize the function when you create it with useCallback
:
const useAppContext = () => {
const { state, dispatch } = useContext(AppContext)
const impersonate = useCallback(
(employeeScope: string | number) => dispatch({
type: 'IMPERSONATE',
value: employeeScope
}), [dispatch])
if (!state) {
throw new Error("useAppContext must be used within within AppContextProvider")
}
const currentImpersonation = state.currentImpersonation
return {
impersonate,
currentImpersonation,
}
}
If you can't, a workaround would be to put the function in a ref. The ref
is an immutable reference to an object, with the current
property which is mutable. Since the ref
itself is immutable, you can use it as a dependency without causing useEffect
to activate (the linter knows it, and you don't even need to state it as a dependency). You can mutate current
as much as you like.
const impersonateRef = useRef(impersonate)
useEffect(() => {
impersonateRef.current = impersonate
}, [impersonate])
useEffect(() => {
if (!window || !window.localStorage) return
const storageString = localStorage.getItem(impersonateStorageKey)
if (!storageString) return
const data = JSON.parse(storageString)
impersonateRef.current(data.currentImpersonation)
}, [impersonateStorageKey])