Poznajemy kilka custom hooks z internetu, dzięki którym lepiej zrozumiemy useEffect, który znamy już dobrze i niewiele nam do nauki w tym temacie zostało.

Ok, hook useEffectOnce:

import { useEffect } from "react"

export default function useEffectOnce(cb) {
  useEffect(cb, [])
}

Pusta tablica zależności – zostanie odpalony 1 raz. Użycie:

import { useState } from "react"
import useEffectOnce from "./useEffectOnce"

export default function EffectOnceComponent() {
  const [count, setCount] = useState(0)

  useEffectOnce(() => alert("Hi"))

  return (
    <>
      <div>{count}</div>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </>
  )
}

Oczywiście to, co w callbacku nie może polegać na czymkolwiek reaktywnym (znamy już definicję reaktywności).

Btw, WebDevSimplified te hooki stworzył i trzyma na swoim Githubie, polecam poczytać. Ok, useUpdateEffect:

import { useEffect, useRef } from "react"

export default function useUpdateEffect(callback, dependencies) {
  const firstRenderRef = useRef(true)

  useEffect(() => {
    if (firstRenderRef.current) {
      firstRenderRef.current = false
      return
    }
    return callback()
  }, dependencies)
}

Czyli:

  • przekazujemy callback i zależności
  • przy mount ref ustawione na true
  • przy useEffect odpalonym po raz pierwszy mamy ref na true
  • zatem ustawiamy ref na false i wychodzimy
  • przy kolejnych zawołaniach wywołanych przez update, ale nie mount, nasz kod wywoła callback

Oto przykład użycia:

import { useState } from "react"
import useUpdateEffect from "./useUpdateEffect"

export default function UpdateEffectComponent() {
  const [count, setCount] = useState(10)
  useUpdateEffect(() => alert(count), [count])

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  )
}

Ok, a teraz useEventListener:

import { useEffect, useRef } from "react"

export default function useEventListener(
  eventType,
  callback,
  element = window
) {
  const callbackRef = useRef(callback)

  useEffect(() => {
    callbackRef.current = callback
  }, [callback])

  useEffect(() => {
    if (element == null) return
    const handler = e => callbackRef.current(e)
    element.addEventListener(eventType, handler)

    return () => element.removeEventListener(eventType, handler)
  }, [eventType, element])
}

Ok, czyli:

  • do hooka przekazujemy eventType, callback i element (domyślnie będzie window)
  • przy mount tworzymy ref do callbacka
  • mamy useEffect, który patrzy, czy nam się callback nie zmienił i ustawia ref na nowy callback
  • mamy useEffect, który słucha na zmianę eventType oraz element
    • Jeżeli element jest nullem to brutalny return
    • Tworzymy handler, którym jest callback z przekazanym eventem
    • dodajemy do element event listener określonego typu i z handlerem, który wywołuje callback przekazując event
    • w cleanup czyścimy event listener

Przykład użycia:

import { useState } from "react"
import useEventListener from "./useEventListener"

export default function EventListenerComponent() {
  const [key, setKey] = useState("")
  useEventListener("keydown", e => {
    setKey(e.key)
  })

  return <div>Last Key: {key}</div>
}

Więcej Reacta niedługo, przeanalizujmy to i szukajmy w internecie dobrych custom hooków.