yeti logo icon
Close Icon
contact us
Yeti postage stamp
We'll reply within 24 hours.
Thank you! Your message has been received!
A yeti hand giving a thumb's up
Oops! Something went wrong while submitting the form.

React Hooks 102: When to Avoid useEffect

By
-
February 12, 2025
software developer

Optimizing your React code with best practices for React Hooks

React Hooks are powerful tools for managing state and side effects in React applications. However, the useEffect hook is often overused—leading to overly complex, error-prone, and inefficient components. In this post, we explore when not to use useEffect and how to leverage React’s built-in capabilities for cleaner, more efficient code.

TL;DR: Avoid useEffect Unless Absolutely Necessary

  1. Don't use useEffect to update state variables derivable in render.
  2. Don't use useEffect to cache calculations.
  3. Don't use useEffect to handle user events.
  4. Do use useEffect only for synchronizing with external systems after a render.

The React Render Cycle in a Nutshell

Whenever a React component needs to update—either because it received new props or its internal state changed— React will re-run the component function to produce new JSX. This is often referred to as a “re-render.” After the render completes, any registered useEffect hooks are evaluated. If an effect updates state, React will trigger yet another re-render, and the cycle continues.

This cycle means any state update you perform inside a useEffect will lead to additional re-renders. Overusing or misusing useEffect—for instance, setting state in ways that could be computed directly during the render—can create unnecessary render loops, degrade performance, and produce a bad developer experience.

React’s Render Cycle

Every time your component renders, React:

  1. Initializes values: Using hooks like useState and simple constants.
  2. Returns JSX: The output from your component function.
  3. Updates the DOM: Through React’s reconciliation process.
  4. Schedules side effects: Via useEffect (executed after the DOM update).

When useEffect is overused or misapplied (especially for computations that can be done within the component’s render), it can:

  1. Trigger extra re-renders: Causing performance issues.
  2. Complicate compoenents: Slowing down development and creating a worse DevX.
  3. Introduce buggy code: Obscuring straightforward data flow and introducing side effects and unexpected behavior.

Common Misuses of useEffect

Below are some common, but incorrect, utilizations of useEffect, along with better alternatives.

1. Updating State Variables in useEffect

Rule of Thumb 

useEffect should not be used to update state variables.

The Issue:

Using useEffect to derive or update state variables (which could simply be computed during the render) will cause a re-render after the effect runs—potentially leading to redundant render cycles.

Example (WRONG):

const Wrong = ({ firstName, lastName }: Props) => {
   const [fullName, setFullName] = useState<string>('');
    useEffect(() => {
     setFullName(firstName + ' ' + lastName);
   }, [firstName, lastName]);
    return (
     <Container>
       <Typography>{fullName}</Typography>
     </Container>
   );
 };

Better Approach (RIGHT):

const Right = ({ firstName, lastName }: Props) => {
 // Or directly in JSX
 const fullName = `${firstName} ${lastName}`;

 return (
   <Container>
     <Typography>{fullName}</Typography>
   </Container>
 );
};

2. Caching Calculations with useEffect

Rule of Thumb 

useEffect should not handle state variable calculations

The Issue:

Calculating derived data in a useEffect and storing it in state can cause unnecessary re-renders, because every time that state updates, the component re-renders. If the calculations are pure and only depend on existing props or state, you can typically do this calculation during the render—or use a memoization hook like useMemo. This is quite similar to the previous example but it’s helpful to know that even more complicated computations should not be placed in a useEffect when updating a state variable.

Example (WRONG):

const Wrong = ({ items }: Props) => {
   const [filteredItems, setFilteredItems] = useState(items);
   const [activeFilter, setActiveFilter] = useState<string | null>(null);
    useEffect(() => {
     if (activeFilter) {
       setFilteredItems(items.filter((item) => item.type === activeFilter));
     } else {
       setFilteredItems(items);
     }
   }, [activeFilter, items]);
    return (
     <Container>
       <Box>
       <Button onClick={() => setActiveFilter('red')}>Red</Button>
         <Button onClick={() => setActiveFilter('blue')}>Blue</Button>
         <Button onClick={() => setActiveFilter('green')}>Green</Button>
       </Box>
       <Box>
         {filteredItems.map((item) => (
           <Typography key={item.name}>{item.name}</Typography>
         ))}
       </Box>
     </Container>
   );
 };

Better Approach (RIGHT):

