Is there a way to recalculate the initial state for a slice. I am trying to reset the entire store doing:
const rootReducer = (state: any, action: any) => {
// Clear the state
if (action.type === clearState.type) {
// Clear all cached api requests
apiSlice.util.resetApiState();
// Return initial state
state = undefined;
}
return combinedReducer(state, action);
};
The problem is that my auth slice has the following initial state:
export const initialState = {
isLoggedIn: checkIfUserIsAlreadyLoggedIn(),
email: "",
status: "idle",
errorMessage: "",
};
So, when I start my application and checkIfUserIsAlreadyLoggedIn returns true, then when I reset the store, isLoggedIn is still true.
Is there any way I can recalculate the checkIfUserIsAlreadyLoggedIn function thus the initial auth slice state?
Thanks!
No. That would be a side effect within a reducer, which in Redux is strictly forbidden. A reducer should only depend on state and action, not on external data sources.
Also, apiSlice.util.resetApiState() will not do anything unless you dispatch it.
Maybe write it all as a thunk instead:
const resetAll = dispatch => {
dispatch(resetAction())
dispatch(apiSlice.util.resetApiState())
dispatch(setLoginState(checkIfUserIsAlreadyLoggedIn()))
}
Related
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
},
},
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.
I want to update a specific property in each object in redux state, and assume I have this kind of main state
networkFailure: true
employer: {
signUp: {
data: null
error: null
loading: true
}
}
user: {
signUp: {
data: null
error: null
loading: false
}
}
Assume I have faced to a network failure, then the networkFailure state becomes true , and with that I need to fail(false) loading signUp state of the employer and all loading status to the false value of other objects too
Is that possible to do this in one reducer? or any suggestions for do it in a better way?
I have setup 3 reducer functions for employer, user and network
There are two ways as I think:
1. Using the redux-thunk and dispatch an action and then dispatch 3 actions inside the action function which return dispatch function to you.something like this:
function setErrorAction() {
return dispatch => {
const networkError = {type: "network/singup/error"}
const employer Error = {type: "employer/singup/error"}
const userError = {type: "user/singup/error"}
dispatch(networkError)
dispatch(employer)
dispatch(userError)
}
}
Write a hook function that it has access to the dispatch function which exposed function to dispatch actions that when you call the hook function, it will dispatch actions to the reducers but it is a way to centralizing the business. something like this:
import {useDispatch} from "react-redux";
function useAuthError() {
const dispatch = useDispatch();
function setError() {
const networkError = {type: "network/singup/error"}
const employer Error = {type: "employer/singup/error"}
const userError = {type: "user/singup/error"}
dispatch(networkError)
dispatch(employer)
dispatch(userError)
};
return {
setError
}
}
One answer is found from here
Actually when a network failure happened, for me it is fine to reset the application state,
So I have reset the application state in the root reducer
const rootReducer = (state, action) => {
if (action.type === 'NETWORK_ERROR') {
state = undefined
}
return appReducer(state, action)
}
this will keep other reducer's state to its default one
So I have been following the tutorial to make redux state persist to the local storage.
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
However, what if I create my store like this with reducers:
const store = createStore(rootReducer, initialState)
And the root reducer is a combined reducers of:
const rootReducer = combineReducers({
auth: AuthStore,
content: contentReducer,
pages: pageReducer,
}
And each reducer has its own state/initialState, for example contentReducer is constructed as below:
const initialState = {selected, search}
const contentReducer = (state = initialState, action) => {
switch (action) {
case SELECT :
return {
...state,
selected: action.payload
case SEARCH:
return {
...state,
search: action.payload
}
}
}
If I follow the tutorial, then I can do
const saveSelected = (state) => {
try {
const serializedSelected = JSON.stringify(state);
localStorage.setItem('selected', serializedSelected)
} catch (err){
console.log(err)
}
}
store.subscribe(() => {
saveSelected(store.getState().content.selected)
})
However since I have many more reducers (I am only giving some examples above)and thus there are many actions dispatched to the store, I don't want the store to keep firing subscribe function everytime when any action is dispatched. I only want the store to subscribe to changes when the particular store.getState().content.selected is updated. How can I do that?
You can use a Redux middleware, which lets you register a function that's being called on each dispatch. You get a reference to the store object, so that you can check store.getState().content.selected state and act accordingly. See more details and examples here.
I'm trying to figure out how to set an initial state for a store in redux. I'm using https://github.com/reactjs/redux/blob/master/examples/todos-with-undo/reducers/index.js as an example. I tried to modify the code such that the todos had a value initialized.
const todoApp = combineReducers({
todos,
visibilityFilter
}, {
todos: [{id:123, text:'hello', completed: false}]
})
following the doc: http://redux.js.org/docs/api/createStore.html
but it's not working, and I'm not quite sure why.
It needs to be the second argument to createStore:
const rootReducer = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter
});
const initialState = {
todos: [{id:123, text:'hello', completed: false}]
};
const store = createStore(
rootReducer,
initialState
);
You can set the initial state in the reducer(s).
const initialTodos = [{id:123, text:'hello', completed: false}]
// this is the ES2015 syntax for setting a default value for state in the function parameters
function todoReducer(state = initialTodos, action) {
switch(action.type) {
...
}
return state
}
const todoApp = combineReducers({
// todos now defaults to the array of todos that you wanted and will be updated when you pass a new set of todos to the todoReducer
todos: todoReducer,
visibilityFilter
})
There have been great answers so far but let me ice the cake; perhaps to give you an in-depth analysis, so that you don't just copy StackOverflow codes``` that works but don't know why your program is working.
There are two main ways to accomplish this viz:
1. using the createStore method. It takes an optional second argument (the preloadedState value)
const store = createStore(counter) // createStore without preloadedState
const initialState = {} // or in your case:
const initialState = {
initialTodos = [{id:123, text:'hello', completed: false}]
}
const store = createStore(counter, initialState) // create store with preloadedState
if you call createStore without the preloadedState it would initialize the state to {}
Hence the reducers will receive undefined as their state values.
That brings us to the second method.
You can set it at the reducers. Reducers can also set initialState by looking at the incoming state argument (which would be undefined if createStore is not called with initialState) and returning the values they would like to use as default.
const initialState = {} // a common pattern but in your case:
const initialState = {
initialTodos = [{id:123, text:'hello', completed: false}]
}
function todoReducer(state = initialState, action) {
switch (action.type) {
case // your type:
return ...
default:
return state
}
}
The drawback of method 2 is evident in a case where there is huge data; like a huge todo list for instance that you want to pass as initialState "app-wide". Method 2 would bring in a lot of repetition as you would have to do the same thing across all your reducers. This is the main drawback. But it is quite popular when you just want to set initialState as {} it is a common pattern.
Here is a 4min read for better understanding: https://dev.to/lawrence_eagles/how-to-properly-set-initial-state-in-redux-78m
per #ctrlplusb answer, the reason this works is because
const rootReducer = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter
});
the first todos is a key which sets the second todos as a return value from the reducer. Reducers always run one time upon store creation. This initializes your global store.
There's an action dispatched when the store is created. That's how the initial state supplied in each combined reducer gets initialized in the store. If you check redux dev tools you'll see the first action dispatched is "##redux/INIT{something}"
In redux's documentation, near the end of the file, there is a dispatch({ type: ActionTypes.INIT })
See here https://github.com/reduxjs/redux/blob/master/src/createStore.js#L281-L284
See this question/answer I made on stackoverflow clarifying the response:
Different ways of initializing a react redux store's initial global state?