简体   繁体   中英

How to avoid letting an async thunk that is no longer useful (the app is not expecting it anymore) from being able to update the state?

I have the following pattern on my single page app (React + Redux).

It runs every time a load a page on the app. User navigates to a specific page, and the loadPageThunk is dispatched. The initial state of the page shows a spinner to the user. This is used for example, in a blogpost page.

That thunk will get some async data (the blogpost), and then will show the page with that data.

It works fine. When the user navigates away from the page. A useEffect dispatches a RESET action to reset the state back to its initial value.

My question is:

What if the async call takes too long to complete and the user navigates away? It will create a problem because now there's a pending promise that will complete in an unexpected time. How can I prevent that completion from updating my state?

Imagine the following steps for an async call that is taking 10 seconds to complete:

#### FIRST PAGE LOAD ####

USER VISITS blog/slug-1
loadPageThunk() IS DISPATCHED
blogPost1 STARTS GETTING FETCHED (WILL TAKE 10 SECONDS)
USER NAVIGATES AWAY

#### SECOND PAGE LOAD ####

USER VISITS blog/slug-2
blogPost2 STARTS GETTING FETCHED (WILL TAKE 10 SECONDS)
USER IS STILL SEEING SPINNER
blogPost1 (FROM THE PREVIOUS VISIT) HAS COMPLETE AND WILL UPDATE THE STATE
USER NOW SEES blog/slug-2 WITH THE DATA FROM blogPost1 WHICH IS AN ERROR
blogPost2 WILL EVENTUALLY COMPLETE AND USER WILL SEE A CONTENT FLICKER ON THE PAGE

QUESTION

How can I avoid pending promises that are no longer useful from being able to update the state?

This problem is not currently happening in my app, but I think that a good design should account for that.

Should I add an ID for my LOAD_PAGE cycle, so I can check the ID of the current cycle before allowing callbacks / async code from updating the state when IDs don't match? How do people usually handle this?

Personally I store blog data as entities (posts, comments, etc.) keyed by id and collections . The collection is just the array of post ids on a particular page.

For example,

{
  entities: {
     posts: {
       1: {...},
       2: {...}
     },
     comments: {
        123: {...},
        999: {...}
     }
  },
  collections: {
     "blog/slug-1": [99,98,97...],
     "blog/slug-2": [89,88,87...],
  }
}

This sort of structure means that every page can save its data in the correct place regardless of whether it is the current page or not. It also means that every page can select its own data and can see whether that data already exists in the state.

The promise returned by createAsyncThunk has an abort() method attached which can be used to 'cancel' the promise. See canceling while running . You can call abort() in your cleanup to prevent the thunk from being fulfilled .

In your reducers, if you are handling the rejected case for your thunk, then you can add an exception for cases where the error name is AbortError to do nothing instead.

To expand a bit about your specific situation: a good rule of thumb is that if you find yourself 'resetting' state when you unmount the component, then it should have just been local component state in the first place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM