Hooks Primer
7 Aug 2019
- Introduction
Intro
- Introduction
Resources
- Introduction
Documentation
- Introduction
useState
Example and comparison
- useState
class Counter extends Component {
state = {
counter: 0
}
increment = this.setState(
({ counter }) => ({ counter: counter + 1 }
)
decrement = this.setState(
({ counter }) => ({ counter: counter - 1 }
)
render() {
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>
}
}
- useState
Class Example
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useState
Hooks Example
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count => count + 1);
const decrement = () => setCount(count => count - 1);
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useState
Hooks Example
- useState
Takeaways
- Granular Control
- Memoized Functions
- Takes in an update object or update function ( T | T => T )
- Changes value and setter on each value change
- useState
Best Practices
- Keep the amount of state data to a minimum
- Prefer derived data (see: later)
- Make sure that, if you need your state to be a function, you prevent useState from treating your inbound state as an update function ( T => T ).
useEffect
Example and comparison
- useEffect
class Counter extends Component {
state = {
counter: 0
}
componentDidMount() {
document.title = `Ready to count!`;
}
componentDidUpdate(props, state) {
if (this.state.counter !== state.counter) {
document.title = `Counted: ${state.counter}`;
}
}
render() {
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>
}
}
- useEffect
Class Example
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(
() => document.title = `Counted ${count}`,
[count],
);
useEffect(
() => document.title = `Ready to count`,
[],
);
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useEffect
Hook Example
const NotificationsBadge = ({ id }) => {
const [notifications, setNotifications] = useState(0);
useEffect(
() => {
const subscription = API.subscribeToNotifications(
id,
notification => setNotifications(
notifications => [...notifications, notification]
)
);
return subscription.unsubscribe();
},
[id],
);
return (...);
}
- useEffect
Hook Subscription Example
- useEffect
Takeaways
- Scoped to memoized input array
- Takes in an effect function
- Effect function acts as an effect constructor, that returns the effect destructor
- Creates a subscription
- Handles functionality
- Returns the unsubscribe handler
- useEffect
Best Practices
- Don't duplicate effects on input array
- Use block functions as a default, to prevent useEffect interpreting any return values as functions (when they are by-products of an update process, for example)
- If dealing with effects that need destructuring on change, make sure to return a destructure function
- useEffect
Resize Example
const NotificationsBadge = () => {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(
() => {
const handler = () => setSize({
width: window.innerWidth,
height: window.innerHeight,
});
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
},
[],
);
return (...);
}
useMemo
Example
- useMemo
const Counter = () => {
const [count, setCount] = useState(0);
const [hasCounted, setHasCounted] = useState(false);
useEffect(
() => {
if (count !== 0) {
setHasCounted(true);
}
},
[count],
);
const title = useMemo(
() => hasCounted ? `Counted ${count}` : `Ready to count`,
[hasCounted, count],
);
useEffect(
() => document.title = title,
[title],
);
return <div>
<h1>{title}</h1>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useMemo
- useMemo
Takeaways
- Granular Control
- Memoized Returns
- Mostly used for derived data
- Takes a function that returns the value ( () => T )
- useMemo
Best Practices
- Use as much as needed instead of useState
- Be careful with the input array, make sure it's satisfactory to the output
- Remember that useState setters change on value set
- If derived data is a function, use useCallback instead
useCallback
Example and comparison
- useCallback
- useCallback
useCallback vs useMemo
const Counter = () => {
const [count, setCount] = useState(0);
const increment = useMemo(
() =>
() => setCount(count => count + 1),
[setCount],
);
const decrement = useCallback(
() => setCount(count => count - 1),
[setCount],
);
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useCallback
useCallback vs useState (don't)
const Counter = () => {
const [count, setCount] = useState(0);
const [increment, setIncrement] = useState(null);
// Won't work as expected
useEffect(
() => setIncrement(
() => setCount(count => count + 1)
),
[setCount],
);
const decrement = useCallback(
() => setCount(count => count - 1),
[setCount],
);
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
useContext
Example and comparison
- useContext
const CounterContext =
createContext({ counter, increment, decrement });
class Counter extends Component {
static contextType = CounterContext;
render() {
const { counter, increment, decrement } =
this.context;
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>
}
}
- useContext
Class Example
- useContext
Consumer Example
const CounterContext =
createContext({ counter, increment, decrement });
const Counter = () => (
<CounterContext.Consumer>
{({ counter, increment, decrement }) => (
<div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>
)}
</CounterContext.Consumer>
}
- useContext
Hooks Example
const CounterContext =
createContext({ counter, increment, decrement });
const Counter = () => {
const { counter, increment, decrement } = useContext(CounterContext);
return (
<div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>
);
}
- useContext
Takeaways
- Can consume N contexts, before rendering
- Can be used in conjunction with other use hooks
Custom Hooks
Example and comparison
- Custom Hooks
useReducer
Example and comparison
- useReducer
const Counter = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount(count => count + 1));
const decrement = useCallback(() => setCount(count => count - 1));
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useReducer
Basic State Example
const Counter = () => {
const [{ count: counter }, dispatch] = useReducer(
({ count: c }, { type, value }) => {
switch (type) {
case 'increment':
return { count: c + 1 };
case 'decrement':
return { count: c - 1 };
default:
throw new Error('Unknown action');
}
},
{ count: 0 }
);
const increment = useCallback(() => dispatch({ type: 'increment' }), [dispatch]);
const increment = useCallback(() => dispatch({ type: 'decrement' }), [dispatch]);
return <div>
<button onClick={increment}>Increment</button>
<p>Count: {counter}<p>
<button onClick={decrement}>Increment</button>
</div>;
}
- useReducer
useReducer Example
- useReducer
Takeaways
- Reminiscent of Flux / Redux
- Increases code size
- Usually not required
- All Done!
Hooks Primer
By Sabin Marcu
Hooks Primer
- 560