Optimizing useEffect in React
Tags: react, reactnative, javascript, webdev
Published: 2022-12-10 11:35:35

> Originally published at [https://huzaima.io](https://huzaima.io/blog/optimizing-useeffect-in-react).

`useEffect` is one of the most widely used hooks in React. Optimizing `useEffect` in React gives you a significant boost in performance and sometimes gets your code rid of nasty bugs. I've encountered many situations where there were performance issues or some difficult-to-find bugs due to incorrect usage of `useEffect`. Here I'll discuss a few of the optimization patterns which are very easy to adhere to yet very effective.

## 1. Use primitives in the dependency array

We often pass JavaScript objects in the `useEffect` dependency array. Although nothing is wrong with doing that, sometimes it makes more sense to destructure the JavaScript object and only pass the specific value to the dependency array of `useEffect`. Let's take a look at an example. Suppose we have a `UserInfo` component which takes the `user` object as a prop and fetches the user's transaction data whenever the user changes. We need to _only_ get the user's transaction data when the user changes. Our user object looks like this:

```json
{
  "id": 123,
  "name": "John Doe",
  "imageURL": "...",
  "isActive": false
}
```

The typical implementation will look like this:

```jsx
function UserInfo({ user }) {

  useEffect(() => {
    getUserData(user.id);
  }, [user]);

  return (
    ...
  );
}
```

This will get user data from the server whenever the user object changes. But looking at the user object, it only makes sense to get the new data when the `user.id` changes. There could be instances where the user's data changes but the `id` don't, which won't benefit us in making an API call and getting the data from the server again. Let's say the user's `isActive` was `false` initially and later changed to `true`. In this case, we don't need to get the user's transaction data again because the user is still the same. We can avoid this redundant API call by changing our `useEffect` dependencies. Here's the refactored component using primitive data types:

```jsx
function UserInfo({ user }) {
  const { id } = user;

  useEffect(() => {
    getUserData(id);
  }, [id]);

  return (
    ...
  );
}
```

With the above optimization of `useEffect`, we will only get the user's transactional data when the user `id` changes. Remember, in `useEffect` dependencies, non-primitives (like objects and arrays) are checked for equality only by reference, not by the value. So the following code will always return `false`:

```jsx
{ name: "Huzaima" } === { name: "Huzaima" }
```

## 2. Avoid needless dependencies

Sometimes, we use `useEffect` to derive a new state from our current state. Suppose we've a counter which totals the length of the string every time the input value is updated. We can implement that like this:

```jsx
function CharacterCounter() {
  const [inputValue, setInputValue] = useState("");
  const [compoundStringLength, setCompoundStringLength] = useState(0);

  useEffect(() => {
    if (inputValue) {
      setCompoundStringLength(compoundStringLength + inputValue.length);
    }
  }, [inputValue, compoundStringLength]);

  return (
    ...
  );
}
```

The above code results in `useEffect` being executed infinitely. Why? Because of the incorrect dependency. We're updating `compoundStringLength` inside the `useEffect` and using the same as a dependency. How do we solve this problem? We need to use `compoundStringLength` inside the `useEffect` for computation, so we can't get rid of it. But what we can do is remove it from the dependency array. You might think this will result in an incorrect value of `compoundStringLength` due to `useEffect` being a closure. You're right... and wrong. This is correct because `useEffect` is a closure, so we won't get the correct value of `compoundStringLength` if we don't use it in the dependency array. Still, there's a way of getting the correct value of `compoundStringLength` without specifying it in the dependency array. The state setter function (`setCompoundStringLength`) doesn't only accept value but also a function. The signature of that function looks like this:

```jsx
setCompoundStringLength: (currentStateValue) => newStateValue;
```

We can leverage the above function signature in the `useEffect` to get the correct value of `compoundStringLength` for our computations _without specifying it as a dependency_. The refactored optimization of `useEffect` looks like this:

```jsx
function CharacterCounter() {
  const [inputValue, setInputValue] = useState("");
  const [compoundStringLength, setCompoundStringLength] = useState(0);

  useEffect(() => {
    if (inputValue) {
      setCompoundStringLength(
        (compoundStringLength) => compoundStringLength + inputValue.length
      );
    }
  }, [inputValue]);

  return (
    ...
  );
}
```

## 3. Hoist functions

Any unmemoized function that you define inside the component is recreated on every render. A very naive example is this:

```jsx
function EmailValidate() {
  const [email, setEmail] = useState("");

  const isEmailValid = (input) => {
    const regex =
      /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

    if (regex.test(input)) return "Email is valid";
    return "Email is invalid";
  };

  return (
    <>
      <input value={email} onChange={(event) => setEmail(event.target.value)} />
      <p>{isEmailValid(email)}</p>
    </>
  );
}
```

Here you can see that the `isEmailValid` function is a pure function and doesn't depend on the component's props and/or state. We can easily optimize this by extracting the function outside the component. Rewriting the above component will look like this:

```jsx
const isEmailValid = (input) => {
  const regex =
    /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  if (regex.test(input)) return "Email is valid";
  return "Email is invalid";
};

function EmailValidate() {
  const [email, setEmail] = useState("");

  return (
    <>
      <input value={email} onChange={(event) => setEmail(event.target.value)} />
      <p>{isEmailValid(email)}</p>
    </>
  );
}
```

## 4. Clean up

![Clean up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwf198f2xryya1aojg4t.gif)

The primary purpose of `useEffect` is to handle side effects. Sometimes you have to clean up those side effects like event handler subscriptions or observables. You can return the function having clean-up logic from the function inside `useEffect`. Let us take an example of an input field which should clear when the user presses the `Escape` key. We can do so like this:

```jsx
function Input() {
  const [input, setInput] = useState("");

  const escFunction = useCallback((event) => {
    if (event.key === "Escape") {
      setInput("");
    }
  }, []);

  useEffect(() => {
    document.addEventListener("keydown", escFunction, false);

    return () => {
      document.removeEventListener("keydown", escFunction, false);
    };
  }, [escFunction]);

  return (
    <>
      <input value={input} onChange={(event) => setInput(event.target.value)} />
    </>
  );
}
```

In the above code, we're returning a function from inside the `useEffect`. That function unsubscribes the `keydown` event listener, saving us from nasty memory leak problems.

## 5. Use multiple effects to separate concerns


![divide and conquer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmco5od6rj8sgnwro54t.gif)

Ever heard of divide and conquer? Yup! That's precisely what we need to do here. Aka [separation of concerns](https://engineering.teknasyon.com/separation-of-concerns-on-the-front-end-with-react-fd5d4afcc298), a widely practiced principle in software engineering. [React team advocates for separation of concerns](https://reactjs.org/docs/hooks-effect.html#tip-use-multiple-effects-to-separate-concerns) in `useEffect`, i.e. using different `useEffect` for every use case. Suppose we've a component which gets `userId` and `organizationId` in props. We make an API call to get user and organization data based on the props. We can write `useEffect` like this:

```jsx
useEffect(() => {
  getUser(userId);
  getOrganization(organizationId);
}, [userId, organizationId]);
```

The above `useEffect` will work fine. But there's one fundamental problem. If the `userId` change but the `organizationId` don't, `getOrganization` will still get called. Why? Because we've added two separate concerns in a single `useEffect`. We need to segregate them into two different `useEffect`. We can rewrite it like this:

```jsx
useEffect(() => {
  getUser(userId);
}, [userId]);

useEffect(() => {
  getOrganization(organizationId);
}, [organizationId]);
```

## 6. Custom hooks

Custom hooks are a great way of extracting side effects, which can be reused by many other components. It's also an elegant way of organizing your code. Suppose we've a custom hook to return details about the input string. We can write that custom hook like this:

```jsx
function useStringDescription(inputValue) {
  if (!inputValue) {
    return defaultValue;
  } else {
    const wordCount = inputValue.trim().split(/\s+/).length;
    const noOfVowels = inputValue.match(/[aeiou]/gi)?.length || 0;
    return {
      wordCount,
      noOfVowels,
    };
  }
}
```

The `useStringDescription` hook returns the number of vowels and the word count of a string passed in the argument. We can use this hook like this:

```jsx
function App() {
  const [inputValue, setInputValue] = useState("");
  const stringDescription = useStringDescription(inputValue);
  const { wordCount, noOfVowels } = stringDescription;

  useEffect(() => {
    console.log("stringDescription changed", stringDescription);
  }, [stringDescription]);

  return (
    ...
  );
}
```

Let's try to use the above setup and see how many times the `useEffect` is executed.

![unnoptimized rerender](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clioiv59uosc2o1qf8yk.gif)

You can see that `useEffect` is called even when the `stringDescription` object's value doesn't change. We can destructure the `stringDescription` object as defined in the 1st rule of this article, or we can optimize our custom hook like this:

```jsx
function useStringDescription(inputValue) {
  const [stringDescription, setStringDescription] = useState(defaultValue);

  useEffect(() => {
    if (!inputValue) {
      setStringDescription(defaultValue);
    } else {
      const wordCount = inputValue.trim().split(/\s+/).length;
      const noOfVowels = inputValue.match(/[aeiou]/gi)?.length || 0;

      if (
        wordCount !== stringDescription.wordCount ||
        noOfVowels !== stringDescription.noOfVowels
      ) {
        setStringDescription({
          wordCount,
          noOfVowels,
        });
      }
    }
  }, [inputValue, stringDescription.noOfVowels, stringDescription.wordCount]);

  return stringDescription;
}
```

Now, we're updating the state when any value changes. Let's check out the results:


![optimized rerender](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wf8rfn45sii3gud2ioh.gif)

See? `useEffect` didn't get called needlessly because the referential equality holds.

These were the bunch of `useEffect` optimizations we can use to make the usage of hooks more effective and get rid of nasty and notoriously hard to find bugs caused due to incorrect usage of hooks. Optimizing `useEffect` in React also gives you a boost in performance. What are your secret `useEffect` recipes? Let me know on [twitter](https://twitter.com/HuzaimaKhan). And don't forget to read my previous blog about [promise speed up](/blog/speed-up-promises-javascript).