iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
⛩️

Why Use useEffect? The Reason for Wrapping document.title = 'title'

に公開

Answer (Conclusion):

  • To separate rendering from effects
  • To set up cleanup functions

Isn't There No Point in Wrapping with useEffect?

Both of the following code snippets display "Hello world" and set the page title to "Hello world" when rendered.

const App1 = () => {
  useEffect(() => {
    document.title = 'Hello world';
  });
  return <h1>Hello world</h1>
};
const App2 = () => {
  document.title = 'Hello world';
  return <h1>Hello world</h1>
};

If they behave the same way, why do we need to wrap it in useEffect?

Reason 1: You Don't Have to Consider React's Internal Workings During Rendering

In App2, which doesn't use useEffect, document.title = 'Hello world' is executed while React is updating the DOM. Can you say exactly at what point during rendering the title change in App2 occurs? I can't.

On the other hand, in App1, which uses useEffect, the effect is executed after the DOM update is completely finished. Therefore, you don't need to think about the part wrapped in useEffect during App1's rendering process.

In this way, by wrapping it in useEffect, there is no chance that the effect you wrote will conflict with React's internal processing. It is quite hard (or rather, impossible) to write effects while considering React's DOM operations without using useEffect. By wrapping it in useEffect, the effect runs after React's rendering is complete, so you don't need to be aware of the behavior during rendering.

Let's look at another example. Below is a component that accepts key inputs. If you don't use useEffect, you would have to investigate every single time whether it's okay to attach an event listener in the middle of React's DOM operations.

const App = () => {
  const [num, setNum] = useState(0);

  // ↓ Not executed during rendering = safe to ignore
  useEffect(() => {
    const keydownListener = () => {}
    document.addEventListener('keydown', keydownListener);
    return () => document.removeEventListener('keydown', keydownListener);
  }, []);
  // ↑ Up to here

  return <div>{num}</div>;
};

By using useEffect, rendering and effects are completely separated, allowing you to keep the rendering phase simple.

Summary: If you don't have a perfect and complete understanding of React's internal rendering behavior, just play it safe and write your effects inside useEffect. (Even if you do understand it, I think you should still avoid it.)

Reason 2: You Can Set Up Cleanup Functions

If you don't use useEffect, you cannot perform cleanup (resetting the effect). This is the second problem.

For example, consider an SPA with multiple pages.

const NewsPage = () => {
  document.title = 'news';
  return <h1>News</h1>;
};
const AboutPage = () => {
  // Implementation of document.title = 'about' is missing!
  return <h1>About</h1>;
};
const Layout = ({children}) => (
  <div>
    <Nav />
    {children}
  </div>
);

AboutPage is missing the implementation for document.title. In this case, when you navigate from NewsPage to AboutPage, the title remains as "news". This is a bug.

If you had set up cleanup on the NewsPage side, you could mitigate the damage to some extent.

const NewsPage = () => {
  useEffect(() => {
    const prevTitle = document.title;
    document.title = 'news';
    return () => {
      document.title = prevTitle;
    }
  });
  return <h1>News</h1>;
};

This way, when you navigate to AboutPage, the title will revert to the previous one. This is better than it remaining as "news".

In this way, if cleanup is missing, the effects of what was rendered immediately before will persist. At this scale, the issue might seem minor. However, it becomes a major hindrance during debugging in actual app development. Imagine: "The bug where the title of AboutPage becomes 'news' only happens when you visited NewsPage just before." Since the reproduction conditions are complex, investigation will definitely be difficult.

Thus, if cleanup is not done, the influence of a specific rendering persists, making it difficult to render components in isolation. To avoid having to think about "what components have been rendered so far," cleaning up effects is essential.

Reason 3: Effects Are Not Executed During SSR

Effects defined with useEffect are not executed during server rendering. This is also stated in the official documentation.

Effects only run on the client. They don't run during server rendering.

useEffect – React

If you write document.title = 'title' without useEffect and perform server rendering, document.title = 'title' will be executed on the server. However, since the server environment does not have a document object, an error will occur.

As mentioned in the official documentation, useEffect is a "React Hook that lets you synchronize a component with an external system." Therefore, processes that synchronize with external systems (in this case, the browser's title) should be performed within useEffect.

Summary

  • Writing effects inside useEffect completely separates rendering from effects, making the component simpler.
  • Since it avoids conflicts with React's DOM operations, you can code without needing to know React's internal workings.
  • By cleaning up effects, you prevent bugs where influences from previous renderings persist, making application debugging easier.

Always wrap them in useEffect.

References

Discussion