What is a proper way of defining side effects in action(not async thunks) when using redux toolkit?

Viewed 2535

Currently I'm digging into Redux Toolkit with Typescript and I'm stuck with a logout action. It basically shoudn't have any payload at all, but should modify localStorage and axios config. I'm aware of two ways that work slightly different.

One is to define reducer with a prepare callback inside of a createSlice:

const authSlice = createSlice({
  name: "authentication",
  initialState,
  reducers: {
    logout: {
      reducer: () => initialState,
      prepare: () => {
        api.logout();
        return { payload: undefined, error: undefined, meta: undefined };
      },
    },
  },...

In this case type signature of the prepare callback forces me to explicitly return a payload, error and meta properties.

Another way is to use a createAction with a prepare callback:

export const logout = createAction("authentication/logout", () => {
  api.logout();
  return { payload: undefined };
});

This is a bit better looking solution. At least I don't have to pass an error and meta. But still forced to return payload even though its not needed.

A bit confused why prepare function signatures differs, but thats not the point.

My question is how you would handle such cases. Maybe I'm doing something completelly wrong?

I understand that there is also option to use middleware, but setting up middleware that handles only one action looks like an overhead.

2 Answers

You are confusing terms a bit here - and missing one important term ;)

An action is an object with a type property. You can never execute code there.

An action creator is a function that returns an action. You could execute code here, but it will be executed before dispatching the resulting action.

An async thunk is a special form of thunk that attaches lifecycle actions (pending/fulfilled/rejected) to a thunk. To be exact, the createAsyncThunk function creates an async thunk action creator - a function that creates something you can dispatch for it to be triggered.

So, the thing that is missing: What is a thunk?

A thunk is a function that is dispatched - and then executed by the redux-thunk middleware. That function will be passed dispatch as first argument and getState as the second argument so you can trigger more redux-related stuff from in there if necessary.

So you are looking for a thunk. Commonly, those are written with an action creator, so you are looking for a thunk action creator.

const logout = (potentiallySomeArgument) => (dispatch, getState) => {
  api.logout(potentiallySomeArgument);
  // maybe you also want to dispatch an action to clear state
  dispatch(userSlice.clear())
}

This would be dispatchged like any other action, so the fuctionality is completely irrelevant to your component:

dispatch(logout("whatever"))

I'd willing to bet that api.logout is async function.

If it is async, you should use createAsyncThunk instead of createAction

Please see example:

import { createAsyncThunk } from '@reduxjs/toolkit'

const api = async () => { }

const increment = createAsyncThunk('counter/increment', api)

const action = increment() // AsyncThunkAction<void, void, {}>

Playground

Related