reactjs - Hvordan unngå uendelige oppdateringer i useEffect, når avhengigheten er en funksjon


En hook sjekker om det finnes etterligningsinformasjon lagret i lagring når siden gjengis, og i så fall setter den denne informasjonen i den globale AppState-konteksten.

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])

Med en andre krok, som vedvarer en endring i den nåværende etterlignede identiteten til lagringen:

useEffect(() => {
    if (!window || !window.localStorage) return;
    localStorage.setItem(impersonateStorageKey, JSON.stringify({ /* ... */}))
}, [currentImpersonation, impersonateStorageKey])

Pluss de relevante delene fra 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,
    }
}

Dette fungerer som det skal, men linteren klager over at avhengigheten impersonate mangler fra den første useEffect-hooken

Når jeg legger til impersonate i avhengighetslisten, vil dette forårsake en konstant oppdateringssløyfe og gjøre applikasjonen uresponsiv.

Jeg vet hva som forårsaker denne oppførselen, men jeg klarer ikke å se en løsning (bortsett fra å ignorere regelen) på hvordan man kan bryte løkken og gjøre linten fornøyd.

Hvilke tilnærminger kan jeg ta her?

Svar #1

Du kan memoize funksjonen når du oppretter den med 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,
  }
}

Hvis du ikke kan, vil en løsning være å legge funksjonen i en ref. ref er en uforanderlig referanse til et objekt, med current-egenskapen som er foranderlig. Siden selve ref er uforanderlig, kan du bruke den som en avhengighet uten å aktivere useEffect (linteren vet det, og du trenger ikke engang å angi det som en avhengighet). Du kan endre current så mye du vil.

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])

Relaterte artikler