In general, infinite loops are considered bad practice. But in some boundary cases, you have no choice but to go through an infinite loop. React’s infinite loop mode is a good thing to know.

When the infinite loop doesn’t stop, eventually the browser will kill the tag your code is running on. So don’t use “infinite loops” without any breakpoints.

useEffect

UseEffect Hook allows us to show side effects in a component. When hooks were introduced in react 16, useEffect hooks had more appeal than other hooks. Because it provides the combined functionality of componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods.

UseEffect Hook fires the callback function only when the dependency is changed. And it uses shallow comparisons to compare the values of the hooks.

You can think of the useEffect as an energy stone, which is the most powerful stone and will destroy you if you don’t handle it properly.

Missing dependent

UseEffect with missing dependencies is generally considered a bad practice, so it is always avoided.

Consider the following code, which calls the API all the time.

useEffect(() = > {
  fetch("/api/user")
    .then((res) = > res.json)
    .then((res) = > {
      setData(res);
    });
});
Copy the code

What happens

If useEffect only triggers a callback when a dependency changes, why do we have an infinite loop here?

Another important rule to consider in React is that “components will be re-rendered when state or props changes.”

In this code, we use setData to set the status value after a successful network call, which will trigger a re-rendering of the component. Because useEffect has no value to compare, it invokes the callback.

Fetch will trigger setData again, which will trigger component rerendering, and so on.

How can this be fixed?

We need to specify the dependency as an empty array.

useEffect(() = > {
  fetch("/api/user")
    .then((res) = > res.json)
    .then((res) = >{ setData(res); }); } []);// <- dependencies
Copy the code

According to official documentation, it is not safe to omit dependencies

Function of dependence

UseEffect uses shallow object comparisons to determine whether data has been changed. Because of the strange JavaScript conditional judgment system 😈.

var mark1 = function () {
  return "100";
}; // A unique object reference
var mark2 = function () {
  return "100";
}; // A unique object reference
mark1 == mark2; // false
mark1 === mark2; // false
Copy the code

Let’s look at the following code

import React, { useCallback, useEffect, useState } from "react";
export default function App() {
  const [count, setCount] = useState(0);
  const getData = () = > {
    return window.localStorage.getItem("token");
  };
  const [dep, setDep] = useState(getData());
  useEffect(() = > {
    setCount(count + 1);
  }, [getData]);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={()= > setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
Copy the code

The function getData is passed in as a dependency.

When you run this code, it will throw an “beyond maximum update” error, which means the code has an infinite loop.

What happened?

Due to useEffect, shallow comparison method is used to compare values. A shallow comparison of this function will always give false.

How can this be fixed?

To fix this, we need to use another hook called useCallback.

UseCallback returns a Memoized version of the callback that only changes when the dependency changes.

const getData = useCallback(() = > {
  return window.localStorage.getItem("token"); } []);// <- dependencies
Copy the code

Use arrays as dependencies

As you probably know, shallow comparisons between the two are always false, so passing dependencies as arrays also leads to an “infinite loop.”

Let’s look at the following code

import React, { useCallback, useEffect, useState } from "react";
export default function App() {
  const [count, setCount] = useState(0);
  const dep = ["a"];
  const [value, setValue] = useState(["b"]);
  useEffect(() = > {
    setValue(["c"]);
  }, [dep]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={()= > setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
Copy the code

Here, the array DEP is passed in as a dependency on useEffect.

When you run this code, the browser console throws this error.

What happened?

Shallow comparisons between two arrays are always false, so useEffect always triggers a callback.

How to fix this problem?

Since the return from useCallback is a function, we cannot use it.

So what should we do?

We need to use another hook called useRef

UseRef returns a mutable object with.current having an initial value.

Treat objects as dependencies

You might guess that shallow comparisons between two objects are always false, so useEffect will always trigger callbacks.

Let’s take a look at this code

import React, { useEffect, useState, useRef } from "react";
export default function Home() {
  const [value, setValue] = useState(["b"]);
  const { current: a } = useRef(["a"]);
  useEffect(() = > {
    setValue(["c"]);
  }, [a]);
}
Copy the code

Data is passed in as a dependency on useEffect.

When you run this code, your browser console will throw an infinite loop error.

What’s going on here?

The shallow comparison of objects will always be false, so it will trigger the useEffect callback.

How to fix this problem?

If we memorise our dependencies, we can break the endless cycle. So how do you do this?

Yes, we will use another hook called useMemo.

UseMemo recalculates the memorized value only when the dependency changes.

import React, { useMemo, useEffect, useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const data = useMemo(
    () = > ({
      is_fetched: false,}), []);// <- dependencies
  useEffect(() = > {
    setCount(count + 1);
  }, [data]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={()= > setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
Copy the code

False dependencies

Bad dependencies have nothing to do with React, or even javascript. We must take responsibility when we use the wrong dependencies.

Let’s take a look at this code

import React, { useEffect, useState } from "react";
const App = () = > {
  const [text, setText] = useState("");
  useEffect(() = > {
    setText(text);
  }, [text]);
  return null;
};
export default App;
Copy the code

I hope there is no need to explain the problem pattern and its fixes. If you want an explanation and fix, let me know in the comments.

Note: There are many ways to avoid the infinite loop inside the React component, and I’ve only mentioned a few.

Always use eslint-plugin-react-hooks or create-react-app, which will help you find problems before they reach production.

One company lost $7.2m in one week due to infinite cycling.

So be very careful when using the useEffect.