DEV Community

Alessio Michelini
Alessio Michelini

Posted on • Updated on

Understanding React useEffect

If you are new to React or you used React with class based components, you are probably trying to understand how exactly useEffect works, when to use and how to use it.

But before we show any code, we need to understand that a React component has a few various lifecycle events, and the main ones are the following:

  • A component is created
  • A component, or it's state, changes
  • A component is destroyed

And React has some very hand hooks that you can use to 'hook' to those events (sorry for the pun).

The useEffect hook

Now before we see how the hook works in practice, we need to see how this function is expecting from us, the developer, to be called.

The useEffect(callback, [dependencies]) expects a callback function as a first argument, and this must not be an async function, to essentially define what to execute when the hook is invoked.

The second argument, that can be optional, represents the dependencies, and the dependencies are essentially a list of state variables that we want to listen to, but I'll explain later in more detail.

But let's see how it works.

A component is created

When a component is added to the virtual DOM, and it's rendered for the first time, this is when we can say it has been created.
If you used React in the past, to handle compoment events, you probably used something like this code:

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

  // component is mounted
  componentDidMount() {
    // do something
    this.setState({
      name: 'Fred'
    });
  }

  // component state changed
  componentDidUpdate(prevProps, prevState) {
    //do something
  }

  // component is destroyed
  componentDidUnmount() {
    // do something
  }

  render() {
    const { name } = this.state;
    return <div>My name is {name}</div>;
  }
}
Enter fullscreen mode Exit fullscreen mode

So in the component above, we'll have our state to contain a variable called name that is empty at the very beginning, but right after the component is mounted, or created, will be set to Fred.

To do the exact same behaviour as above with the useEffect hook, using a functional component, our code will look like this:

import { useEffect, useState } from 'react';

const MyComponent = () => {
  const [name, setName] = useState('');

  useEffect(() => {
    setName('Fred');
  }, []);

  return <div>My name is {name}</div>;
}
Enter fullscreen mode Exit fullscreen mode

But let's explain this code a little bit.
As you see, we tell the useEffect to invoke a function that updates the state for the name state variable, but as a dependencies we pass an empty array.
With this hook, an empty array simply means "do it when the component mounts and only once".

So you might ask, why the need to pass an empty array? Why we don't simply pass nothing?

Because with the useEffect hook, if you pass no dependencies at all, it will call the callback function on every single render of the component, not only at the beginning, and that's something you normally want to avoid to to have the event handler to unnecessarily get detached and reattached on every time the component renders.

A component is updated

If you want to listen to a state variable and see when it changes, here is the place where you want to pass the array of dependencies.

For example in this code we have a firstname and lastname variables, and we want to console.log the variable every time it changes, our code will look like this:

import { useEffect, useState } from 'react';

const MyComponent = () => {
  const [firstname, setFirstname] = useState('Fred');
  const [lastname, setLastname] = useState('Flinstone');

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

  const handleFirstname = newName => setFirstname(newName);
  const handleSurname = newName => setLastname(newName);

  return (
    <div>
      <p>My name is {firstname} {lastname}</p>
      <a onClick={() => handleFirstname('Barney')}>Change firstname</a>
      <a onClick={() => handleSurname('Rubble')}>Change lastname</a>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the code above we set the initial firstname to be "Fred", but when we click on the component it changes to "Steve", and our hook will listen to the firstname to change, running the callback only when that variable changes, and not when any other ones do, like the lastname one.

And you can also use multiple useEffect in your component, for example if we want one hook each for our variables, we can something like this:

useEffect(() => {
  // do something when firstname changes
}, [firstname]);

useEffect(() => {
  // do something when lastname changes
}, [lastname]);
Enter fullscreen mode Exit fullscreen mode

A component is destroyed

The last lifecycle event I want to show you is the one invoked when a component is destroyed, or removed by the virtual DOM.
This is rather simple, and essentially all you need to do, is to return a function inside the callback of the useEffect.

Let's say that you want to do some stuff when you create the component, like reading some data from a datasource, and when you destroy the component you just want to get rid of it, your hook will look like this:


import { useEffect, useState } from 'react';

const MyComponent = () => {
  const [data, setData] = useState({});

  useEffect(() => {
    // fetch some data from somewhere and add it to the state
    setData(someStuff);
    return () => {
      // just empty the data
      setData({});
    }
  }, []);
}
Enter fullscreen mode Exit fullscreen mode

And again, this can be done with dependencies as well if you need to.

The above lifecycle event is not used extremely often, but it can be handy in some occasions, like telling a websocket connection to disconnect for example.

I hope that this post was useful for you to better understand how to use the React useEffect hook.

Top comments (0)