useEffect
In topic 3 of React Hooks: Tyler McGinnis
In programming, a side effect is a state change that can be observed outside of its local environment, or interacts with the outside world.
To minimize bugs in software, we try to make our program be as predictable as possible, and minimize side effects.
Common side effect examples in web dev include mutating non-local variables, making network requests, or updating the DOM.
Because managing side-effects properly is so crucial, React comes with a built-in hook to do it, useEffect
, which allows you to do side effects in function components.
Whenever you want to do something that reaches out of the functional component (make a network request, interact with the DOM) you reach for useEffect
.
There are three things that are crucial to understand about the useEffect
API:
how to add an effect
how to skip re-invoking the effect
(optionally) how to clean up the effect
Adding an effect
To add a side-effect to your component you call the useEffect
hook and pass it a function that defines the effect:
React.useEffect(() => {
document.title = 'My new Title'
})
By default this effect will be re-invoked after every render. So, including the initial render the sequence will go:
React runs the function,
React updates the DOM,
The browser paints those updates to the view
The effect is run
This is so that the side-effect does not block the UI.
This is really important to remember. People often misunderstand the useEffect hook because they don’t understand when it runs.
For example we might try naively to do a network request like this:
function Profile() {
const [profile, setProfile] = React.useState(null)
React.useEffect(() => {
getGithubProfile('tyler') // fetch request
.then(setProfile)
})
if (profile === null) {
return <p>loading...</p>
}
return ( <MyUI>)
}
This will result in an infinite loop - we’ll load the initial loading screen, React will invoke the side effect, the data will come back and we’ll update the component state. React will then re-render as its state has changed, update the DOM, and then… invoke the side-effect again, which will update the state again and so on.
How do we avoid this? By passing a second argument to useEffect. This will be an array of all the values that the effect depends on. The effect will only be run again when one of those values changes. It looks like this:
React.useEffect(() => {
// Will be invoked on initial render
// and all subsequent re-renders.
});
React.useEffect(() => {
// Will be invoked on initial render
// and when 'id' or 'authed' changes
}, [id, authed])
React.useEffect() => {
// Will be invoked on initial render
// and never again
}, [])
There’s one more scenario to cover - what about when a side effect needs some clean up to avoid a memory leak or other problem.
To manage this, the final piece of the useEffect API is that if you return a function from the side-effect function, that will be invoked before invoking the new effect on a re-render and right before removing the component from the DOM.
Like this:
React.useEffect(() => {
// my side effect
return () => {
// my cleanup
}
}
Again the execution order matters. React will still prioritize the UI, so if you trigger a re-render that will also require a side-effect to be cleaned-up and re-executed it will first run the functional component and update the DOM, then when the browser has painted the UI it will clean up any old effect, and then finally run the new effect.