0
  1. They propose to use subscribe for vanilla Redux here:

Where to write to localStorage in a Redux app?

  1. Here for Redux Toolkit he propose to sort out local storage write operation in a separate thunk, and make dispatch from one reducer to the thunk.

How to subscribe to state outside React component in Redux Toolkit?

What do you think?

Right now I have this simple solution, one reducer, it saves to state and to localStore also, I do not face any issue yet. Shall I change?

    extraReducers: (builder) => {
        builder
            .addCase(me.fulfilled, (state, { payload }) => {
                state.authnRes = payload
                localStorage.setItem('authnRes', JSON.stringify(payload))
            })

Shall I make a separate thunk, and move the localStorage.setItem('authnRes', JSON.stringify(payload)) into there?

He says:

Reducer is never an appropriate place to do this because reducers should be pure and have no side effects.


Do you see any drawback in this approach? Thunk fires an other thunk with local store operation.

export const loginEmail = createAsyncThunk(
    `${namespace}/loginEmail`,
    async (req: any, { dispatch }) => {
        const { data } = await axios(req).then((res) => {
            dispatch(persistAuthnUser(res.data)) // <--- here
            return res
        })
        return data
    }
)

and here persist happens:

export const persistAuthnUser = createAsyncThunk(
    `${namespace}/persistAuthnUser`,
    async (data: any, { dispatch }) => {
        return Promise.resolve().then(function () {
            localStorage.setItem('authnRes', JSON.stringify(data))
        })
    }
)
János
  • 32,867
  • 38
  • 193
  • 353
  • Personally, I recently moved my persist-session logic from `subscribe` to my session reducer slice due to some problems (for example, needed to remove the storage manually on logout). Got a cleaner solution moving all the logic to the slice, now all persist-session logic is centralized. Working perfectly in all cases I tested. – Rashomon Jun 09 '22 at 10:18
  • Would you show an exaple how you did it? Did you create an other thunk, and reducer dispatch that thunk? – János Jun 09 '22 at 11:30
  • @Rashomon You're still removing from localStorage manually on logout in your snippet below. – timotgl Jun 09 '22 at 11:54
  • But now its centralized in a single place, the session reducer. Previously I had to remove persist-session storage on every `dispatch(logoutAction)` across the application. – Rashomon Jun 09 '22 at 11:56
  • This still holds true: "Reducer is never an appropriate place to do this because reducers should be pure and have no side effects." Why not do it in a middleware? it has access to all dispatched actions and you can call `store.getState()`. Writing to localStorage *sometimes* in *some* reducers is not great for separation of concerns. – timotgl Jun 09 '22 at 12:03
  • Yeah a middleware sound good. Honestly dont have created a redux middleware before (just using orhers). I will have a look. I think my solution is clean, but its true doesnt accomplish not having side effects. – Rashomon Jun 09 '22 at 12:16
  • @Rashomon, I do not get it why your solution is better than my, you call a method from resolver, but resolver is still 'overloaded', resolver can not return before `removePersistedSession` or `savePersistedSession` finished. Why don't you put local store operation in a thunk? – János Jun 09 '22 at 13:34
  • Added a new solution using a middleware instead of a reducer to avoid side effects. – Rashomon Jun 10 '22 at 09:17
  • I think it is way too complex how you do it. – János Jun 10 '22 at 11:49

1 Answers1

0

I managed to make it work implementing a middleware:

import {
  isAsyncThunkAction,
  isPending,
  isRejected,
  isFulfilled,
  isAnyOf,
  createListenerMiddleware,
} from '@reduxjs/toolkit'


const sessionListenerMiddleware = createListenerMiddleware()

sessionListenerMiddleware.startListening({
  matcher: isAnyOf(isAsyncThunkAction(me)), // execute middleware every time "me" is dispatched
  effect: (action, listenerApi) => {
    // listenerApi.getState() // You can also access state using this function

    const shouldSaveSession = isFulfilled(me) // save session every time async thunk "me" is fulfilled

    if (shouldSaveSession(action)) {
      // Middleware is executed after reducer, so we can assume state is already updated
      localStorage.setItem('authnRes', JSON.stringify(action.payload))
    }
  },
})

export const sessionMiddleware = sessionListenerMiddleware.middleware

Dont forget to add the middleware to your store:

export const store = configureStore({
  reducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .concat(otherMiddlewareYouMightHave)
      .concat(sessionMiddleware), // concat it here
})

Rashomon
  • 5,962
  • 4
  • 29
  • 67