Hooks

  • allows state and side-effects in function components
  • simple to use, but follow the rules
  • create custom hooks, great for encapsulating common functionality

Rules

  • only call hooks at the top level
    • don’t call hooks inside loops, conditions, or nested functions
    • rationale:
      • same hook can be used multiple times
      • they must be called in the same order every time, otherwise React does not know which hook is which
  • only call hooks from React functions
    • don’t call hooks from regular JavaScript functions
    • rationale:
      • hook state is bound to the component instance that is being rendered
  • (convention) custom hooks start with use

useState

  • const [currentState, setNextStateFn] = useState(initialValue?)
  • use for internal state that affects DOM
  • takes initial value; returns a tuple of the current value and a function to update it
  • docs
import React, { FC, useState } from 'react'

export const Counter: FC = () => {
  const [value, setValue] = useState(0)

  return (
    <>
      <button type="button" onClick={() => setValue(value - 1)}>
        -
      </button>
      <span> {value} </span>
      <button type="button" onClick={() => setValue(value + 1)}>
        +
      </button>
    </>
  )
}

export default <Counter />
0

useRef

  • const ref = useRef(initialValue?)
  • use for referencing elements, e.g. to use their methods
  • docs
import React, { FC, useRef } from 'react'

export const PlayPause: FC = () => {
  const videoRef = useRef<HTMLVideoElement>(null)
  const playPause = async () => {
    const video = videoRef.current as HTMLVideoElement
    if (video.paused) {
      await video.play()
    } else {
      video.pause()
    }
  }

  return (
    <>
      <video
        ref={videoRef}
        autoPlay
        loop
        src="https://media.tenor.com/videos/632c96bbc411d8baa3f7f43692474808/webm"
        aria-label="video"
      />
      <br />
      <button type="button" onClick={playPause}>

      </button>
    </>
  )
}

export default <PlayPause />

  • use for internal state that does not affect DOM, e.g. timer handles
import React, { FC, useRef, useState } from 'react'

export const SelfDestruct: FC = () => {
  const [destroyed, setDestroyed] = useState(false)
  const timeoutRef = useRef<number>()

  const cancel = () => {
    window.clearTimeout(timeoutRef.current)
    timeoutRef.current = undefined
  }
  const start = () => {
    cancel()
    timeoutRef.current = window.setTimeout(() => {
      setDestroyed(true)
    }, 2000)
  }

  return destroyed ? null : (
    <>
      <button type="button" onClick={start}>
        start self-destruct sequence
      </button>
      <button type="button" onClick={cancel}>
        cancel self-destruct sequence
      </button>
      <p>this message will destroy itself in 2 seconds</p>
    </>
  )
}

export default <SelfDestruct />

this message will destroy itself in 2 seconds

useEffect

  • useEffect(effectFn, deps?)
  • use for side-effects
  • effectFn: function that can have side-effects; optionally return cleanup function
  • deps: a list of values that the side-effect depends on (optional)
  • when dependencies change, the effect is cleaned up and ran again
  • if no dependencies are given, then the effect runs on every render
  • docs
import React, { FC, useEffect, useState } from 'react'

export const LocaleClock: FC<{ locale?: string }> = ({ locale = 'en-US' }) => {
  const [timeString, setTimeString] = useState<string>()
  useEffect(() => {
    const update = () => {
      setTimeString(new Date().toLocaleTimeString(locale))
    }
    update()

    const interval = window.setInterval(update, 500)

    return () => {
      window.clearInterval(interval)
    }
  }, [locale])

  return <p>{timeString}</p>
}

export const ToggleLocale: FC = () => {
  const [locale, setLocale] = useState('en-GB')
  const toggleLocale = () => {
    setLocale(locale === 'en-GB' ? 'th-TH-u-nu-thai' : 'en-GB')
  }

  return (
    <>
      <button type="button" onClick={toggleLocale}>
        toggle locale
      </button>
      <LocaleClock locale={locale} />
    </>
  )
}

export default <ToggleLocale />