Search code examples
reactjsreduxredux-sagaredux-thunkredux-toolkit

createAsyncThunk: dispatch more actions after updating store with fetched data, within a same payload creator


I just started using Redux Toolkit(RTK), and I'm having a hard time figuring out a proper way of using createAsyncThunk from RTK.

So what I'm trying to do is to

  1. dispatch an asynchronous action created by createAsyncThunk
  2. update store with fetched data,
  3. and dispatch another synchronous action within a same payload creator.

 
For example, you could write such code with redux-saga like below.

function* fetchBooksSaga() {
  try {
    yield put(fetchBooksStart()); // set loading
    const keyword = yield select(state => state.keyword); // get keyword from store
    const { data } = yield call(SearchBooksAPI, query); // fetch books
    yield put(fetchBooksSuccess(data)); // set books
    
    // Here, you can dispatch another action with newly updated state.
    const books = yield select(state => state.books);

    // I know you can just use 'data' variable here,
    // but let's say you have to update store first.
    yield put(anotherAction(books)); 
  } catch (error) {
    yield put(fetchBooksFailure(error)); // set error
  }
}

 

I was trying to write same code with createAsyncThunk, and the result looks like below.

export const fetchBooks = createAsyncThunk(
  'BOOKS/FETCH_BOOKS',
  async (params, ThunkAPI) => {
    try {
      // when 'fetchBooks' is dispatched, fetchBooks.pending will be dispatched automatically.
      const { keyword } = ThunkAPI.getState(); // get keyword from store.
      const { data } = await SearchBooksAPI(query); // fetch books
      ThunkAPI.dispatch(fetchBooks.fulfilled(data)); // set books
      
      // store updated at this moment

      const { books } = ThunkAPI.getState();
      ThunkAPI.dispatch(anotherAction(books));
      
      // NOPE - returning value here will dispatch fetchBooks.fulfilled again.
      return data;
    } catch (error) {
      ThunkAPI.rejectWithValue(error); // dispatch fetchBooks.rejected
    }
  }
);

Like its name, payload creator should create a payload. So it's obvious that I should return something within a payload creator. But in this case, returning value will dispatch asyncAction.fulfilled again, with returned value wrapped by Promise as payload.

I could just use a normal thunk action or saga, but the reason I'm trying this way is to minimize boilerplate. Using a normal thunk action will require to create action creators for pending/success/failure, which is not necessary when using createAsyncThunk.

Does anyone have idea that could help me solve this issue? any comments will be appreciated. Thank you for reading.


Solution

  • createAsyncThunk isn't quite meant for that use case. It's specifically meant to handle the "fetch data and dispatch result" use case, not an ongoing sequence of dispatches.

    You do have access to thunkAPI.dispatch inside of the payload creation callback, so you could dispatch additional actions before the fulfilled action is dispatched. But, the fulfilled action won't get dispatched until the returned promise resolves, so it doesn't give you the ability to dispatch more actions after that is done.

    The closest thing I can think of at the moment would be something like this:

    export const fetchBooks = createAsyncThunk(
      'BOOKS/FETCH_BOOKS',
      async (params, ThunkAPI) => {
        try {
          // when 'fetchBooks' is dispatched, fetchBooks.pending will be dispatched automatically.
          const { keyword } = ThunkAPI.getState(); // get keyword from store.
          const { data } = await SearchBooksAPI(query); // fetch books
          
          const resultPromise = Promise.resolve(data);
          
          resultPromise.then(() => {
            const { books } = ThunkAPI.getState();
            ThunkAPI.dispatch(anotherAction(books));
          })
          
          return resultPromise;
        } catch (error) {
          ThunkAPI.rejectWithValue(error); // dispatch fetchBooks.rejected
        }
      }
    );
    

    but I'm not sure the timing on that will be entirely correct with when that promise resolves and when the fulfilled action gets dispatched.

    I think your best option here would be to break this into two thunks: one that does just the normal fetch+dispatch for the original data, and a second thunk that dispatches the first one and then does the additional logic.