DEV Community

Lucas Santos de Souza
Lucas Santos de Souza

Posted on

How not to useEffect

The first thing that we have to understand is:
 

Effects lifecycle are not the same thing as the Component's lifecycle

 

Components may mount, update, or unmount. An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it. This cycle can happen multiple times if your Effect depends on props and state that change over time. react.dev

 

Effect's only run after the render. This information alone already discards the idea that we can use useEffect to run some code before the render (something like the old componentWillMount).
 

  • Effect's will always run after the render.
  • Also, effect's will only cause a rerender if you change an state (this case is a bad use, will talk about in a sec)

 

In this post I want to focus on the bad use of useEffect.

 

Let's take this very simple but common example below:

const [firstName, setFirstName] = useState<string>("John");
const [lastName, setLastName] = useState<string>("Stewart");

const [fullName, setFullName] = useState
<string>("");

useEffect(() => {
  setFullName(firstName + " " + lastName);
}, [firstName, lastName]);
Enter fullscreen mode Exit fullscreen mode

React will run the following order of actions:

  1. Will store the value "John" to the firstName state;
  2. Will store the value "Stewart" to the lastName state;
  3. Will store an empty string to the fullName state;
  4. With all the calculations finished, will render the component;
  5. Then will call the effect considering the array of dependencies [firstName, lastName];
  6. The effect will schedule the change for the fullName state;
  7. Will start a new render (caused by setFullName), going through firstName and lastName with the same values;
  8. Then will store the value "John Stewart" to the fullName state;
  9. Finally will render the component with all the information;

 

Describing all the steps makes easy to see the problem: We already have all the information that we need on the first render, so why render twice?

 

Instead wee can do something much simpler, removing the state for fullName and the effect:

const [firstName, setFirstName] = useState<string>("Taylor");
const [lastName, setLastName] = useState<string>("Swift");

const fullName: string = firstName + " " + lastName;
Enter fullscreen mode Exit fullscreen mode

Let's list it out the steps again:

  1. Will store the value "John" to the firstName state;
  2. Will store the value "Stewart" to the lastName state;
  3. Will store the value "John Stewart" to the fullName const;
  4. With all the calculations finished, will render the component;

That's it!

If the firstName and/or lastName states change at some point, the component will rerender by itself and then store the new value in fullName.

For more bad examples of useEffect use, you can look at the react docs article here: https://react.dev/learn/you-might-not-need-an-effect
 
 

Let's see some good uses of Effects:

useEffect was created so we can synchronize with external systems. Like a browser API, set up a server connection, or send an analytics log. All these things happen after the render.

Let's look at this page that renders a Video.

  • First we have the page:
export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? "Pause" : "Play"}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

 

So here we have a page with the isPlaying state, that allows the user to control the video with a button.

 

  • And here we have the Video component:
function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  });

  return (
    <>
      <video ref={ref} src={src} loop playsInline />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

 

In this component, we have:

  • A ref for the HTML video tag, so we can access all its methods, properties, and events;
  • An effect that calls the method play() or pause() based on isPlaying's value.

 

The user can click multiple times on the Play/Pause button and the video player will stay synchronized to the isPlaying value.

 

So, we could see a simple but objective example on how we can use Effects to synchronize our component with a external system.

Top comments (0)