How to reset a redux slice state with Redux Toolkit (RTK) - reactjs

I am trying to reset state via a redux action called resetState, where I assign the state to the initialState variable, however this does not work.
const initialState = {
someArray: [],
someEvents: {}
}
const stateSlice = createSlice({
name: "someState",
initialState,
reducers: {
...someActions,
resetState: (state, action) => {
// THIS DOES NOT WORK
state = initialState
},
resetState2: (state, action) => {
// THIS WORKS!!!!
return initialState
},
});
When I return the initialState, the state resets properly, but assigning it to initialState does not.

Assigning state = anything is not correct, regardless of whether you're using Redux Toolkit or writing the reducers by hand. All that does is point the local state variable to a different reference.
RTK's createSlice uses Immer inside. Immer primarily works by tracking mutations to a wrapped value, such as state.someField = 123. Immer also allows you to return an entirely new state you've constructed yourself.
So, in this case, what you want is return initialState to return a new value and force Immer to replace the old one.

resetState: (state, action) => {
Object.assign(state, action.payload)
},

Related

Any tips for mutating the state using redux-toolkit

I'm using redux-toolkit and React.
I know basic redux (not using redux-toolkit) aren't allowed to mutate the original state. That's why I chose redux-toolkit to be able to do it. However,I can't understand what's a forbidden way to mutate the state using redux-toolkit. I read an offical document below but it didn't work to me.
https://redux-toolkit.js.org/usage/immer-reducers
Especially Immer Usage Patterns in the document.What's the difference between Mutating and Returning state and Resetting and Replacing state? It looks like similar. Plus, I can't figure out Updating Nested Data in the document.
could you tell me any tips for using redux-toolkit?
The intro of the page you've linked to effectively covers a bit of history for how Redux reducer functions worked in that past. Redux state is considered to be immutable, so if you wanted to update any part of a slice's state you necessarily needed to shallow copy all state and nested state that you wanted to update.
See Reducers and Immutable State:
function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
Redux-Toolkit comes with immer baked right in, so we can write mutable reducer functions and know we are only editing a draft copy of the state that will get applied in an immutable way.
You are interested in the Immer Usage Patterns, specifically Mutating and Returning State.
In any given case reducer, Immer expects that you will either mutate
the existing state, or construct a new state value yourself and return
it, but not both in the same function! For example, both of these are
valid reducers with Immer:
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
todoAdded(state, action) {
// "Mutate" the existing state, no return value needed
state.push(action.payload)
},
todoDeleted(state, action.payload) {
// Construct a new result array immutably and return it
return state.filter(todo => todo.id !== action.payload)
}
}
})
You can either mutate a part of the state and the change will be correctly applied or you can return an entirely new state object with as much or as little of it updated as you need. The only thing that is forbidden is to mutate part of the existing state and return a new state object reference.
Forbidden update:
const todosSlice = createSlice({
name: 'todos',
initialState: {todos: [], status: 'idle'}
reducers: {
todoDeleted(state, action.payload) {
// Construct a new array immutably
const newTodos = state.todos.filter(todo => todo.id !== action.payload);
// "Mutate" the existing state to save the new array
state.todos = newTodos;
return {
...state,
status: 'complete',
};
}
}
});
Acceptable update:
const todosSlice = createSlice({
name: 'todos',
initialState: {todos: [], status: 'idle'}
reducers: {
todoDeleted(state, action.payload) {
// Construct a new array immutably
const newTodos = state.todos.filter(todo => todo.id !== action.payload);
// "Mutate" the existing state to save the new array
state.todos = newTodos;
state.status = 'complete';
}
}
});

Unable to Update State ReduxToolkit

I have a array of objects kept in my state, I want to be able to edit one of the objects in the array and update the state.
However, I cannot seem to update anything with the state except push more items into it.
I am using #reduxjs/toolkit and the createSlice() method for my reducers.
Here is my slice, it has some logic to pull the initial state array from an API.
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import { BACKEND_API } from "../../utilities/environment";
import fetchViaApi from "../../utilities/fetchViaApi";
export const getInitialDashboards = createAsyncThunk(
'dashboard/getDashboards',
async () => {
const response = await fetchViaApi('/dashboards', {
baseUrl: BACKEND_API,
method: "GET"
});
const data = await response.json();
return data;
}
)
const initialState = [];
const dashboardsSlice = createSlice({
name: 'dashboards',
initialState,
reducers: {
setDashboards: (state,action) => {
state = action.payload;
},
updateDashboard: (state,action) => {
// state.push(action.payload);
state = [...state.slice(0, 5)];
},
deleteDashboard: (state, action) => {
},
},
extraReducers: builder => {
builder.addCase(getInitialDashboards.fulfilled, (state, action) => {
action.payload.forEach(element => {
state.push(element);
});
})
}
});
export const { setDashboards, updateDashboard, editDashboard, deleteDashboard } = dashboardsSlice.actions;
export default dashboardsSlice.reducer;
The commented out state.push(action.payload) works fine, but sometimes I don't want to add new object to the array, but edit existing ones.
My thought was to slice the existing element out and add the new version back to the array. But I cannot slice the state.
I am using Redux DevTools in Chrome and watching the state not change after calling updateDashboard, there were 10 elements after getDashboards is completed.
You had the right idea, but your reducers need to be returning the new state, not assigning it.. e.g.
reducers: {
setDashboards: (state,action) => {
return action.payload;
},
updateDashboard: (state,action) => {
return [...state.slice(0, 5)];
},
deleteDashboard: (state, action) => {
return [];
},
},
The issue is that state = anything is not a valid way to update data with Immer. It's not mutating the existing state, and it's not returning a new value - it just points the local state variable to something else, so Immer has no way to know that anything changed.
If you want to replace the existing state entirely, do return newStateValue. If you want to update part of the state, then mutate a nested field or value.
See the Writing Reducers with Immer page in the RTK docs for more details.
I faced a similar problem today. Updating or assigning values to the state directly is not working. But updating the properties inside the state variable works
I would add a property named dashboards to the state and update it instead of updating the state directly in reducer
Redux toolkit is using immer under the hood. It might be helpful to take a look at immer and get an idea to mutate the state

