What is the expected return of `useEffect` used for?

Viewed 60769

In the React documentation for hooks they say:

"This also allows you to handle out-of-order responses with a local variable inside the effect"

useEffect(() => {
    let ignore = false;
    async function fetchProduct() {
      const response = await fetch('http://myapi/product/' + productId);
      const json = await response.json();
      if (!ignore) setProduct(json);
    }

    fetchProduct();
    return () => { ignore = true };
  }, [productId]);

Demo app

Please help me understand this better by explaining:

  1. Why is the return a function? return () => { ignore = true };
  2. What is ignored used for in this example?

Thanks!

8 Answers

Why is the return a function? return () => { ignore = true };

From the docs,

Why did we return a function from our effect? This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. They’re part of the same effect!

And

When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We’ll discuss why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues later below.

What is ignored used for in this example?

Initially in useEffect Hook ignore is set like, let ignore = false;. When fetchProduct function executes it checks for ignore is true and accordingly sets setProduct(json). This means we have state called product and setting the value in state using setProduct(json). This product in state is used to render details on page.

Note: As [productId] is passed as second argument to useEffect, fetchProduct function will only get executes when productId changes.

See optimizing performance by skipping effects.

I will explain it here, as it took me a while to understand the explanations above, so I will try to make it simpler on others.

Answers for your questions:
1- Why is the return a function? return () => { ignore = true };
useEffect offers the use of return function, which is used for cleanup function purposes, OK!, When do you need a clean up? If u made a subscription for something, and u want to unsubscribe from it for example, you should add unsubscription logic in "return function" inside useEffect , instead of putting the logic in other places, that may cause a race condition!

2- What is ignored used for in this example?
The ignore is used as a flag that tells the function to ignore the api call or not. when is that used? when you have a race condition.

[example for a race condition - you can ignore this part of u are already familiar with race conditions] For example u click in list of products, and when each product is clicked you will update the page with this product info, if u click quickly in the products for many times , the useEffect will be updated whenever product_id is changed, and at some point clicking on product1 [will call api for product1 ]then quickly product2 [will call api for product2 and retrieve data causing -> setProduct(product2data) ] while still data for product1 has just arrived causing -> setProduct(product1data) . This way we have product2 clicked ! but product1 data appears at page!!.

BUTTTT WHAT THAT HAVE TO DO WITH ignore ??
[logic explanation]
ignore , my dear , tells the setProduct to ignore the old data, and not set it, this way we only will set the last clicked product, even if old data arrived later.
[code explanation]
1- first scenario : no races , product1 is clicked -> ignore is false , call api , retrieve data , if(!ignore ) -> will set product1 data , now clean up function will set ignore to true , congrats !.

2- second scenario -> race condition , product1 is clicked -> ignore is false, call api , [data still hasn't arrived] ,now product2 is clicked ignore is false , call api , data arrived , if(!ignore ) -> will set product2 data , clean up function will set ignore to true , product1 function data has arrived -> if(!ignore ) -> is false OOOPS, will not set product1 data "old data".

AND THIS WAY WE WILL ALWAYS HAVE NEW DATA . CHEERRRRRRRRS :D

//all imports
function mufunc(){
  useEffect(()=>{ 
    const a = addEventListner('mouse' , console.log('mouse moves') )  //it prints when 
                                                                      //mouse moves
    return ()=>{
      removeEventListner(a)  //whenever the component removes it will executes
    }
  } ,[])
}

we use a boolean flag called ignore to let our data fetching logic know about the state (mounted/unmounted) of the component.If the component did unmount, the flag should be set to true which results in preventing to set the component state after the data fetching has been asynchronously resolved eventually. when component is unmounted then "return" (second part of useEffect) is called. but in this case because of dependency [query] when query changes, our component is re-rendered, so sideEffect is re-initialized. Run this code on your IDE then open Devtools in your browser like chrome and clear console then in input which the word react is written, add char 2 as fast as possible and then immediately remove char 2. check console to see what happened.

useEffect execute every render not once When you want to clean up effects from the previous render before executing the subsequent effects that's time to return a function with cleans up logic.

For the second question --What is ignored used for in this example? :

It is used to ensure that the latest value of productId is the one that our state update will depend on, mainly when more than one request is being processed simultaneously. In this case, ignore value will be true after every request that is being processed sends a response except for the one where the URL contains the latest updated value of productId. (here in this case ignore will remain false)

Now let's discuss the code above:

We know that useEffect runs the function declared in return before the next effect runs. Let's imagine that two changes in the value of productId happened so quickly (maybe due to a quick input typing which updates the value on every change). For the first update, the effect will run and set ignore to false. Let's say that before the fetch request is successfully processed and response is assigned a value, the second change to productId happens. Here the function returned by our old effect will run and assign true to ignore, and right after that, the new effect related to the new value of productId will also run assigning false to ignore and proceed to the fetch request with our new productId. However, the value of ignore* for the old effect is now true, which means that after getting a response the state update will be ignored ( ! ignore = false ). For the new effect if a successful response arrives with data and the code execution proceeds to the if statement before any change in productId value occurs, ignore will remain false (! ignore = true), and our state will be updated using the latest value of productId.

The return function is the cleanup function, or when the user leaves the page and the component will unmount. The array is the last part, and it is where you put the states that will update throughout the component's lifecycle.
For more

when component is unmounted then return (second part of useEffect) is called. but in this case because of dependency [query] when query changes, our component is re-rendered, so sideEffect is re-initialized. Run this code on your IDE then open Devtools in your browser like chrome and clear console then in input which the word react is written, add char 2 as fast as possible and then immediately remove char 2. check console to see what happened.

Related