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?
Related
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()))
}
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 have a project where half of it was made with classes, and the other half is being made with hooks and Redux.
For this, I have created a store with configureStore() from Redux Toolkit and provided it using the Provider component. In a very minimal way, the store is set up as follows:
const userSlice = createSlice({
name: 'user',
initialState: {
user: {}
},
reducers: {
validate: (state, action) => state.user = action.payload
}
})
const store configureStore({
reducer: {
user: userSlice.reducer
}
})
There are two components - a new one, functional, which uses the useSelector() hook, and an older one, which is class based, but needs to use this sasme store to dispatch an action.
To do this, I import the store and fire
store.dispatch({type: 'user/validate', payload: newUser});
from the class component.
I receive no errors, yet nothing happens at all.
I tracked my input from DevTools' Redux plugin, and I can see the state does not change, so I assume my manual call to dispatch is somehow wrong.
What I expect to happen is for the state to update, which would trigger a re-render of the component that uses useSelector
The following way is a safe way to dispatch actions without misspelling the type string.
Extract the action from reducer
const userSlice = createSlice({
name: 'user',
initialState: {
user: {}
},
reducers: {
validate: (state, action) => {
state.user = action.payload
}
}
})
// <------------------
// Action creators are generated for each case reducer function
export const { validate } = userSlice.actions
export const store = configureStore({
reducer: {
user: userSlice.reducer
}
})
Dispatch the action as so
store.dispatch(validate(newUser))
Issue
I'm going to say the issue is that you are trying to both mutate your state object in the reducer function and return it.
See 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!
Solution
Just mutate the state, don't return it.
reducers: {
validate: (state, action) => {
state.user = action.payload;
},
}
If you want your class-based component to subscribe to your redux store then you can still use the connect Higher Order Component from react-redux.
Example:
import { connect } from 'react-redux';
import { validate } from '../path/to/userSlice';
class MyComponent extends Component {
...
// In a function you can simply dispatch the validate action
// as it was wrapped in a call to dispatch already and injected
// as a prop.
this.props.validate(somePayloadValue);
...
}
const mapDispatchToProps = {
validate
};
export default connect(null, mapDispatchToProps)(MyComponent);
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 am trying to wrap my head around redux and sagas and I think I have set something up wrong and i'm hoping someone can lend some insight.
I have created my store with my inital state and I dispatch an action, as seen here:
const initialState = fromJS({
product: {},
basket: {},
global: {}
});
const reducers = combineReducers({ product, basket, global });
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducers,
initialState,
applyMiddleware(sagaMiddleware))
initSagas(sagaMiddleware);
store.dispatch(retrieveSiteKeyValues())
return store;
};
Combine Reducers is from redux-immutable.
My saga function:
export function* fetchSiteKeyValuesSaga() {
yield take(RETRIEVE_SITE_KEY_VALUES)
const siteKeyValues = yield call(retrieveSiteKeyValues)
yield put(storeSiteKeyValues(siteKeyValues));
}
My reducer function:
const storeSiteKeyValues = (state, payload) => {
payload.anotherObject = {};
payload.anotherMap = new Map();
const newState = fromJS({ payload })
return newState
// OR return state.merge({ global: { siteKey: action.siteKey } }); ?
}
When I interrogate the state object the size is zero. I expected the size to be at least 3 due to my initalState. When the newState is 'created' the size is 4. But when it drops back into the state switch statement, the state size is zero again:
export default (state, action) => {
switch (action.type) {
case STORE_SITE_KEY_VALUES : {
return storeSiteKeyValues (state, action.payload);
}
default:
return state;
}
}
Im 90% sure just dumping over the state as I am doing in the reducer function is wrong and i should be using set() or setIn() i thought update() would make more sense, but when I use those methods the state is always empty or 'undefined' if I try to enact .get(x) in the console.
When I inspect the state in the browser it looks like this:
storeState:{
[2],
[2]
[2]
}
The array expanded looks like this:
0:"product"
1:{anotherObject :{}, anotherMap: map()
size:1
I would expect the values that were part of of the payload to be here not just the new object and map.
Am I initaiting my state incorrectly at the store creation? Am I approaching redux and state management in the wrong way?
I want to be sure you aren't missing a fundamental part: where is the sagaMiddleware.run(YOUR_SAGA); call? Is it hidden inside initSagas?
It was setting my inital state twice, once when I was initialsing my store and again when the reducer inital state was read. My state in my reducer was an empty object as it would be if on the time of reducer 'activation'. In the end I realised i'm not reading some 'remembered' state from anywhere, I just needed some inital values. Which I moved into the reducer and remvoed the immutable js from my app as it was confusing matters.
Some lessons for all you newbies to react/redux-saga! Don't try and over complicate matters. Learn what immutable mean! Figure out for yourself if you need it, in my case just having one source of truth and access to state was enough.
Further reading:
Initalising State ,
Immutable and State considerations