Home > Software engineering >  How to ignore previous async effects when useEffect is called again?
How to ignore previous async effects when useEffect is called again?

Time:10-16

I have a simple component that makes an async request when some state changes:

const MyComp = () => {
  const [state, setState] = useState();
  const [result, setResult] = useState();

  useEffect(() => {
    fetchResult(state).then(setResult);
  }, [state]);

  return (
    <div>{result}</div>
  );
};

The problem is, sometimes the state changes twice in a short lapse of time, and the fetchResult function can take a very different amount of time to resolve according to the state value, so sometimes this happens:

Effect calls sequence diagram

As you can guess, as state now is state2 and not state1 anymore, I would like result to be result2, ignoring the response received in the then of the -obsolete- first effect call.

Is there any clean way to do so?

CodePudding user response:

I've not tested this... but my initial thought would be if you have the state in the response, you could check if the state fetched matches the current state. If not, then the state has changed since the request and you no longer care about the response so don't set it.

  useEffect(() => {
    fetchResult(state).then((response) => {
      response.state === state ? setResult(response.data) : false;
    });
  }, [state]);

You might also be able to do it by keeping a record of the fetchedState on each request.. and again discard it if it no longer matches.

  useEffect(() => {
    let fetchedState = state;
    fetchResult(fetchedState).then((response) => {
      fetchedState === state ? setResult(response) : false;
    });
  }, [state]);

CodePudding user response:

I would suggest you setup some kind of request cancellation method in the useEffect cleanup function.

For example with axios, it looks like that:

const MyComp = () => {
  const [state, setState] = useState();
  const [result, setResult] = useState();

  useEffect(() => {
    const source = axios.CancelToken.source();
    fetchResult({state, cancelToken: source.cancelToken }).then(setResult);
    return () => {
      source.cancel()
    }
  }, [state]);


  return (
    <div>{result}</div>
  );
};

You have a similar API with fetch called AbortController

What this will do is it will cancel the stale requests if your state changed so only the last one will resolve (and set result).

  • Related