ref – https://reactjs.org/docs/hooks-state.html
Initially, there was function components, and class components:
– Functional components does not have state. Nor does it have lifecycle functions.
– Class components have state, and has lifecycle functions.
However, with the introduction of Hooks, in functional components, we now can have state and lifecycle functionality.
Hooks – JS functions that lets you ‘hook into’ React State and LifeCycle features within your functional component.
– DOES NOT work inside class components.
– ONLY call top level. Don’t call hooks inside loops, conditions, standard functions, or nested functions.
– ONLY call from Functional Components.
Many hooks exists. You can even write your own. Let’s take a look at an very basic hook, useState
When would I use a Hook?
If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component. We’re going to do that right now!
Hook useState returns a pair of state. A state value, and a setState value.
1 2 3 |
function example() { const [ count, setCount ] = useState(0); } |
Whatever name you get first is to get the state property. The second parameter you simply add a set to it to say you want to set this state property.
The 0 is the default value we want to give for our state property.
1 2 3 |
const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{...}, {...}, {...}]); |
We use it like so. We declare count value, set its default to 0, and then declare its set functionality setCount.
We then can set it in our JSX.
In order to set new count, we simply use setCount.
In order to use it, we simply use the variable count.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } |
UseEffect
The Effect Hook lets you perform side effects in function components
– Data fetching
– subscribing
– manually changing the DOM
are all examples of side effects. We call these operations “side effects” (or “effects” for short) because they can affect other components and can’t be done during rendering.
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API. (We’ll show examples comparing useEffect to these methods in Using the Effect Hook.)
When you call useEffect, you’re telling React to run your “effect” function after flushing changes to the DOM.
Effects are declared inside the component so they have access to its props and state. By default, React runs the effects after every render — including the first render.
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don’t need a special API to read it — it’s already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.
Does useEffect run after every render? Yes!
By default, it runs both after the first render and after every update. Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } |
Example
1 2 3 4 5 6 7 |
function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); } |
We declare the count state variable, and then we tell React we need to use an effect. We pass a function to the useEffect Hook. This function we pass is our effect. Inside our effect, we set the document title using the document.title browser API. We can read the latest count inside the effect because it’s in the scope of our function. When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM. This happens for every render, including the first one.
Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale. Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes the effects behave more like a part of the render result — each effect “belongs” to a particular render.
When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once.
This is why React also cleans up effects from the previous render before running the effects next time.
We’ll discuss why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues later below.
ref – https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Explanation: Why Effects Run on Each Update
Note – Update in this case means that the useState’s previous and current state are DIFFERENT
If the previous and current state are the same, then React will bail out, and none of the children of the component will be rendered.
If you’re used to classes, you might be wondering why the effect cleanup phase happens after every re-render, and not just once during unmounting. Let’s look at a practical example to see why this design helps us create components with fewer bugs.
Earlier on this page, we introduced an example FriendStatus component that displays whether a friend is online or not. Our class reads friend.id from this.props, subscribes to the friend status after the component mounts, and unsubscribes during unmounting:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } |
But what happens if the friend prop changes while the component is on the screen? Our component would continue displaying the online status of a different friend. This is a bug. We would also cause a memory leak or crash when unmounting since the unsubscribe call would use the wrong friend ID.
In a class component, we would need to add componentDidUpdate to handle this case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // Unsubscribe from the previous friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // Subscribe to the next friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } |
Forgetting to handle componentDidUpdate properly is a common source of bugs in React applications.
Now consider the version of this component that uses Hooks:
1 2 3 4 5 6 7 8 9 10 11 12 |
function FriendStatus(props) { useEffect(() => { // this effect ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // clean up this effect return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); |
This behavior ensures consistency by default and prevents bugs that are common in class components due to missing update logic.
Mount with { friend: { id: 100 } } props
DOM gets updated. Causes previous clean up effect to be called.
There is none, so our to be called after the render.
1 |
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect |
Update with { friend: { id: 200 } } props
DOM gets updated. Causes returned effect clean up effect to be called.
1 |
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect |
Render complete, call current effect
1 |
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect |
Update with { friend: { id: 300 } } props
DOM gets updated. Causes returned cleanup effect function to be called.
1 |
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect |
Render complete. calls current effect
1 |
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect |
As times goes by…or when app quits, it will call update one last time.
The previous clean up effect function executes.
Causes returned cleanup effect function to be called.
1 2 |
// Unmount ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect |
Optimizing Performance by Skipping Effects
As shown, cleaning up and/or applying the effect after every render might create a performance problem in some cases.
In class components, we can solve this by writing an extra comparison with prevProps or prevState inside componentDidUpdate:
1 2 3 4 5 |
componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${this.state.count} times`; } } |
This requirement is common enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:
1 2 3 |
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Only re-run the effect if count changes |
In the example above, we pass [count] as the second argument. What does this mean? If the count is 5, and then our component re-renders with count still equal to 5, React will compare [5] from the previous render and [5] from the next render. Because all items in the array are the same (5 === 5), React would skip the effect. That’s our optimization.
When we render with count updated to 6, React will compare the items in the [5] array from the previous render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6. If there are multiple items in the array, React will re-run the effect even if just one of them is different.
This also works for effects that have a cleanup phase:
1 2 3 4 5 6 7 8 9 10 |
useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }, [props.friend.id]); // Only re-subscribe if props.friend.id changes |
Creating basic custom Hook
We create a get and set for our property.
get property is pretty standard so we just leave it.
We can over the set property by declaring another function name and using the default set property function from useState.
After we’re done, we simply create a literal object, insert those properties, and return it
userOrderCount.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { useState } from 'react'; function useOrderCountHook() { // already declared function to get order count const [orderCount, setOrderCount] = useState({ count: 0 }); // implement the setOrderCount const changeOrderCount = () => { setOrderCount({ count: orderCount.count + 1 }) } // put both properties into object to be returned and used return { orderCount, changeOrderCount }; } export default useOrderCountHook; |
We import it into main. Then simply use those properties as see fit. Put the get into the view.
Put the set as callback of the interactive controls.
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function App() { const orderHook = userOrderCountHook(); return ( <div className="App"> <header className="App-header"> <h1>count: {orderHook.orderCount.count}</h1> <img src={logo} className="App-logo" alt="logo" /> <button onClick={orderHook.changeOrderCount}> increment </button> </header> </div> ); } export default App; |