NashTech Blog

React useEffect: Best Practices for Using and Avoiding it

Table of Contents

UseEffect hook is a powerful tool enables us to perform tasks such as fetching data, setting up subscriptions, and updating the DOM in response to component updates. However, while the useEffect hook is a great solution for many use cases, it may not always be the best choice due to potential performance concerns. In certain situations, using the useEffect hook can lead to unnecessary re-renders and slow down the performance of our application.

Effects are an escape hatch from the React paradigm. They let you “step outside” of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component’s state when some props or state change), you shouldn’t need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.

So, how to remove unnecessary Effects

1. You don’t need useEffect for transforming data

Consider an example: You want to calculate total of a list before displaying it. You might feel tempted to write an Effect that updates a state variable when the list changes. However, this is inefficient.

function Cart() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);

  useEffect(() => {
    setTotal(
      items.reduce((currentTotal, item) => {
        return currentTotal + item.price;
      }, 0)
    );
  }, [items]);
}

When you update the state, React will first call your component functions to calculate what should be on the screen. Then React will “commit” these changes to the DOM, updating the screen. Then React will run your Effects. If your Effect also immediately updates the state, this restarts the whole process from scratch!

To avoid the unnecessary render passes, transform all the data at the top level of your components. Or we can simply calculate it during rendering.

function Cart() {
  const [items, setItems] = useState([]);
  const total = items.reduce((currentTotal, item) => {
      return currentTotal + item.price;
    }, 0);
}

2. You don’t need useEffect for communicating with parents

Consider an example: You’re toggling isOpen state and calling the appropriate parent callbacks over here.

function ToogleUI({ onOpen, onClose }) {
  const [isOpen, setIsOpen] = useState(false);

  useEffect(() => {
    if (isOpen) {
      onOpen();
    } else {
      onClose();
    }
  }, [isOpen]);

  return (
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
  );
}

With below approach, both the ToogleUI component and its parent component update their state during the event

function ToogleUI({ onOpen, onClose }) {
  const [isOpened, setIsOpened] = useState(false);

  function handleToogle() {
     const nextIsOpened = !isOpened;
     setIsOpened(nextIsOpened);
     if (nextIsOpened) {
        onOpen();
     } else {
        onClose();
     }
  };

  return (
      <button onClick={handleToogle}>Toggle</button>
  );
}

3. You don’t need useEffect to handle user events

Consider an example: You are building a form and on mount it sends a analytics event and upon filling the form and clicking Submit it sends POST request to the /register endpoint.

function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [jsonToSubmit, setJsonToSubmit] = useState(null);

  
  useEffect(() => {
    post('/analytics/event', { eventName: 'visit_form' });
  }, []);

  useEffect(() => {
    if (jsonToSubmit !== null) {
      post('/api/register', jsonToSubmit);
    }
  }, [jsonToSubmit]);

  function handleSubmit(e) {
    e.preventDefault();
    setJsonToSubmit({ firstName, lastName });
  }
  return <form onSubmit={handleSubmit}>...</form>
}

The analytics POST request should remain in an Effect. This is because the reason to send the analytics event is that the form was displayed. However, the /api/register POST request is not caused by the form being displayed. You only want to send the request at one specific moment in time: when the user presses the button. It should only ever happen on that particular interaction.

function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  // ✅ Good: This logic runs because the component was displayed
  useEffect(() => {
    post('/analytics/event', { eventName: 'visit_form' });
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    // ✅ Good: Event-specific logic is in the event handler
    post('/api/register', { firstName, lastName });
  }
  return <form onSubmit={handleSubmit}>...</form>
}

Conclusion

When you’re not sure whether some code should be in an Effect or in an event handler, ask yourself why this code needs to run. Use Effects only for code that should run because the component was displayed to the user.

Reference links

https://medium.com/@heWhoScripts/useeffect-best-practices-for-using-avoiding-it-ae3f047db871

https://react.dev/learn/you-might-not-need-an-effect

Picture of kt1

kt1

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top