Advanced Hooks
- remember the rules
useCallback
const memoizedFn = useCallback(fn, deps)
<button onClick={() => ...} />
triggers unnecessary re-renders- because
(() => ...) !== (() => ...)
- every event handler should be wrapped with
useCallback
- docs
useMemo
const memoizedValue = useMemo(computeValueFn, deps)
useMemo
will only recomputememoizedValue
when one ofdeps
have changed- wrap expensive computations to improve render performance
- docs
import React, { FC, useMemo } from 'react'
const fib = (n: number): number => {
if (n <= 2) return 1
return fib(n - 1) + fib(n - 2)
}
export const Fib: FC<{ n: number }> = ({ n }) => {
const f = useMemo(() => fib(n), [n])
return (
<pre>
{n}-th fibonacci number: {f}
</pre>
)
}
export default <Fib n={10} />
10-th fibonacci number: 55
useReducer
const [currentState, dispatchFn] = useReducer(reducerFn, initialState)
- use for
- complex state
- distributed state updates
- migrating from redux
- if possible, use multiple
useState
s instead - docs
import React, { FC, Reducer, useCallback, useReducer } from 'react'
interface State {
count: number
}
interface IncrementAction {
type: 'increment'
}
interface DecrementAction {
type: 'decrement'
}
type Action = IncrementAction | DecrementAction
const reducer: Reducer<State, Action> = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
throw new Error()
}
}
export const Counter: FC = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 })
const decrement = useCallback(() => dispatch({ type: 'decrement' }), [])
const increment = useCallback(() => dispatch({ type: 'increment' }), [])
return (
<>
Count: {state.count}
<button type="button" onClick={decrement}>
-
</button>
<button type="button" onClick={increment}>
+
</button>
</>
)
}
export default <Counter />
Count: 0
useLayoutEffect
- same as
useEffect
but runs synchronously, immediately after rendering - use for
- DOM manipulation
- when relying on order of effects
- prefer
useEffect
- docs
useImperativeHandle
useImperativeHandle(ref, createRefValueFn, deps?)
- use for
- exposing an imperative API
- syncing two refs
- docs
import React, {
FC,
Ref,
RefObject,
useCallback,
useImperativeHandle,
useRef,
} from 'react'
export interface Focusable {
focus(): void
}
export const FocusableInput: FC<{ focusable?: Ref<Focusable> }> = ({
focusable,
}) => {
const inputRef = useRef<HTMLInputElement>(null)
const focus = useCallback(() => {
inputRef.current?.focus()
}, [])
useImperativeHandle(focusable, () => ({ focus }), [focus])
return (
<div>
<label>
focusable input
<input ref={inputRef} placeholder="required" />
</label>
</div>
)
}
export const Error: FC<{ target: RefObject<Focusable> }> = ({
target,
children,
}) => {
const onClick = useCallback(() => {
target.current?.focus()
}, [target])
return (
<div>
{children}
<button type="button" onClick={onClick}>
focus field
</button>
</div>
)
}
const Example: FC = () => {
const focusableRef = useRef<Focusable>(null)
return (
<>
<FocusableInput focusable={focusableRef} />
<Error target={focusableRef}>this field is required</Error>
</>
)
}
export default <Example />
this field is required