Introduction of useState and useEffect

November 14th, 2020 | โ˜•๏ธ 6 min read
Photo by Efe Kurnaz, powered by unsplash.

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.

useState

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!

react lifecycle graph

useEffect

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 ๐Ÿป.

Discuss on Twitter