Why React Hooks (Part I: Complicated lifecycles)
(Source/Credits: https://dev.to/dinhhuyams/why-react-hooks-part-i-1jo)
Prerequisite: Basic knowledge about React In this post, we are going to talk about one in million re...
Prerequisite: Basic knowledge about React
In this post, we are going to talk about one in million reasons why we should all start to learn and use Hooks even though fishing is not our hobby ๐ ๐ฃ.
Hey Siri, set timer for 5 minutes ๐
If you have never tried Hooks or heard of it before, stay with me for 5 mins and you can immediately recognize the first important benefit of Hooks ๐ฅ.
I have started to use React hooks for about a month and it is a real game-changer. One clear benefit of Hooks is that it helps us remove redundant complicated React lifecycles.
Didnโt you see the no-fishing sign, son?
Me: Iโm not fishing, sir. ....
๐ Let's how it works in action
Our task today is simply to subscribe the user to a radio channel ๐ป.
1. Class component
For this simple task, we will use the componentDidMount lifecycle
```jsx class Radio extends React.Component { state = {channelId: 1}
componentDidMount() {
subscribeToRadio(this.state.channelId)
}
...
} ```
One channel is kind of boring ๐
Let's allow users to jump to their favorite channels by clicking the button. For this, we need to unsubscribe from the previous channel before subscribing to the new one, componentDidUpdate should be the right place to do this
```jsx class Radio extends React.Component {
...
componentDidUpdate(prevProps, prevState) {
if (prevState.channelId !== this.state.channelId) {
unsubscribeRadio(prevState.channelId)
subscribeToRadio(this.state.channelId)
}
}
changeChannel = () => {
const id = randomId()
this.state({channelId: id})
}
render() {
return (
<div>
<p>Current channel: ${channelId}</p>
<button onClick={this.changeChannel}>Change channel</button>
</div>
)
}
} ```
Last but not least, we have to unsubscribe to the channel when the user stops listening. We will do this in componentWillUnmount
```jsx class Radio extends React.Component {
...
componentWillUnmount() {
unsubscribeRadio(this.state.channelId)
}
} ```
So, for this dead simple radio subscribing task, we still need 3 lifecycles in total:
- componentDidMount
- componentDidUpdate
- componentWillUpdate
If you add more and more features, I assume it has some side-effects, to your app, these features would be grouped by lifecycle methods instead of by side-effect. You will end up stacking up logics all across these lifecycle methods.
๐คจ ๐ง ๐
Imagining when you have a bug ๐, you need to go through 3 different places to find and fix it. You have to play Three-bug Monte game to find that bug and I bet you all know how difficult it is to win that kind of game ๐คฆโ
2. Function component with Hooks
๐ฅ Hooks come to rescue
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
Let's see how Hook does to achieve the same result. To use Hooks, we need to convert the class component above to a functional component. We can create a super simple component in the following way:
```jsx const Radio = () => { const [channelId, setChannelId] = React.useState(1)
React.useEffect(() => {
subscribeToRadio(channelId)
return () => unsubscribeRadio(channelId)
}, [channelId])
const changeChannel = () => {
const id = randomId()
setChannelId(id)
}
return (
<div>
<p>Current channel: ${channelId}</p>
<button onClick={changeChannel}>Change channel</button>
</div>
)
} ```
๐ฒ Wow, It's magic. How do you do that?
React.useState and React.useEffect are React Hooks that helps you achieve the same results as you did with lifecycles. Even though you might not totally clear on those above Hooks, I bet the function names might give you some hints, and you can still immediately feel that Hooks make the code much cleaner and simpler.
As you can see, all the logics are concentrated in 1 Hook instead of 3 places like before. It is easier to debug. If you want to remove the feature, all you need is to delete the related effect.
๐ช Let's break it down, together.
โข useState
useState is a Hook that helps us manage local state in function component.
```jsx const Radio = () => { const [channelId, setChannelId] = React.useState(1)
const changeChannel = () => {
const id = randomId()
setChannelId(id)
}
...
} ```
The useState Hook accepts initial state as its argument. In the code above, 1
is the initial value for channelId.
This Hook returns an array which contains 2 variables where the first one is the current state and the second one is a function that allows us to update state. We are using array destructuring [channelId, setChannelId]
and you can name them whatever you want
โข useEffect
React.useEffect lets us perform side effects in function component
```jsx const Radio = () => {
const [channelId, setChannelId] = React.useState(1)
React.useEffect(() => {
subscribeToRadio(channelId)
})
...
} ```
In this effect, we perform the radio channel subscription. By default, our effect will run after every component's rendering and updating.
However, that's actually not what we want, if we only need to perform this effect only once after the first render (componentDidMount), we need to pass an empty array as a second optional argument of useEffect Hook. An empty array means that this effect depends on nothing, so it will only run on the mount (and unmount if you return a clean-up function)
```jsx const Radio = () => {
const [channelId, setChannelId] = React.useState(1)
React.useEffect(() => {
subscribeToRadio(channelId)
}, [])
...
} ```
Additionally, we also need to perform the effect after every time the channelId state changes (when the user clicks the button). We will tell the effect to do that by passing channelId to the array. Remember what you put here in the array is what the effect will depend on. The good news is, you can put multiple variables to this array!!
```jsx const Radio = () => {
const [channelId, setChannelId] = React.useState(1)
React.useEffect(() => {
subscribeToRadio(channelId)
}, [channelId])
...
} ```
The effect will determine when channelId changes by comparing the current channelId value with the previous channelId value (using shallow comparison ===).
If those values are unchanged, React would skip the effect ๐. It's the same when we use componentDidUpdate lifecycle to compare this.state.channelId and prev.state.channelId
Note: It uses SHALLOW equality instead of deep comparison
Lastly, we will unsubscribe to ๐ป when user changes the channel
```jsx const Radio = () => {
const [channelId, setChannelId] = React.useState(1)
React.useEffect(() => {
subscribeToRadio(channelId)
return () => unsubscribeRadio(channelId)
}, [channelId])
...
} ```
The effect returns a function known as the clean-up function.
When using class component, we have to split this logic to 2 components componentDidUpdate and componentWillUnmount
Our effect will not only run when first render but also every update (when channelId changes), therefore, the clean-up function will be called each time before the next render
3. Conclusion:
One benefit of Hooks is that it makes your React app 90% cleaner by removing the complicated lifecycles.
It helps us keep things together, group our app features by side-effect rather than spread them into different lifecycle methods
Feel the need for learning Hooks now, here are some good resources for you: - Hooks Intro - Modern React workshop by Kent C. Dodds (Part I) - Modern React workshop by Kent C. Dodds (Part II)
๐ ๐ช Thanks for reading! Brace yourself, part II is coming
Please leave your comments below to let me know what do you think about React Hooks
โ๏ธ Written by
Huy Trinh ๐ฅ ๐ฉ โฅ๏ธ โ ๏ธ โฆ๏ธ โฃ๏ธ ๐ค
Software developer | Magic lover
Say Hello ๐ on
โ Github
โ LinkedIn
โ Medium
Comments section