Understanding React useState and useEffect hooks

React hooks are JavaScript functions that you can import from React package in order to add features to your components. Hooks are available only for function-based components, so they can’t be used inside a class component.

React provides you with 10 hooks function, but only 2 of these hooks are going to be used very frequently when you write function components. They are useState and useEffect hooks. Let’s learn about useState first.

The useState hook is a function that takes one argument, which is the initial state, and it returns two values: the current state and a function that can be used to update the state. Here’s the hook in action:

import React, { useState } from 'react'

function UserComponent() {
  const [name, setName] = useState('John')
}

Notice the use of square brackets when state variable is declared. This is the ES6 array destructuring syntax, and it means we’re assigning the first element of the array returned by useState to name and the second element to setName variable.

So this means we have a state named name and we can update it by calling on setName() function. Let’s use it on the return statement:

import React, { useState } from 'react'

function UserComponent() {
  const [name, setName] = useState('John')

  return <h1> Hello World! My name is {name} </h1>
}

Since function components don’t have the setState() function, you need to use the setName() function to update it. Here’s how you change the name from “John” to “Luke”:

import React, { useState } from 'react'

function UserComponent() {
  const [name, setName] = useState('John')

  if(name === "John"){
    setName("Luke")
  }

  return <h1> Hello World! My name is {name} </h1>
}

When you have multiple states, you can call the useState hook as many times as you need. The hook receives all valid JavaScript data types such as string, number, boolean, array, and object:

import React, { useState } from 'react'

function UserComponent() {
  const [name, setName] = useState('Jack')
  const [age, setAge] = useState(10)
  const [isLegal, setLegal] = useState(false)
  const [friends, setFriends] = useState(["John", "Luke"])

  return <h1> Hello World! My name is {name} </h1>
}

And that’s all there is to it. The useState hook basically enables function components to have its own internal state.

The useEffect hook

The useEffect hook is the combination of componentDidMount, componentDidUpdate and componentWillUnmount class lifecycle methods. This hook is the ideal place to set up listeners, fetching data from API and removing listeners before the component is removed from the DOM.

Let’s look at an example of useEffect in comparison with class lifecycle methods. Normally in class component, we write this kind of code:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Nathan',
    };
  }

  componentDidMount() {
    console.log(
      `didMount triggered: Hello I'm ${this.state.name}`
    );
  }

  componentDidUpdate() {
    console.log(
      `didUpdate triggered: Hello I'm ${this.state.name}`
    );
  }

  render() {
    return (
      <div>
        <p>{`Hello I'm ${this.state.name}`}</p>
        <button
          onClick={() =>
            this.setState({ name: 'Gary'})
          }
        >
          Change me
        </button>
      </div>
    );
  }
}

Since componentDidMount is run only once when the component is inserted into the DOM tree structure, subsequent render won’t trigger the method anymore. In order to do run something on each render, you need to use componentDidUpdate method.

Using useEffect hook is like having both componentDidMount and componentDidUpdate in one single method, since useEffect runs on every render. It accepts two arguments:

  • (mandatory) A function to run on every render
  • (optional) An array of state variables to watch for changes. useEffect will be skipped if none of the variables are updated.

Rewriting the above class into function component would look like this:

const Example = props => {
  const [name, setName] = useState('Nathan');

  useEffect(() => {
    console.log(`Hello I'm ${name}`);
  });

  return (
    <div>
      <p>{`Hello I'm ${name}`}</p>
      <button
        onClick={() => {
          setName('Gary')
          }}>
        Change me
      </button>
    </div>
  )
}

The function component above will run the function inside of useEffect function on each render. Now this isn’t optimal because the state won’t be updated after the first click. This is where useEffect second argument come into play.

useEffect(() => {
    console.log(`Hello I'm ${name} and I'm a ${role}`);
  }, 
  [name]);

The second argument of useEffect function is referred to as the “dependency array”. When the variable included inside the array didn’t change, the function passed as the first argument won’t be executed.

The componentWillUnmount effect

If you have code that needs to run when the component will be removed from the DOM tree, you need to specify a componentWillUnmount effect by writing a return statement into the first argument function. Here is an example:

useEffect(() => {
    console.log(`useEffect function`);

    return () => { console.log("componentWillUnmount effect"); }
  }, [name] );

Running useEffect only once

To run useEffect hook only once like componentDidMount function, you can pass an empty array into the second argument:

useEffect(
  () => {
    console.log(`useEffect function`);
  }, 
  [] );

The empty array indicates that the effect doesn’t have any dependencies to watch for change, and without a trigger, it won’t be run after the component is mounted.

Take your skills to the next level ⚡️

I'm sending out an occasional email with the latest tutorials on programming, web development, and statistics. Drop your email in the box below and I'll send new stuff straight into your inbox!

No spam. Unsubscribe anytime.