You are a developer with previous experience using React, and you are comfortable using React class components. You're so comfortable with class components that switching to functional components sounds daunting. How will you manage the situation? How can you access the React component lifecycle?
If this sounds like you, then this guide will help you start transitioning your class components into functional components. React hooks provide the ability to use state and lifecycle functions inside functional components.
We'll cover the two built-in React hooks: useState
and useEffect
.
The main difference between class and functional components is the fact that class components have state whereas functional components have no state. The useState hook lets us add a local state to a functional component. This hook holds the state between re-renders.
Let's start by comparing the class component and functional component.
import React from 'react'; class Increment extends React.Component { constructor(props) { super(props); this.state = { counter: 0 }; this.setCounter = this.setCounter.bind(this); } setCounter() { this.setState({ counter: this.state.counter + 1 }); } render() { return( <div> <h1>Counter: {this.state.counter}</h1> <button onClick={this.setCounter}>Increment</button> </div> ) } }
In code above, we have a stateful component. Since you are already familiar with class components, let's keep this explanation brief.
A constructor is created at the start of the component and sets the initial state.
counter
is the only value in this example and is initially set to 0
.
To update the state, this.setState
is called in setCounter
function.
The onClick
event in the button tag calls setCounter
, allowing the state to be updated.
How we can we achieve the same thing in stateless component?
import React, { useState } from 'react'; const Increment = () => { const [counter, setCounter] = useState(0); function incrementByOne() { setCounter(counter + 1); } return ( <div> <h1>Counter: {counter}</h1> <button onClick={incrementByOne}>Increment</button> </div> ); }
Now let's describe what happens in this stateless component:
Importing the useState
hooks.
Declaring a state variable for the component. We are using array destructuring to get the getter and setter methods, and the parameter passed in the useState
hooks is the initial value for our state. In this case counter
is the getter and setCounter
is the setter and the initial value is 0
.
To update the state, setCounter
is called in incrementByOne
function.
The onClick event in the button tag calls setCounter
, allowing the state to be updated.
With this, we finished replicating the state management from class component into functional component. Hooray!
The useEffect
hook allows us to implement lifecycle methods to tell the component to perform an side effect after rendering. The different types of effects are not limited like changing document title, adding animation or music, data retrieval, and subscriptions. I have write the article about lifecyle method if you need some refresher.
As mentioned earlier, the useEffect
hook allows us to use React's lifecycle methods in stateless components. We'll look at mirroring the effects of componentDidMount
and componentDidUpdate
.
import React from 'react'; class FavoriteVehicle extends React.Component { constructor(props) { super(props); this.state = { vehicle: 'Walk ๐' }; componentDidMount() { console.log(`Component mounted | ${this.state.vehicle}`); } componentDidUpdate() { console.log(`Component updated | ${this.state.vehicle}`); } changeToCar() { this.setState({ vehicle: 'Car ๐' }) } changeToRocket() { this.setState({ vehicle: 'Rocket ๐' }) } render() { return( <div> <h1>My Fav Vehicle: {this.state.vehicle}</h1> <button onClick={changeToCar}>Change to Car ๐</button> <button onClick={changeToRocket}>Change to Rocket ๐</button> </div> ); } } }
componentDidMount
will be called as soon as the component is mounted. In our example, when FavoriteVehicle
is rendered, the string "Component mounted | Walk ๐"
will appear in console.
When you click the "Change to Car ๐"
button, componentDidUpdate
is called (because the state has changed), the console will print "Component updated | Car ๐"
. If you click the "Change to Rocket ๐"
button componentDidUpdate
will be called again. componentDidUpdate
will be executed every time the component is re-rendered.
How we can achieve the same thing in functional component? Let's see the code below.
import React, { useState, useEffect } from 'react'; const FavoriteVehicle = () => { const [vehicle, setVehicle] = useState('Walk ๐'); useEffect(() => { console.log(`Component mounted | ${vehicle}`); }, []); useEffect(() => { console.log(`Component updated | ${vehicle}`); }, [vehicle]) return( <div> <h1>My Fav Vehicle: {vehicle}</h1> <button onClick={() => setVehicle('Car ๐')}>Change to Car ๐</button> <button onClick={() => setVehicle('Rocket ๐')}>Change to Rocket ๐</button> </div> ); }
Let's describe what happened here:
Importing useState
and useEffect
hooks
We called useEffect
in our functional component. useEffect
accept two parameters, the first parameter is the effect callback, and the second parameter is dependency array. Notice that we called it twice in this example. When we called it the first time, we pass empty array, and on the second time we pass vehicle
as our dependency. There is a subtle difference in both of these useEffect
invocations.
If we pass empty array to our useEffect
invocation it will only run once, therefore it will behave same as componentDidMount
. Otherwise when we pass vehicle
on dependency list, our component will re-render every time if the value of vehicle
is changed, it will behave same as componentDidUpdate
.
When the component is rendered we will see both "Component mounted | Walk ๐"
and "Component updated | Walk ๐"
is logged in console. And when we tried to click "Change to Car ๐"
, we will see "Component updated | Car ๐"
is logged in console. This happen because we pass vehicle
in our dependency array in useEffect
hook. Note that "Component mounted | Walk ๐"
is only logged once this happen because we didn't pass anything in our dependency array.
Another thing to note is, if we only pass one parameter (the effect callback) into useEffect
, the effect will be always executed after every render. There will be a nasty bug in your code if you're trying to set the state in useEffect
without putting the state in the dependency array. Let's say maybe you're fetching userData from API and tried to save the returned data into your state. You will have an infinite re-render of your component! ๐งจ ps: When I first learn about useEffect this happen to me. I tell you so hopefully you are not suffer the same thing that I experienced. LOL'
The last thing that I want to mentioned is you also can mimic componentWillUnmount
behavior in useEffect
by having a return function in your effect callback. useEffect
will invoke this function when unmounting the component. This is useful when you want to unsubscribe to some listener API or to cleaning up async effect in your component.
That's it for the introduction of useState
and useEffect
. I hope this article will help you in your journey to understanding react hooks ๐ป.