The Problem
I hear a lot about how to translate class lifecycle methods into hooks. I think this can be helpful at first explaining them but harmful in the long run if this is the only way you think about hooks.
componentDidMount is a lifecycle method that can trip you up if you try to implement it with hooks. You want something to run just once and set some values in your component state.
Say a component took a user id and called some endpoint to load the user:
function User({ id }) {
const [user, setUser] = React.useState()
React.useEffect(async () => {
const userResult = await axios.get(`/users/${id}`)
const { data } = userResult
setUser(user)
}, [])
return <div>{user ? user.name : 'loading...'}</div>
}
This looks fine if you are thinking about components in a class lifecycle way. There is a subtle bug here though. Say someone navigated away through React Router and changed pages without re-rendering this component. This effect wouldn't re-run. If you landed on another users page, the old data would be there until a page refresh occured.
The Explaination
We need to think of react hooks in a new way. Don't build your hooks like you would your class components. Think of our components in effects and state. Our useEffects allow us to sync our effects with our state.
In this case, our state is the user inside of the component. We fire off an effect so that we can eventually load the user into state. This effect has an id dependency. Thus, we need to add that to our dependency array:
function User({ id }) {
const [user, setUser] = React.useState()
React.useEffect(async () => {
const userResult = await axios.get(`/users/${id}`)
const { data } = userResult
setUser(user)
}, [id])
return <div>{user ? user.name : 'loading...'}</div>
}
If your effects are interacting with your state and changing it, then you need to make sure that effect runs every time your state changes.
The Solution
Luckily, we can have our code editor tell us when we are using state inside of an effect.
The React team has built an eslint plugin that checks the state you use in an effect and makes sure that state is also in the dependency array.
If you are using a recent version of CRA, then you already have this eslint rule installed!
The eslint plugin for React hooks can save you from introducing subtle bugs into your application.
yarn add eslint-plugin-react-hooks --dev
The react team suggests this configuration:
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
},
// I had to add this for my set up
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
}
}
Top comments (1)
Hi Ian,
After using hooks, I just can't go back to Class component life cycle methods, which I had to look up everytime for method definitions, what props/states are passed to each.
useEffect
became more natural as I don't have to figure out when it's called and can control when it's called.To make a comparison, old class component life cycle methods reminds me of old ASP.NET Web forms, which had its life cycle methods, such as page load, page unload, etc and ASP.NET MVC rid that issue.
And
async
method callback foruseEffect
will trigger a warning.Homebrew React Hooks: useAsyncEffect Or How to Handle Async Operations with useEffect
Laurin Quast ・ Jul 5 ・ 7 min read
Also discussed here
Async useEffect is pretty much unreadable #14326
The only alternative is using
then()
chains which aren't very readable at their own.