I have an application with Reactjs and Redux. There is an action which resets the state of the reducers. My question is: where is the best place to perform that? I am considering two options:
each reducer handle the action and resets its state
const reducer1 = (state = defaultState, action) => {
switch (action.type) {
case 'reset': {
// ...
}
// ...
}
the root reducer resets the global state
const appReducer = combineReducers({
reducer1,
reducer2,
reducer3
})
const rootReducer = (state, action) => {
if ( (action.type === 'reset') ) {
state = {}
}
return appReducer(state, action)
}
The best practice is to have an action for reseting each reducer, this helps for extensibility in the future. Dispatch the clearState action, but do not set an empty object. Set it to the initial state, because if you put an empty object you can introduce bugs
The first thing we must have clear is that reducers don't have states, stores have states, so you shouldn't say "the state of the reducer".
A reducer is a function that performs some change in the state of a store, and there is no specific limitation in the scope of such change, besides the scope of the store, so many reducers have overlapping scopes over the state of the store.
After that, I see no reason why you can not reset the whole state of the store with a single reducer, and when you need to make other changes with different scope, you can create other reducers to manage it.
Related
I have a simple counter slice. In increment function I want to access root state. How can I do that?
const initialState = {
value: 1
}
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value++
}
}
})
Generally, you can't. Redux (and not only toolkit) is designed around the idea that your Reducer should only rely on it's own state value and the contents of the action coming in, nothing else.
If you really really need data from another state slice, you probably need to copy that into the action when calling it, but it is usually considered a bad idea.
You might have chosen your slice too small.
to make things simple consider these two reducers(written in typescript):
export type userActionTypes =
| Interface1
| Interface2
const initialState1 = {...//some state} //<--comment indicates some sort of values are present
const initialState2 = {...//some state}
const reducer1 = (state = initialState1, action: userActionTypes){
switch(action.type) {
case action1.case1:
return {...//some new state1}
default: return state
}
}
const reducer2 = (state = initialState2, action: userActionTypes){
switch(action.type){
case action2.case2:
return {...//some new state1}
default: return state
}
}
const rootReducer = combineReducers({
mReducer1: reducer1,
mReducer2: reducer2
})
now say somewhere we call the following in our code:
newAction = {type: action2.type2, //some other values}
dispatch(newAction);
**my question is: ** How does react know which reducer to call? I mean, it doesn't pass the action to every reducer there is in the rootReducer, does it? I mean if that was the case, then all the default cases would be meaningless and all the cases in the case statements would have to be unique. That is not possible, is it?
How does react know which reducer to call?
How does react know which reducer to call? I mean, it doesn't pass the
action to every reducer there is in the rootReducer, does it?
Trick question, redux actually calls all of your reducers.
Consider your root reducer:
const rootReducer = combineReducers({
mReducer1: reducer1,
mReducer2: reducer2
})
This creates a reducer function tree. When an action is dispatched to the store it calls the root reducer and passes the current state and the current action. The reducers in turn recursively call their nested combined reducers, passing state and action until they reach a leaf where you hit a reducer function and compute their next state. They either have a case to handle action or the return the default case which is simply their current state. The recursion goes back up, returning each next state slice piece, combined at each level until you arrive back at the root reducer which returns the entire next state object.
I mean if that was the case, then all the default cases would be
meaningless and all the cases in the case statements would have to be
unique. That is not possible, is it?
Remember that each reducer function is operating on only its little slice of state, not the entire state object. The default case is there for the reducer to return its current state value since there is no work for it to do. All the reducer cases within a reducer function should be unique. If two actions trigger the same state update then they should be grouped
case "case1":
case "case13":
// both cases apply the same update
I'm using redux-form and it provides a built-in reducer, called "formReducer" that need to be registered with the combined reducers to manage the form state with redux's store.
I'm also using redux-persist to persist the redux store.
The problem raised when I don't want to have my form automatically re-populate the data entered by the user on page reloading or page refreshing. In a normal reducer written by my own, I can simply add an switch case for action of type "REHYDRATE" (dispatched by redux-persit) to prevent the state slice from auto-rehydrating by just returning its initial state or an empty state. But redux-form's formReducer is built-in provided by redux-form, so I cannot change. So, is there any way to "customize" the redux-form reducer to add that switch case? Or, is there any way I can config redux-persist to not auto-rehydrate a specific state slice, or is there any way I can config redux-form to not being auto-populated by page reloading or page refreshing?
I have a "perfect" solution based on suggestion by #jpdelatorre from this thread How to handle redux-form/CHANGE in reducer
Basically it's to "extend" the formReducer provided by redux-form, then add switch case for the event "REHYDRATE":
import { reducer as reduxFormReducer } from 'redux-form'
import { REHYDRATE } from 'redux-persist/constants'
const formPlugin = {
my_redux_form_name: (state, action) => {
switch (action.type) {
case REHYDRATE:
return {}
default:
return state
}
}
}
const formReducer = reduxFormReducer.plugin(formPlugin)
export default formReducer
then have the extended reducer to register with the root reducer.
import formReducer from './form.reducer'
const rootReducer = combineReducers({
...other reducers,
form: formReducer
})
If you are using the latest (v5) redux-persist version, in the persistConfig option there's a whitelist key-option where you whitelist which reducers should be persisted/rehydrated. You should use that, e.g:
const persistConfig = {
key: 'root_key_in_localstorage',
storage,
whitelist: ['session'],
}
You can use a Middleware that will handle this specific action type and prevent it from being passed to the reducers.
const myMiddleWare = store => next => action => {
if(action.type != 'REHYDRATE'){
next(action); // pass the action forward to the reducers
} else{
// do your logic here, you can use store.dispatch to dispatch other actions
// when your not invoking next(action) this action won't pass through to all the reducers
}
}
A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.
I use redux-storage for persisting the redux state tree across refreshes, etc.
redux-storage-engine-localstorage as the storage engine.
My problem has to do with the order of
1) the action which loads this persisted state, and
2) some other action I set off in a componentDidMount lifecycle function.
Because the storage load happens after some action I trigger, the action I trigger might as well not have happened, since the old state overwrites it.
Printing out the action type at the top of my reducer, on page load I see:
action.type ##redux/INIT
action.type HIDE_MODAL
action.type REDUX_STORAGE_LOAD
action.type REDUX_STORAGE_SAVE
action.type FETCH_USER_FAILURE
action.type REDUX_STORAGE_SAVE
The HIDE_MODAL action is triggered in one of my components' componentDidMount method.
The storage load happens before mounting my root node (right after creating my redux store):
import mainReducer from './reducers';
const reducer = storage.reducer(mainReducer);
const storageEngine = createEngine(some key);
const storageMiddleware = storage.createMiddleware(storageEngine);
const middleware = applyMiddleware(
storageMiddleware,
);
const storageLoad = storage.createLoader(storageEngine);
const store = createStore(
reducer,
middleware,
);
storageLoad(store);
render(
<Provider store={store}>
...
</Provider>,
document.getElementById('root')
);
Why does the REDUX_STORAGE_LOAD not happen before everything else, and is there a way to make sure it occurs before everything else?
I added a new state field reduxStorageLoaded, initially false, and added a case to my reducer:
import { LOAD } from 'redux-storage';
...
const mainReducer = (state = initialState(), action) => {
switch (action.type) {
...
case LOAD:
return {
...state,
reduxStorageLoaded: true,
};
...
and wrapped my whole app in a conditional based on this new state variable reduxStorageLoaded so it only mounted once it becomes true, which is when redux-storage has fully loaded and dispatched the LOAD action.