How to call other reducers in createSlice to change state?

I use redux to createSlice, here is my createSlice looks like:
export default createSlice({
name:'testSlice',
initialState,
reducers:{
setRecords: setRecordsReducer,
setObjects: setObjectsReducer,
setBoth:....
}
});
Here is setRecordsReducer and setObjectsReducer look like:
const setRecordsReducer = (state, {payload:{id,recordName}}) => {
return {...state, items:{...state.items, records:{...state.items.records, [id]:{recordName}};
};
const setObjectsReducer = (state, {payload:{id,objectName}}) => {
return {...state, objects:{...state.objects, records:{...state.objects.records, [id]:{objectName}};
};
The two reducer setRecordReducer and setObjectsReducer change different parts of the state (actually not change the state, but build new state based on it). setRecordReducer returns new state based on state.items.records; setObjectReducer returns new state based on state.objects.records.
I tried to create a new action setBoth, can I call both setRecordReducer and setObjectsReducer when action setBoth happens? So the two parts of the state can be changed and return me the new state?
Since the state each is operating over then yes, I see no reason why you couldn't write a reducer function that called the others and passed the appropriate arguments. This new reducer function needs to also return the next computed state value, returned the combined updated state from each reducer function called.
const setBothReducer = (state, action) => {
return {
...state,
...setRecordsReducer(state, action),
...setObjectsReducer(state, action),
};
}
export default createSlice({
name:'testSlice',
initialState,
reducers:{
setRecords: setRecordsReducer,
setObjects: setObjectsReducer,
setBoth: setBothReducer,
}
});

Why can't I update the state using Redux?

I'm trying to integrate Redux in a project that works already. I've configured the Redux store using multiple slices, here's the one causing troubles:
const initialCategoriesState = [];
const categoriesSlice = createSlice({
name: "categories",
initialState: initialCategoriesState,
reducers: {
setCategories(state, action) {
state = action.payload;
},
},
});
In my component I'm using useSelector to access the state:
const categories = useSelector(state => state.categories);
And to update it I dispatch an action, accordingly with the one declared in the slice:
const fetchedCategories = await fetchData(urlCategories, CATEGORIES);
dispatch(categoriesActions.setCategories(fetchedCategories));
But once I run the code, the categories constant gets never updated. Since I wasn't sure the action was getting the data, I tried to console.log the state inside the reducer it as it follows:
reducers: {
setCategories(state, action) {
console.log("state before", state);
state = action.payload;
console.log("state after", state);
},
},
The state is indeed changing, but not the extracted state in the categories constant.
Is there something I'm missing here?
I've got the same problem a few months ago and solved it this way. But, I may be missing something too.
Please try this for your initial state:
const initialCategoriesState = { categories: [] };
And then in your reducer:
state.categories = action.payload;
I've found one of the cool things about Redux Toolkit to be it's Entity Adapters, which can safely initialize your state while providing helper functions for updating your state. They (reasonably) assume that your slice will have collections, and probably a main collection of things with the same name as the slice. createEntityAdapter() allows you to do like:
const categoriesAdapter = createEntityAdapter()
// name the below "initialState" exactly
const initialState = categoriesAdapter.getInitialState({
// this will, by default, get you an `entities{}` and `ids[]` representing your categories
// you can add any additional properties you want in state here as well
})
Then, when you are wanting to update state, in your reducer you can do like:
reducers: {
setCategories(state, action) {
categoriesReducer.setAll(state, action.payload)
// this will update both entities{} and ids[] appropriately
},
},

How can you replace entire state in Redux Toolkit reducer?

EDIT: The solution is to return state after I replace it completely (return state = {...action.payload})! But why? I don't need to return it when I replace the fields individually.
I'm working with the Redux Toolkit, which simplifies some Redux boilerplate. One thing they do is use Immer to allow you to directly 'modify' state (in fact, you're not). It works fine, except I don't know how to replace my section of state entirely. For instance, I want to do something like this
const reducer = createReducer({ s: '', blip: [] }, {
[postsBogus.type]: (state, action) => {
state = { ...action.payload };
}
but state remains untouched. Instead I have to do this
[postsBogus.type]: (state, action) => {
state.s = action.payload.s;
state.blip = action.payload.blip;
}
Is there a way I can replace state entirely?
Yes, as you noted, you must return a new value to replace the state entirely.
Even in a "plain" Redux reducer, assigning state = newValue does nothing, because all that does is say that the local function variable named state is now pointing to a different value in memory. That does nothing to return a new value.
For Immer specifically, you can either mutate the contents of the Proxy-wrapped state value as long as it's an object or array, or you can return an entirely new value, but not both at once.
You can, but not in this way, when you say:
function x(y){
y = 4
}
You're mutating the function parameter, but not the state of redux,
you have two options to update this state of your redux store:
either to set state.your_state_name = something or, in your case, what you want to do is to return a new object, the new object is what the new state value is going to be.
simple example:
myReducerFunc: (state, action) => {
return {...action.payload }
},
another example:
const loggedInUserSlice = createSlice({
name: '$loggedInUser',
initialState: {
isLoggedIn: false,
},
reducers: {
loggedIn: (state, action) => {
return {
isLoggedIn: true,
...action.payload,
}
},
},
})

Resources