const Right = ({ items }: Props) => {
   const [activeFilter, setActiveFilter] = useState<string | null>(null);
    const filter = (item: { name: string; type: string }) => {
     if (activeFilter === null) {
       return true;
     }
     return item.type === activeFilter;
   };
    // Ideal for expensive calculations
   // const filteredItems = useMemo(() => items.filter(filter), [activeFilter]);
   const filteredItems = items.filter(filter);
    return (
     <Container>
       <Box>
         <Button onClick={() => setActiveFilter('red')}>Red</Button>
         <Button onClick={() => setActiveFilter('blue')}>Blue</Button>
         <Button onClick={() => setActiveFilter('green')}>Green</Button>
       </Box>
       <Box>
         {filteredItems.map((item) => (
           <Typography key={item.name}>{item.name}</Typography>
         ))}
       </Box>
     </Container>
   );
 };

3. Handling User Events with useEffect

Rule of Thumb

useEffect should not handle user events

The Issue:

Using useEffect to manage user-driven side effects complicates event handling and can cause errors. Instead, handle those actions directly in an event handler.
For example, in an ecommerce app, if you save an item to cart by adding it to local storage, then the incorrect logic below would show a notification on reload if a user already added an item to cart. We don’t want that message to appear on load, but only at the moment when a user adds the item to cart. 

Example (WRONG):

const Wrong = ({ items, selectedItem, setSelectedItem }: Props) => {
   const { createSnackNotice } = useSnackbar();
    useEffect(() => {
     if (selectedItem) {
       createSnackNotice(
         `User selected ${selectedItem.name}`,
         SnackbarVariant.Success
       );
     }
   }, [selectedItem]);
    return (
     <Container>
       <Box>
         {items.map((item) => (
           <Typography onClick={() => setSelectedItem(item)} key={item.name}>
             {item.name}
           </Typography>
         ))}
       </Box>
     </Container>
   );
 };
  export default Wrong;

Better Approach (RIGHT):

const Right = ({ items, setSelectedItem }: Props) => {
   const { createSnackNotice } = useSnackbar();
    const handleSelection = (item: { name: string; type: string }) => {
     setSelectedItem(item);
     createSnackNotice(`User selected ${item.name}`, SnackbarVariant.Success);
   };
    return (
     <Container>
       <Box>
         {items.map((item) => (
           <Typography onClick={() => handleSelection(item)} key={item.name}>
             {item.name}
           </Typography>
         ))}
       </Box>
     </Container>
   );
 };

When Should You Use useEffect?

Despite the potential pitfalls, useEffect is still valuable for synchronizing your component with external systems after it has rendered. Use it for:

  1. Fetching data from APIs
  2. Subscribing to external data sources or event listeners
  3. Integrating with third-party libraries (e.g., Google Maps, WebSockets)
  4. Manually syncing with external storage (if you really need to)

Rule of Thumb 

useEffect should only be used because the component was displayed

Final Thoughts

“Effects are an escape hatch from the React paradigm. They let you step outside of React and synchronize your components with external systems.” — You Might Not Need an Effect

In many cases, React’s built-in mechanisms and specialized libraries like Apollo Client's useQuery, React Router’s useSearchParams, or React’s useSyncExternalStore can provide cleaner alternatives to useEffect. The useEffect is meant to be a last resort to do things in a “non-React” way. Knowing that frontend programs can be complicated, React gave us an “escape hatch”, but it’s best stay within the paradigm whenever feasible.

Yeti designs and develops innovative digital products. If you have a project you'd like to get started on, we'd love to chat! Tell us a bit about what you're working on and we'll get back to you immediately!

You Might also like...

code on a computerManaging Perisistent Browser Data with useSyncExternalStore

Struggling to keep React state in sync across tabs and sessions? Learn how to use useSyncExternalStore to manage state persistence with localStorage and sessionStorage—without complex state management libraries. Improve performance and streamline your app’s state logic.

software developer codingFintech Security with GraphQL Shield

Securing fintech applications is crucial, and GraphQL’s flexibility can be a security risk if not properly managed. GraphQL Shield helps enforce fine-grained authorization using role-based (RBAC) and attribute-based (ABAC) access control. In this post, we’ll explore why authorization matters, how to implement secure, composable rules, and walk through a demo app showcasing best practices.

Developer codingCross-Domain Product Analytics with PostHog

Struggling with cross-domain user tracking? Discover how PostHog simplifies product analytics across multiple platforms. Learn how to set up seamless cross-domain tracking, gain valuable marketing insights, and optimize user engagement with minimal effort. Read our step-by-step guide and example project to streamline your analytics today!

Browse all Blog Articles

Ready for your new product adventure?

Let's Get Started