DEV Community

Iliya.Faz
Iliya.Faz

Posted on

useEffect in React: Summarized

First, we need to understand 3 things:

  • trigger
  • render
  • commit

These are the 3 steps taken in order for a component to be displayed on the screen.

  • First, a render is triggered.

For instance:

function Component({ text }) {
  return <div>{text}<div/>
}

// triggering the component:
<Component text="hello" />
Enter fullscreen mode Exit fullscreen mode
  • Then, the component is rendered, meaning React calls your component to figure out what to display on screen.
    ( In the example above, React would call <Component text="hello"/> and after doing the calculations come to the conclusion that it should look like <div>hello</div> in the DOM )

  • Finally, the DOM is modified to match the component.
    Either the component is appended to it (while mounting for instance) or it's updated. This is what we call "commit".

Side note: Mounting refers to when your React component is first rendered.

Note: commit only takes place if the rendered component is different from the real DOM element. And if it isn't, it will make the minimum amount of modifications required to update the element and sync it with the component.

useEffect runs on every commit

useEffect is a React hook that runs after the render is committed to the DOM, whereas other logic is typically executed when the component is being rendered, before it is updated in the real DOM.

For instance, to put it simply:

  1. <Component text="hello"/> triggers a render.
  2. It's rendered and converted into <div>hello</div>
  3. It's appended to the DOM.

And then it would be time for useEffect to do its job!

Examples use cases include:

  • When you want to access real DOM elements.
  • For instance, when you're looking to pause a video player using its reference with ref.current.pause();.
    Running the piece of code above outside of useEffect will throw an error, because it's executed before the video player element is appended to the DOM! putting it into a useEffect however will make sure it's executed only after the rendering and committing process is complete.

  • If you want to prevent useEffect from running on every re-render, use a dependency array. The dependency array tells React that it should skip re-running your Effect if your dependencies (the items in the array) are the same as they were during the previous render.

Different useEffect behaviours:

Here's a very simple demonstration from the official react docs:

useEffect(() => {  
// This runs after every render  
});  

useEffect(() => {  
// This runs only on mount (when the component appears)  
}, []);  

useEffect(() => {  
// This runs on mount *and also* if either a or b have changed since the last render  
}, [a, b]);
Enter fullscreen mode Exit fullscreen mode

Cleanup functions

Cleanup functions are functions you return in your useEffect hook.
React will call your cleanup function each time before the Effect runs again, and one final time when the component unmounts (gets removed).

useEffect(() => {
  // effect;
  return () => {
   // cleanup;
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

You could sort of think of the cleanup function as a function that stops everything the Effect is doing.

  • If your Effect subscribes to something, the cleanup function should unsubscribe.
  • If your Effect animates something in, the cleanup function should reset the animation to the initial values.
  • If your Effect fetches something, the cleanup function should either abort the fetch or ignore its result.

Here's a more realistic example:
Side note: If you're not familiar with AbortController have a look at here
It's pretty much used to cancel ongoing requests in here.

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;
  fetch(API, {
    signal: signal,
  })
    .then((response) => response.json())
    .then((response) => {
      // handle success
    });
  return () => {
    // cancel the request before component unmounts
    controller.abort();
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Not an Effect: Initializing the application

Some logic should only run once when the application starts. You can put it outside your components:

if (typeof window !== 'undefined') { // Check if we're running in the browser.  
  checkAuthToken();  
  loadDataFromLocalStorage();  
}

function App() {  
// ...  
}
Enter fullscreen mode Exit fullscreen mode

This guarantees that such logic only runs once after the browser loads the page.

Parts of the article are taken from the react docs, Credits to them for that!
If I happened to make a mistake, make sure to let me know.
See you on the next one!

Top comments (0)