React Hooks
Content
- Why use React Hooks
- What is a hook
- When should I use Hook
- Rules of hooks
- Hooks API
- Tips for hooks
Why use Hooks?
Current Problems
- It’s hard to reuse stateful logic between components
- Complex components become hard to understand
- Classes confuse both people and machines
Hooks let you
- Reuse stateful logic without changing your component hierarchy
- Split one component into smaller functions based on what pieces are related
- Use more of React’s features without classes
What is a Hook?
import React, { useState } from 'react';
function Example() {
const [state, setState] = useState({});
// ...
}
When should I use Hook?
if you write function components and realise that you need to add state
Rules of Hook
- Only Call Hooks at the Top Level
- Only Call Hooks from React Functions
Why order matter
// first render
useState('Mary') // 1 Initialize
useEffect(persistForm) // 2
useState('Poppins') // 3
useEffect(updateTitle) // 4
// second render
useState('Mary') // 1 Read
useEffect(persistForm) // 2
useState('Poppins') // 3
useEffect(updateTitle) // 4
React relies on the order in which Hooks are called
useState('Mary') // 1. Read the name state
// useEffect(persistForm) // 🔴 This Hook was skipped!
useState('Poppins') // 🔴 2 (but was 3).
useEffect(updateTitle) // 🔴 3 (but was 4).
if (name !== '') {
useEffect(function persistForm() {
//...
});
}
Hooks API
- useState
- useEffect
- useCallback
- useMemo
- useRef
useState
function Counter({initialCount}) {
const [count, setCount] = useState(()=>initialCount);
return (
<>
<button onClick={
() => setCount(initialCount)}> Reset</button>
<button onClick={
() => setCount(prevCount => prevCount + 1)}>Plus</button>
</>
);
}
useEffect
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});
More about useEffect
- Mutations, subscriptions, timers, logging, and other side effects
- effects run after every completed render
- useEffect fires after layout and paint, during a deferred event.
- useLayoutEffect fires synchronously
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
const memoizedValue = useMemo(() =>
computeExpensiveValue(a, b),
[a, b]);
useRef
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>
Focus the input</button>
</>
);
}
Tips for Hook
Should I use one or many state variables
function Box() {
const [state, setState] = useState({
left: 0, top: 0,
width: 100, height: 100 });
}
//spreading ensures we don't lose data
setState(state => ({ ...state, left: 10, top: 10 }));
// Recommended way
function Box() {
const [position, setPosition] = useState({
left: 0, top: 0 });
const [size, setSize] = useState({
width: 100, height: 100 });
}
How to skip effect on updates?
useEffect(() => {
fetchData();
}, []);
How to abort data fetch in effect hook?
useEffect(() => {
let ignore = false;
async function fetchProduct() {
const response = await fetch('http://myapi/product/1');
const json = await response.json();
if (!ignore) setProduct(json);
}
return () => { ignore = true };
}, []);
Performance optimisations
How do I implement shouldComponentUpdate?
const Button = React.memo((props) => {
// your component
});
Are Hooks slow because of creating functions in render?
Solution
-
The useCallback Hook lets you keep the same callback reference between re-renders
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
Solution
2. useMemo Hook makes it easier to control when individual children update, reducing the need for pure components.
function Parent({ a, b }) {
const child1 = useMemo(() => <Child1 a={a} />, [a]);
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
Solution
3. useReducer Hook reduces the need to pass callbacks deeply
const TodosDispatch = React.createContext(null);
function TodosApp() {
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
Prevent rerender with memo and useContext hook
- (Preferred): Split contexts that don't change together
- Split your component in two, put memo in between
- One component with useMemo inside
React Hooks
By jingliu
React Hooks
- 283