reactjsreduxreact-reduxreducersredux-toolkit

How can I change state of another reducer from extra reducers add cases


I am trying to create a notification Component. My notification Component is at root level and when ever user tries to login the process of the the async function is relayed to him i.e. pending fulfilled or rejected.

The Problem is that I don't know how to call notification reducer from userSlice or even if its possible is this a good way or not.

User Slice

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const initialUserState = { 
    currentUser:null 
}

export const getUser = createAsyncThunk(
    'user/getUser',
    async (endpoint, data) => {
        return(
            await axios.post(endpoint, data)
            .then(res =>{
                return res.data.user
            })
            .catch(error =>{
                throw Error(error.response.data)
            })
        )
    }
)

const userSlice = createSlice({
    name: 'user',
    initialState: initialUserState,
    reducers:{
        currentUser(state, action){
            state.currentUser = action.payload
        }
    },
    extraReducers: 
        (builder) => {
            builder.addCase(getUser.pending, ()=>{
                console.log("authing")
            })
            builder.addCase(getUser.fulfilled, (state, action)=>{
                state.currentUser = action.payload
                console.log("fulfilled")
            })
            builder.addCase(getUser.rejected, (state, action)=>{
                console.log("failed")
                alert(action.error.message)
            })
        }
})

export const userActions = userSlice.actions;
export default userSlice.reducer;

notificationSlice

import React from 'react'
import { useSelector } from 'react-redux'

function Notification() {

    const toast = useSelector(state => state.notification)
    console.log(toast)
    return (
        toast.active &&
        <div className="notification" style={{backgroundColor:toast.backgroundColor}} >
            {toast.message}
        </div>
    )
}

export default Notification

I want to change notification state when ever one of the extra reducer in userSlice is called


Solution

  • I think you are thinking about this almost exactly backwards. What you want is NOT to "call notification reducer from userSlice," but to LISTEN for userSlice actions in a notificationSlice.

    I have done something like the following, which I think would work well for you:

    import { createEntityAdapter, createSlice, isAnyOf } from '@reduxjs/toolkit'
    
    const notificationsAdapter = createEntityAdapter()
    
    const initialState = notificationsAdapter.getInitialState({
      error: null,
      success: null,
    })
    
    const notificationsSlice = createSlice({
      name: 'notifications',
      initialState,
      reducers: {
        clearNotifications: state => {
          state.error = null
          state.success = null
        },
        setError: (state, action) => {
          state.success = null
          state.error = action.payload
        },
        setSuccess: (state, action) => {
          state.success = action.payload
          state.error = null
        },
      },
      extraReducers: builder => {
        builder
          .addMatcher(
            isAnyOf(
              getUser.fulfilled,
            ),
            (state, action) => {
              state.error = null
              state.success = action.payload.message
            }
          )
          .addMatcher(
            isAnyOf(
              getUser.rejected
              // can add as many imported actions
              // as you like to these
            ),
            (state, action) => {
              state.error = action?.payload
              state.success = null
            }
          )
          // reset all messages on pending
          .addMatcher(
            isAnyOf(
              getUser.pending
            ),
            (state, action) => {
              state.error = null
              state.success = null
            }
          )
      },
    })
    export const { clearNotifications, setError, setSuccess } = notificationsSlice.actions
    
    export default notificationsSlice.reducer
    
    export const getErrorMsg = state => state.notifications.error
    export const getSuccessMsg = state => state.notifications.success
    
    

    Having added the above, you can now create a notification component that listens for

      const error = useSelector(getErrorMsg)
      const success = useSelector(getSuccessMsg)
    

    and shows the messages accordingly.

    Caveat: