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
- more details
- rules are enforceable
eslint-plugin-react-hooks
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 functiondeps
: 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 />