Sentry Answers>React>

React useEffect running twice

React useEffect running twice

Shivan M.

The Problem

When developing an application in React 18+, you may encounter an issue where the useEffect hook is being run twice on mount.

The Solution

This occurs because since React 18, when you are in development, your application is being run in StrictMode by default. In Strict Mode, React will try to simulate the behavior of mounting, unmounting, and remounting a component to help developers uncover bugs during testing.

Although this behavior may seem undesirable, or wrong, it exists to help developers ensure their code properly uses the useEffect hook. From this article from the React team, we can see the reasoning behind useEffect running twice:

This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.

In most cases, it should be fine to leave your code as-is, since the useEffect will only run once in production. In the case that your application isn’t functioning correctly because it runs twice, you can try the following solutions.

Refactor useEffect So That It Works Correctly After Remounting

The useEffect hook enables you to synchronize with state or external services that live outside the React tree.

Consider the following incorrect usage of the useEffect hook:

Click to Copy
"use client"; import { useEffect } from 'react'; export default function MyComponent() { useEffect(() => { if (product.isInCart) { showNotification(`Added ${product.name} to the shopping cart!`); } }, [product]); function handleBuyClick() { addToCart(product); } function handleCheckoutClick() { addToCart(product); navigateTo('/checkout'); } return ( <div> ... </div> ); }

In this example, we want to show a notification when a user puts a product in the cart. In this case, two event handlers encapsulate the addToCart functionality. It might be tempting to consolidate the code that shows the notification in the useEffect; however, this effect is incorrect and will lead to issues.

Suppose that the shopping cart is persisted through page reloads. In this case, when the page is reloaded, the notification will be shown again.

To refactor this function, we should determine why the notification should be shown. In this case, the notification should be shown because the user clicked the button, and not because the component was shown to the user. In general, effects are for code that should run because the component was shown to the user.

Using this logic, we can refactor the component as follows, removing the useEffect:

Click to Copy
"use client"; export default function MyComponent() { function buyProduct() { addToCart(product); showNotification(`Added ${product.name} to the shopping cart!`); } function handleBuyClick() { buyProduct(); } function handleCheckoutClick() { buyProduct(); navigateTo('/checkout'); } return ( <div> ... </div> ); }

The React documentation provides an extensive article on where and how to use the useEffect hook correctly.

Clean Up After the useEffect Hook

Consider the following component:

Click to Copy
"use client"; import { useEffect } from 'react'; export default function MyComponent() { const [count, setCount] = useState(0); useEffect(() => { const interval = setInterval(() => { setCount(count + 1); }, 1000); }, [count]) return ( <div> <h1>Count: {count}</h1> </div> ); }

In this component, we use the setInterval function to update the count variable every second. However, after the component is unmounted, we don’t clean up. This can cause memory leaks and lead to inaccurate values of count when remounting.

By adding a return statement to useEffect, we can clean up the interval, thereby ensuring that the side effect does not persist through component mounts.

Click to Copy
"use client"; import { useEffect } from 'react'; export default function MyComponent() { const [count, setCount] = useState(0); useEffect(() => { const interval = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(interval); }, [count]) return ( <div> <h1>Count: {count}</h1> </div> ); }

Disable Strict Mode

Although it is not recommended, in React 18 you can disable Strict Mode by removing the <React.StrictMode> tag from the return statement in your root component.

In Next.js, you can disable Strict Mode by setting the following parameter in next.config.js:

Click to Copy
module.exports = { reactStrictMode: false, }

Additional Reading

Understanding effects in React is integral to correctly using them in your applications and avoiding errors. The React documentation contains useful and deep articles about effects and their usage. You can find several of them below:

  • Sentry BlogHow to identify fetch waterfalls in React
  • Syntax.fmReact Server Components
  • Syntax.fmWhy the jQuery Creator Uses React and Typescript
  • Syntax.fmListen to the Syntax Podcast
  • Sentry BlogReact Native Debugging and Error Tracking During App Development
  • Syntax.fmDiscussion on building native iOS and Android apps with React Native
  • SentryReact Error & Performance Monitoring
  • Sentry BlogFixing memoization-breaking re-renders in React
  • Syntax.fm logo
    Listen to the Syntax Podcast

    Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.

    SEE EPISODES

Considered “not bad” by 4 million developers and more than 100,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.

© 2024 • Sentry is a registered Trademark
of Functional Software, Inc.