Search code examples
javascriptreactjsreduxredux-toolkit

Redux Toolkit Thunk difference


I was searching about thunks on Redux-toolkit and I found 2 ways of dealing with them:

createAsyncThunk or just using async + dispatch

Using createAsyncThunk:

export const deleteColumnAndTasks = createAsyncThunk(
  'yourSlice/deleteColumnAndTasks',
  async ({ boardId, column }) => {
    const tasksToDelete = column.taskIds || [];
    await changeBoardColumns({
      operationType: 'delete',
      targetBoardId: boardId,
      columnId: column.id
    });
    await deleteColumn({ columnId: column.id });
    await Promise.all(tasksToDelete.map(taskId => deleteTask({ taskId })));
  }
);

Using async + dispatch

export const deleteColumnAndTasks = ({ boardId, column }) => async dispatch => {
  const tasksToDelete = column.taskIds || [];
  try {
    await dispatch(changeBoardColumns({
      operationType: 'delete',
      targetBoardId: boardId,
      columnId: column.id
    }));
    await dispatch(deleteColumn({ columnId: column.id }));
    await Promise.all(tasksToDelete.map(taskId => dispatch(deleteTask({ taskId }))));
  } catch (error) {
    console.log('Error deleting column and tasks', error);
  }
};

What is the difference between the two ways?


Solution

  • They both are designed to dispatch actions to change the state of the store. They both will return a Promise object as written. That is where the similarities end.

    createAsyncThunk() is a factory that registers and returns a function (action creator) that will be called in the future and that is designed to return a Promise object. The docs state:

    It generates promise lifecycle action types based on the action type prefix that you pass in, and returns a thunk action creator that will run the promise callback and dispatch the lifecycle actions based on the returned promise.

    Also Life Cycle Actions from the docs

    createAsyncThunk will generate three Redux action creators using createAction: pending, fulfilled, and rejected. Each lifecycle action creator will be attached to the returned thunk action creator so that your reducer logic can reference the action types and respond to the actions when dispatched. Each action object will contain the current unique requestId and arg values under action.meta.

    So if it is an important design choice to keep track/monitor/subscribe to the async actions you are dispatching, createAsyncThunk() provides you the means to do all of that with no extra effort on your own.

    For example, in a createSlice() call one can do something like this:

    extraReducers: builder =>
      builder
        .addCase(deleteColumnAndTasks.pending, (state, action) => {
          // do pending action
        })
        .addCase(deleteColumnAndTasks.fulfilled, (state, action) => {
            state.value = [action.payload, ...state.value];
        })
        // etc.
    )
    

    One other important capability that createAsyncThunk() provides is the ability to easily cancel the async action based upon a user defined condition.

    =====

    The second example is simply a synchronously executed (at call time) function that uses currying to pass in boardId, column and a reference the to dispatch function. All of the tooling provided automagically by createAsyncThunk() does not exist. Those features, if required will need to be written separately.