I'm using redux toolkit.
I have different slices and they have their own states.
I have a slice roles
import { createSlice } from '@reduxjs/toolkit';
import { getAllRoles } from './roleActions';
const initialState = {
roles: [],
loading: false,
isSuccess: false,
message: '',
};
const authReducer = createSlice({
name: 'roles',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getAllRoles.pending, (state) => {
state.isSuccess = false;
state.message = '';
state.loading = true;
})
.addCase(getAllRoles.fulfilled, (state, { payload }) => {
state.roles = payload.data;
state.message = payload.message;
state.isSuccess = true;
state.loading = false;
})
.addCase(getAllRoles.rejected, (state, { payload }) => {
state.isSuccess = false;
state.message = payload;
state.loading = false;
});
},
});
export default authReducer.reducer;
Here is the the getAllRoles action
import { createAsyncThunk } from '@reduxjs/toolkit';
import API from '../../api';
import { formatError } from '@utils/index';
export const getAllRoles = createAsyncThunk('role/getAllRoles', async (_, { rejectWithValue }) => {
try {
const { data } = await API.get('/roles');
return data;
} catch (error) {
return rejectWithValue(formatError(error));
}
});
I'm fetching all roles as:
useEffect(() => {
dispatch(getAllRoles());
}, [dispatch]);
Another slice:
import { createSlice } from '@reduxjs/toolkit';
import { deleteJob, getJobs, updateJob, createJob } from './jobActions';
const initialValues = {
data: {
jobs: [],
totalCount: 0,
},
loading: false,
isSuccess: false,
message: '',
};
const jobsReducer = createSlice({
name: 'jobs',
initialState: initialValues,
reducers: {},
extraReducers: (builder) => {
// Get Jobs
builder
.addCase(getJobs.fulfilled, (state, { payload }) => {
return {
...state,
data: payload.data,
message: payload.message,
isSuccess: true,
loading: false,
};
})
.addCase(deleteJob.fulfilled, (state, { payload }) => {
const updatedJobs = [...state.data.jobs.filter((job) => job._id !== payload)];
return {
...state,
data: {
totalCount: state.data.totalCount - 1,
jobs: updatedJobs,
},
message: 'Job deleted successfully',
isSuccess: true,
loading: false,
};
})
.addCase(updateJob.fulfilled, (state, { payload }) => {
const updatedJobs = [...state.data.jobs.filter((job) => job._id !== payload.data._id), payload.data];
return {
...state,
data: {
totalCount: state.data.totalCount,
jobs: updatedJobs,
},
message: payload.message,
isSuccess: true,
loading: false,
};
})
.addCase(createJob.fulfilled, (state, { payload }) => {
return {
...state,
data: {
totalCount: state.data.totalCount + 1,
jobs: [payload.data, ...state.data.jobs],
},
message: payload.message,
isSuccess: true,
loading: false,
};
})
.addMatcher(
(action) => action.type.endsWith('/pending'),
(state) => ({
...state,
isSuccess: false,
message: '',
loading: true,
}),
)
.addMatcher(
(action) => action.type.endsWith('/rejected'),
(state, { payload }) => ({
...state,
isSuccess: false,
message: payload,
loading: false,
}),
);
},
});
export default jobsReducer.reducer;
One more another slice:
import { createUser, deleteUser, getUsers, updateUser } from './userActions';
const initialState = {
users: [],
loading: false,
isSuccess: false,
message: '',
};
const usersReducer = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
// Get Users
builder.addCase(getUsers.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload.message;
state.users = payload.data;
state.isSuccess = true;
});
// Delete Users
builder.addCase(deleteUser.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload.message;
state.users = [...state.users.filter((user) => user._id !== payload)];
state.isSuccess = true;
});
// Update User
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload.message;
state.users.splice(
state.users.findIndex((user) => user.email === payload.data.email),
1,
payload.data,
);
state.isSuccess = true;
state.message = payload.message;
});
// Create User
builder.addCase(createUser.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload.message;
state.users.unshift(payload.data);
state.isSuccess = true;
});
// Pending State
builder.addMatcher(
(action) => action.type.endsWith('/pending'),
(state) => {
state.isSuccess = false;
state.message = '';
state.loading = true;
},
);
// Rejected State
builder.addMatcher(
(action) => action.type.endsWith('/rejected'),
(state, { payload }) => {
state.isSuccess = false;
state.message = payload;
state.loading = false;
},
);
},
});
export default usersReducer.reducer;
Now the issue is when getAllRoles triggeres it should change its loading state to true that it does correctly,
but it also chnages the loading state of users and jobs to true, only the loading state and it doesn't not set back to false.
and same like i have some other slices they are also behaving like this,
and when I dispatch the action of getJobs
now it has also different behaviour like it chnages the loading state of users to true but not of roles or any other.
Here is redux devtools screenshot:
I have selected roles/getAllRoles/pending action and check the state difference it is changing the loading state of othere slices also which it shouln't.
Here is my store configuration:
import storage from 'redux-persist/lib/storage';
import { combineReducers } from 'redux';
import { persistReducer } from 'redux-persist';
import thunk from 'redux-thunk';
import { authReducer, usersReducer, rolesReducer, jobsReducer, surveysReducer, pageInfoReducer } from './features';
const reducers = combineReducers({
auth: authReducer,
users: usersReducer,
roles: rolesReducer,
jobs: jobsReducer,
survey: surveysReducer,
pageInfo: pageInfoReducer,
});
const store = configureStore({
reducer: reducers,
devTools: process.env.NODE_ENV !== 'production',
middleware: [thunk],
});
export default store;
Tried removing addMatcher, using unique names for actions and slices, tried using builder.addCase seprately instead of in chaning way. Nothing works.
Both the jobs and users state slices have reducers cases defined that handle any pending or rejected action.
const jobsReducer = createSlice({
name: 'jobs',
initialState: initialValues,
reducers: {},
extraReducers: (builder) => {
// Get Jobs
builder
...
.addMatcher(
(action) => action.type.endsWith('/pending'), // <-- any pending
(state) => ({
...state,
isSuccess: false,
message: '',
loading: true,
}),
)
.addMatcher(
(action) => action.type.endsWith('/rejected'), // <-- any rejected
(state, { payload }) => ({
...state,
isSuccess: false,
message: payload,
loading: false,
}),
);
},
});
const usersReducer = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
...
// Pending State
builder.addMatcher(
(action) => action.type.endsWith('/pending'), // <-- any pending
(state) => {
state.isSuccess = false;
state.message = '';
state.loading = true;
},
);
// Rejected State
builder.addMatcher(
(action) => action.type.endsWith('/rejected'), // <-- any rejected
(state, { payload }) => {
state.isSuccess = false;
state.message = payload;
state.loading = false;
},
);
},
});
When 'role/getAllRoles/pending'
is dispatched to the store, these other reducer cases will, with the current code, correctly update and set their local loading
state (as well as the state updates in their cases). If the asynchronous getAllRoles
action completes successfully, there isn't a case for this in these other state slices to clear their loading states.
Recall that all reducers in the reducer tree of the Redux store are passed each action that is dispatched to the store, and only reducers with cases for specific actions respond.
I suspect you are interested in only the pending/rejected status of the deleteJob
, getJobs
, updateJob
, and createJob
actions in the case of the jobs slice, and only the pending/rejected status of the createUser
, deleteUser
, getUsers
, and updateUser
in the case of the users slice. For this I suggest using the isPending
, isFulfilled
, and isRejected
matching utilities.
Example:
import { createSlice, isPending, isFulfilled, isRejected } from '@reduxjs/toolkit';
import { deleteJob, getJobs, updateJob, createJob } from './jobActions';
const initialState = {
data: {
jobs: [],
totalCount: 0,
},
loading: false,
isSuccess: false,
message: '',
};
const jobsReducer = createSlice({
name: 'jobs',
initialState,
extraReducers: (builder) => {
// Get Jobs
builder
.addCase(getJobs.fulfilled, (state, { payload }) => {
return {
...state,
data: payload.data,
message: payload.message,
};
})
.addCase(deleteJob.fulfilled, (state, { payload }) => {
const updatedJobs = state.data.jobs.filter((job) => job._id !== payload);
return {
...state,
data: {
totalCount: state.data.totalCount - 1,
jobs: updatedJobs,
},
message: 'Job deleted successfully',
};
})
.addCase(updateJob.fulfilled, (state, { payload }) => {
return {
...state,
data: {
totalCount: state.data.totalCount,
jobs: state.data.jobs
.filter((job) => job._id !== payload.data._id)
.concat(payload.data),
},
message: payload.message,
};
})
.addCase(createJob.fulfilled, (state, { payload }) => {
return {
...state,
data: {
totalCount: state.data.totalCount + 1,
jobs: [payload.data, ...state.data.jobs],
},
message: payload.message,
};
})
.addMatcher(
isPending(deleteJob, getJobs, updateJob, createJob),
(state) => {
state.isSuccess: false,
state.message: '',
state.loading: true,
},
)
.addMatcher(
isFulfilled(deleteJob, getJobs, updateJob, createJob),
(state) => {
state.isSuccess: true,
state.loading: false,
},
)
.addMatcher(
isRejected(deleteJob, getJobs, updateJob, createJob),
(state, { payload }) => {
state.isSuccess: false,
state.message: payload,
state.loading: false,
},
);
},
});
import { createSlice, isPending, isFulfilled, isRejected } from '@reduxjs/toolkit';
import { createUser, deleteUser, getUsers, updateUser } from './userActions';
const initialState = {
users: [],
loading: false,
isSuccess: false,
message: '',
};
const usersReducer = createSlice({
name: 'users',
initialState,
extraReducers: (builder) => {
// Get Users
builder.addCase(getUsers.fulfilled, (state, { payload }) => {
state.message = payload.message;
state.users = payload.data;
});
// Delete Users
builder.addCase(deleteUser.fulfilled, (state, { payload }) => {
state.message = payload.message;
state.users = [...state.users.filter((user) => user._id !== payload)];
});
// Update User
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
state.message = payload.message;
state.users.splice(
state.users.findIndex((user) => user.email === payload.data.email),
1,
payload.data,
);
});
// Create User
builder.addCase(createUser.fulfilled, (state, { payload }) => {
state.message = payload.message;
state.users.unshift(payload.data);
});
// Pending State
builder.addMatcher(
isPending(createUser, deleteUser, getUsers, updateUser),
(state) => {
state.isSuccess = false;
state.message = '';
state.loading = true;
},
);
// Fulfilled State
builder.addMatcher(
isFulfilled(createUser, deleteUser, getUsers, updateUser),
(state) => {
state.isSuccess = true;
state.message = '';
state.loading = false;
},
);
// Rejected State
builder.addMatcher(
isRejected(createUser, deleteUser, getUsers, updateUser),
(state, { payload }) => {
state.isSuccess = false;
state.message = payload;
state.loading = false;
},
);
},
});