Redux Toolkit - I can't update Slice state? - reactjs

I wanna update the state and tried few ways to do that, but I can't.
First, I got a problem with fetching state, as a result, I got proxy, not a state.
That is fixed by the current() function by the redux toolkit.
Next, where I have a problem is mutation main slice state.
Here is a reducer for mutating.
reducers: {
setUser: (state, payload) => {
console.log("before", current(state)); //init state
state.currentUser = {
...state.currentUser,
loggined: true,
};
console.log("after", current(state)); // here I have new state but...
},
clearUser: (state) => {},
},
As a result, as a second console log, I got to state what I want, when I want to change a page or just want to do something with new data in the state, by useSelector() redux function, as a result, I got old, not changed state.
Why has this happened?
Example of Slice state:
initialState: {
currentUser: {
loggined: false,
isAdmin: false,
jwt: false,
},
},
Thanks!

Reducers of createSlice use immer:
This object will be passed to createReducer, so the reducers may safely "mutate" the state they are given.
So you can either return a new object that is the new state or "mutate" it and not return it, from createReducer
you need to ensure that you either mutate the state argument or return a new state, but not both.
So you can do:
setUser: (state, payload) => {
//state here is an immer draft, do not use that to copy current state
console.log("before", current(state)); //init state
state.currentUser.loggined = true;
//not returning anyting
},
Not sure how you'd return a new state based on the old one since ...state makes a copy of the immer draft and not of the state. Can't even find examples of doing this unless it's an array.

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

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 to reset a redux slice state with Redux Toolkit (RTK)

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)
},

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