Twinfriends
Twinfriends

Reputation: 1997

React - Old promise overwrites new result

I have a problem and I'm pretty sure I'm not the only one who ever had it... Although I tried to find a solution, I didin't really find something that fits my purpose.

I won't post much code, since its not really a code problem, but more a logic problem.

Imagine I have the following hook:

useEffect(() => {
    fetchFromApi(props.match.params.id);
}, [props.match.params.id]);

Imagine the result of fetchFromApi is displayed in a simple table in the UI.

Now lets say the user clicks on an entity in the navigation, so the ID prop in the browser URL changes and the effect triggers, leading to an API call. Lets say the call with this specific ID takes 5 seconds.

During this 5 seconds, the user again clicks on an element in the navigation, so the hook triggers again. This time, the API call only takes 0,1 seconds. The result is immediatly displayed.

But the first call is still running. Once its finished, it overwrites the current result, what leads to wrong data being displayed in the wrong navigation section.

Is there a easy way to solve this? I know I can't cancel promises by default, but I also know that there are ways to achieve it...

Also, it could be possible that fetchFromApi is not a single API call, but instead multiple calls to multiple endpoints, so the whole thing could become really tricky...

Thanks for any help.

Upvotes: 1

Views: 439

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281894

The solution to this is extremely simple, you just have to determine whether the response that you got was from the latest API call or not and only then except it. You can do it by storing a triggerTime in ref. If the API call has been triggered another time, the ref will store a different value, however the closure variable will hold the same previously set value and it mean that another API call has been triggered after this and so we don't need to accept the current result.

const timer = useRef(null);
useEffect(() => {
    fetchFromApi(props.match.params.id, timer);
}, [props.match.params.id]);

function fetchFromApi(id, timer) {
     timer.current = Date.now();
     const triggerTime = timer.current;
     fetch('path').then(() => {
         if(timer.current == triggerTime) {
              // process result here
              // accept response and update state
         }
     })

} 

Other ways to handle such scenarios to the cancel the previously pending API requests. IF you use Axios it provides you with cancelToken that you can use, and similarly you can cancel XMLHttpRequests too.

Upvotes: 1

Related Questions