UseEffect is the core of React Hooks. Make sure you understand how it works and use it correctly to avoid bugs like this and that. In my previous work, I encountered numerous problems with it, such as getting old values, not executing when it should be executed, executing when it shouldn’t be executed…

Therefore, in order to avoid such embarrassment, save time racking our brains to find bugs and protect our hairline, we must learn how to useEffect hook properly.

What is the Effect

As the saying goes, you can win a hundred battles if you know your enemy. Let’s first understand what Effect is. In fact, most of us have been exposed to the concept of Side effect at some point in our development process

Side effects

The afterthought of fetching data, registering listening events, modifying DOM elements, etc. is a side effect because the page we render is static and any subsequent actions will affect it. while
useEffectIs specifically used to write

Side effects

Code, which is the heart of React.

Relationship to the life cycle

At present, articles on the market, including official documents, let us imagine useEffect as componentDidMount, componentDidUpdate, componentWillUnmount three life cycle combination. In fact, this is not the case. If you have to rely on the lifecycle of the useEffect operation mechanism, there will be some logical confusion, resulting in bugs. All we need to do is treat useEffect as a brand new feature, specifically for functional components, so we don’t get confused. Here’s an example to illustrate its various uses.

Run time

UseEffect must be executed once on render, other times depending on:

  • There is no second parameter.useEffectA hook takes two arguments, the first the code to execute and the second an array specifying a set of dependent variables. Effect is re-executed whenever any of these variables change.
  • There is no return value.useEffectYou can return a function to clean up each time a new render is rendered or the component is unmounted.

Let’s start with a simple example. If you want to see the full code and feel free to play with it, click the button below

Click to see examples


Let’s start with the top-level
code:

function App() {
  const [showList, setShowList] = useState(false);
  const [postCount, setPostCount] = useState(5);

  return (
    <div className="App">
      <button onClick={()= >setShowList(! showList)}> {showList ? "Hide" : "show "}</button>
      <button onClick={()= >SetPostCount (previousCount => previousCount + 1)}> Increment the number</button>
      {showList && <PostList count={postCount} />}
    </div>
  );
}
Copy the code

This component displays a list of articles, along with buttons that control whether the list of articles is displayed and how many articles are displayed. We use showList state to control whether is displayed or not. This is done to unmount and render the component to prove that useEffect hook runs once every time it is rendered and unmounted. The code for the component is as follows:

function PostList({ count = 5 }) {
  useEffect((a)= > {
    let p = document.createElement("p");
    p.innerHTML = 'Number of current articles:${count}`;
    document.body.append(p);
  });

  return (
    <ul>{new Array(count).fill(" article title ").map((value, index) => {return ()<li key={index}>
            {value}
            {index + 1}
          </li>
        );
      })}
    </ul>
  );
}
Copy the code

This component displays a

    list, generating some boring article titles for simplicity. Let’s focus on what useEffect does:

  • To create apThe element
  • Set up thepIs the number of current articles
  • additionalpbodyAt the end of the

In this case, the effect does not return any value and does not pass any arguments to it.

The answer is that this effect will append our newly appended P again every time we click the show or increase button when the count or showList changes. This is also a leak pit. If we add too much memory to this pit and don’t clean it up, it won’t take long for the browser to crash.

useEffect((a)= > {
  let p = document.createElement("p");
  p.innerHTML = 'Number of current articles:${count}`;
  document.body.append(p);

  return (a)= > {
    p.remove();
  };
});
Copy the code

So when we click the button, we make sure that only one P is on the current page. See, isn’t it easier to write this way than splitting componentDidMount and componentWillUnmount? We can easily get a reference to p in the same scope and just delete it.

Class in action – fetching data

In order to further explore useEffect Hook, I wrote an example imitating the actual work. Here, we use useEffect to capture data and display the list of blog articles. Please click the button below to view the complete code:

In this case, the code for the component has made some changes. First, we define two new states:

  • Posts. Saves a list of articles that are loaded remotely
  • Loading. Records ajax request status
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
Copy the code

We thought fetch was an asynchronous operation at first, so why not pass an async function to useEffect hook? No, the function passed to useEffect cannot be async because async essentially returns a Promise, and useEffect only receives a return value that is a function. Async receives the following exception:

Warning: An effect function must not return anything besides a function, which is used for clean-up.

/ / error
useEffect(async() = > {// const response = await fetch("https://jsonplaceholder.typicode.com/posts");
});
Copy the code

The correct way to write this is to define the fetching logic in a separate function and call it in useEffect:

useEffect((a)= > {
  // const response = await fetch("https://jsonplaceholder.typicode.com/posts");

  const loadPosts = async () => {
    setLoading(true);
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?_limit=${count}`
    );
    const data = await response.json();
    setPosts(data);
    setLoading(false);
  };

  loadPosts();
}, [count]);
Copy the code

It does this by setting loading state to true before requesting data, fetching a list of posts based on count, updating the return value to Posts State, and setting loading to false. Finally, according to the loading state, we display the loading or article list:

if (loading) return <div>loading...</div>;

return (
  <ul>
    {posts.slice(0, count).map((post, index) => {
      return <li key={post.id}>{post.title}</li>;
    })}
  </ul>
);
Copy the code

Second parameter

In the example above, we passed useEffect a second parameter and count as the dependent value. Each time count changes, this effect is reexecuted to load new data. In addition, if we hide the list and click the show button again, Effect will run again, because when we click hide, the component will be unmounted and then re-render when it is displayed again. You can see the sign.

If we remove the second argument, then we are stuck in a loop. Why? Since the posts and Loading states will be updated when effect is executed, and the component will render again when state changes, it is not difficult to draw a conclusion according to the rule that useEffect must be executed once in every render.

So what if we give it an empty array? No matter how much you click to increase the number, this effect will not be reexecuted, resulting in only the default 5 articles being loaded forever.

Add additional properties

We can also try adding another property to test useEffect’s array-dependent nature. In the
component we added a button for the layout state vertical and modify layout to control the horizontal and vertical layout of the component:

// APP
function App() {
  // Other code omitted
  const [vertical, setVertical] = useState(true);

  return (
    <div className="App">{/* Other code omitted */}<button onClick={()= >setVertical(prev => ! Prev)}> Change the layout</button>
      {showList && <PostList count={postCount} vertical={vertical} />}
    </div>); } // PostList function PostList({ count = 5, UseEffect (() => {// const response = await fetch("https://jsonplaceholder.typicode.com/posts"); const loadPosts = async () => { setLoading(true); const response = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=${count}` ); const data = await response.json(); setPosts(data); setLoading(false); }; loadPosts(); }, [count, vertical]); // Add vertical here as dependency // other code omitted}Copy the code

Here we add the second parameterverticalDependency, so every clickChange the layoutButton, the list of articles is loaded once, which is suitable for situations where the data needs to be rerequested when the layout changes:

If you don’t need to reload the data, just remove vertical from the dependency array.

To highlight

See if you are using the frequently used useEffect correctly? Here are the highlights:

  1. It’s not quitecomponentDidMount.componentDidUpdate.componentWillUnmountA combination of three life cycles (I have my own idea).
  2. Will be executed every time render is performed.
  3. If a function is returned, the code that returns the function must be run once before the next render or component unmount.
  4. If a dependent array is specified and is not empty, it is reruned whenever every element in the array changes.
  5. If the array is empty, execute only once on the first render, same as 3 if there is a return value.
  6. If theuseEffectAn infinite loop is created when state is updated in, and no dependent array is specified, or when state exists in a dependent array.

Did you get it? Have any question welcome to comment or private letter me! If you find this article helpful, please follow me. Thank you.