DEV Community

Ayako yk
Ayako yk

Posted on

Understanding useEffect in React: Managing Side Effects and Cleanup

React often requires us to manage and synchronize operations outside of its rendering process. In the upcoming blogs, we'll dive into how useEffect helps handle side effects in React.

React has two types of core logic, which are important to understand before diving into Effects:

Rendering Code:
This sits at the top level of a component and returns JSX. It must be pure --- like a mathematical formula, it only calculates and returns a result without causing any side effects.

Event Handlers:
These are nested functions that respond to user interactions, such as button clicks. Event handlers can change state or perform other actions, causing side effects (e.g., updating an input, making a network request, or navigating to another page).

React mounts on initial rendering and whenever there is a change in state. However, there are cases when a render is needed even without a state change or event handler. This can happen due to side effects that require a re-render.

Side effects are operations that happen outside of the main rendering process. They might include modifying non-local variables, raising errors, modifying the DOM, or interacting with external systems (like connecting to a server).

To synchronize React components after side effects, the Effect hook comes into play.

useEffect
useEffect delays the execution of the code inside it.
First, a component or a page is rendered.
After that, the code inside the useEffect is executed.

Here's an example from the React Documentation:

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  if (isPlaying) {
    ref.current.play();  // Calling these while rendering isn't allowed.
  } else {
    ref.current.pause(); // Also, this crashes.
  }

  return <video ref={ref} src={src} loop playsInline />;
}

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

This code won't work because it tries to access the ref while the component is being rendered before the DOM element is available. The ref of the DOM element doesn't exist yet. To fix this, we need to let the component render first and then execute the side effects using useEffect:

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

The Importance of Cleanup
When code that connects to a server is placed inside useEffect, it is executed when the component is mounted. If a user navigates to another component and then returns to the original one, this code is executed again, causing new connections to be queued. This can lead to bugs.

To fix this, we need to disconnect the server or perform a cleanup of the connection.

Here's an example from the React Documentation:

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Welcome to the chat!</h1>;
}
Enter fullscreen mode Exit fullscreen mode

In React Strict Mode (enabled by default in development), React intentionally runs certain lifecycle methods, like useEffect, twice to help developers identify issues and ensure code behaves correctly.

There's much more to learn about useEffect in React. While the React documentation categorizes some of these concepts as "advanced," it's essential for developers to gain a deep understanding of how React works.

Top comments (0)