I have 2 reducers:
const initialState = {
data:[],
};
export const first = (state = initialState, action) => {
switch (action.type) {
case DATA: {
return {
...state,
data: action.payload
}
}
}
return state;
};
and second:
const initialState = {
data2:[],
};
export const second = (state = initialState, action) => {
switch (action.type) {
case DATA2: {
return {
...state,
data2: action.payload
}
}
}
return state;
};
I decided tu use combineReducers to merge them. Fot this, i used:
const rootReducer = combineReducers({first, second});
My store:
import rootReducer from "./reducer/rootReducer";
export const store = createStore(rootReducer);
console.log(store.getState())
When i check the state of the store with store.getState(), i get an object something like this:
{first: {…}, second: {…}},
but i need to use destructuring of these 2 objects inside combine reducers.
For this i made:
const rootReducer = combineReducers({...first, ...second});but now, i got an emty object, plus this error: Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
Question: Why i can't destructure the reducers like i wrote above? How to do this?
The reason you cannot destructure the reducers like above is because they are functions and not objects
If you carefully look you are creating an object for combineReducers like
combineReducers({
first: (state, action) => {...},
second: (state, action) => { ...}
})
Now combineReducer returns you a single function by combining the reducers which you can pass to createStore,
You can destructure the individual state post store.getState()
const {first, second} = store.getState();
const data = {...first, ...second};
Related
I'm fairly new to Redux and I'm trying to understand why is the combineReducers function calling the reducers twice.
My reducer/index.js looks like this
import {combineReducers} from "redux"
const AReducer = (state = "A", action) =>{
console.log("In A reducer")
switch (action.type){
case "Alpha":
return state + action.payload
default:
return state
}
}
const BReducer = (state = "B", action) =>{
console.log("In B reducer")
switch(action.type)
{
case "Beta":
return state + action.payload
default:
return state
}
}
const allReducers = combineReducers({
A : AReducer,
B : BReducer
})
export default allReducers
and my store/index.js looks like this
import {createStore} from "redux";
import allReducers from "../Reducer"
const store = createStore(allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
And the console log is displaying this
index.js:4 In A reducer
index.js:4 In A reducer
index.js:15 In B reducer
index.js:15 In B reducer
index.js:4 In A reducer
index.js:15 In B reducer
I only want to understand why it's behaving like this. I want a better grasp around what's going on in the background
First of all, let's print the action.
import { combineReducers, createStore } from 'redux';
const AReducer = (state = 'A', action) => {
console.log('In A reducer, action: ', action);
switch (action.type) {
case 'Alpha':
return state + action.payload;
default:
return state;
}
};
const BReducer = (state = 'B', action) => {
console.log('In B reducer, action: ', action);
switch (action.type) {
case 'Beta':
return state + action.payload;
default:
return state;
}
};
const allReducers = combineReducers({
A: AReducer,
B: BReducer,
});
const store = createStore(allReducers);
The logs:
In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In A reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' }
In B reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In B reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONu.8.f.5.c.h' }
In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
In B reducer, action: { type: '##redux/INIT3.j.l.q.g.r' }
Explanation
I use AReducer's logs to explain, Breducer is also the same.
combineReducers function calls assertReducerShape() function internally.
assertReducerShape() function will invoke each reducer passed in the combineReducers function with a init action to check if the reducer has a valid returned value. This is how In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' } log come.
And, it also invokes each reducer with unknown actions to check if the reducer return the current state for any unknown actions unless it is undefined. This is how In A reducer, action: { type: '##redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' } log come.
When calling the createStore function, it will dispatch init action. So that every reducer returns their initial state. This effectively populates the initial state tree. This is how In A reducer, action: { type: '##redux/INIT3.j.l.q.g.r' } log come. This process is mentioned in the documentation, See tips.
Also take a look at the INIT and PROBE_UNKNOWN_ACTION action types in utils/actionTypes file.
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `##redux/INIT${/* #__PURE__ */ randomString()}`,
REPLACE: `##redux/REPLACE${/* #__PURE__ */ randomString()}`,
PROBE_UNKNOWN_ACTION: () => `##redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
These action types are private, and used by redux internally, you don't need to handle them. You will see the dispatched INIT action in redux dev tools, don't be surprised.
In redux actions, when we want to set a value, we use a type to dispatch like this :
dispatch({
type: SET_LOADER,
payload: true
})
Where the type: SET_LOADER stored in a different file and export it like below.
export const SET_LOADER = 'SET_LOADER'
And in reducer we will do it like this :
function initialState() {
return {
formErr: {},
isLoading: false
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
So in my application, I have this SET_LOADER type used in different actions and reducers. For example, in authentication, in profile update, when I want to load, I will use this type. So I have this type imported in various places.
I'm not sure if it's okay to use a single type for multipurpose because I noticed now that when I do dispatch, the redux state that get updated is not belonged to the target reducer. The state update is happening at different reducer.
But it's working for the first time dispatch. The next update, it's updating the incorrect redux state. After I refresh the page and try to update again, then it work.
first of all you need to separate your reducer into multiple reducers and then combine them in the store , then you can probably get away by using that same action in multiple cases for but then it'll be only a per reeducer solution meaning that let's say you have and Auth reducer this reducer will have its isLoading , and it may interfere with other actions within that reducer , fore example FetchAllProducts will use isLoading but also FetchByIdProduct is using isLoading and same for other actions that will trigger a loading state .
let's consider these reducers which use the same initial state
function initialState() {
return {
formErr: {},
isLoading: false
}
}
export const authReducer=(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
export const productsReducer=(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
export const cartReducer =(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
//this is the store
import {createStore,applyMiddleware,compose,combineReducers} from 'redux'
import thunk from 'redux-thunk'
import {productsReducer} from './reducers/ProductReducer'
import {cartReducer} from './reducers/CartReducer'
import {authReducer } from './reducers/AuthReducer'
const initialState={
products: {
formErr: {},
isLoading: false
},
cart: {
formErr: {},
isLoading: false
},
auth: {
formErr: {},
isLoading: false
}
}
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(combineReducers({
products: productsReducer,
cart : cartReducer ,
auth : authReducer,
}),
initialState,
composeEnhancer(applyMiddleware(thunk))
)
export default store
even though their using the same initial state you , when you will connect a component to the redux store you have access to three different isLoading :
export default connect((state)=>({
isLoading : state.products.isLoading,
isLoading2: state.authReducer.isLoading,
isLoading3: state.cart.isLoading,
}))(Products)
but to be honest I'd rather have make my actions more explicit and case specific something like productsFetchIsLoading , this gives you more control and prevents bugs
I noticed now that when I do dispatch, the redux state that get updated is not belonged to the target reducer. The state update is happening at different reducer.
Every action gets dispatched to every reducer. When you call dispatch({ type: SET_LOADER, payload: true }), the expected behavior is that the isLoading state will get set to true in every reducer which has a case SET_LOADER.
If you want the loading states to be independent then each reducer needs a unique string action type.
If you have multiple similar reducers then you can use a factory function to generate the type names, action creator functions, and reducer cases. Here we are extending the createSlice utility from Redux Toolkit.
We pass in the name which is the prefix for the auto-generated action types, the initialState of just the unique properties for this reducer state, and any unique reducer cases. This will get merged with the standard base state.
Helper:
const createCustomSlice = ({name, initialState = {}, reducers = {}}) => {
return createSlice({
name,
initialState: {
formErr: {},
isLoading: false
...initialState,
},
reducers: {
setLoader: (state, action) => {
state.isLoading = action.payload;
},
setFormErr: (state, action) => {
state.formErr = action.payload;
}
...reducers,
}
});
}
Usage:
const profileSlice = createCustomSlice({
name: "profile",
initialState: {
username: ""
},
reducers: {
setUsername: (state, action) => {
state.username = action.payload;
}
}
});
// reducer function
const profileReducer = profileSlice.reducer;
// action creator functions
export const { setFormErr, setLoader, setUsername } = profileSlice.actions;
These action creators will create actions with a prefixed type like 'profile/setLoader'.
I have seen solutions for clearing/resetting the store after logout but did not understand how to implement the same functionality for the following way of setting up the redux store.
Store.js:
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import authReducer from './ducks/authentication'
import snackbar from './ducks/snackbar'
import sidebar from './ducks/sidebar'
import global from './ducks/global'
import quickView from './ducks/quickView'
import profileView from './ducks/profileView'
const store = configureStore({
reducer: {
auth: authReducer,
snackbar,
sidebar,
global,
quickView,
profileView,
},
middleware: [...getDefaultMiddleware()],
})
export default store
Here is how all the reducers implemented using createAction and createReducer from #reduxjs/toolkit.
snackbar.js:
import { createAction, createReducer } from '#reduxjs/toolkit'
export const handleSnackbar = createAction('snackbar/handleSnackbar')
export const openSnackBar = (
verticalPosition,
horizontalPosition,
message,
messageType,
autoHideDuration = 10000
) => {
return async dispatch => {
dispatch(
handleSnackbar({
verticalPosition,
horizontalPosition,
message,
autoHideDuration,
messageType,
isOpen: true,
})
)
}
}
export const closeSnackbar = () => {
return dispatch => {
dispatch(handleSnackbar({ isOpen: false }))
}
}
const initialState = {
verticalPosition: 'bottom',
horizontalPosition: 'center',
message: '',
autoHideDuration: 6000,
isOpen: false,
messageType: 'success',
}
export default createReducer(initialState, {
[handleSnackbar]: (state, action) => {
const {
isOpen,
verticalPosition,
horizontalPosition,
message,
autoHideDuration,
messageType,
} = action.payload
state.isOpen = isOpen
state.verticalPosition = verticalPosition
state.horizontalPosition = horizontalPosition
state.message = message
state.autoHideDuration = autoHideDuration
state.messageType = messageType
},
})
As per Dan Abramov's answer, create a root reducer which will simply delegate the action to your main or combined reducer. And whenever this root reducer receives a reset type of action, it resets the state.
Example:
const combinedReducer = combineReducers({
first: firstReducer,
second: secondReducer,
// ... all your app's reducers
})
const rootReducer = (state, action) => {
if (action.type === 'RESET') {
state = undefined
}
return combinedReducer(state, action)
}
So, if you have configured your store with #reduxjs/toolkit's configureStore, it might look like this:
import { configureStore } from '#reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export default configureStore({
reducer: {
counter: counterReducer,
// ... more reducers
},
});
where configureStore's first parameter reducer accepts a function (which is treated as root reducer) or an object of slice reducers which is internally converted to root reducer using combineReducers.
So, now instead of passing object of slice reducers (shown above), we can create and pass root reducer by ourselves, here is how we can do it:
const combinedReducer = combineReducers({
counter: counterReducer,
// ... more reducers
});
Now, lets create a root reducer which does our reset job when needed:
const rootReducer = (state, action) => {
if (action.type === 'counter/logout') { // check for action type
state = undefined;
}
return combinedReducer(state, action);
};
export default configureStore({
reducer: rootReducer,
middleware: [...getDefaultMiddleware()]
});
Here is CodeSandbox
I wanted to extend Ajeet's answer so that it is accessible to those who want complete type safety throughout their Redux store.
The key differences are that you need to declare a RootState type, which is documented in the RTK docs
const combinedReducer = combineReducers({
counter: counterReducer
});
export type RootState = ReturnType<typeof combinedReducer>;
And then in your rootReducer, where you are executing your logout function, you want to maintain type safety all the way down by giving the state param the RootState type, and action param AnyAction.
The final piece of the puzzle is setting your state to an empty object of type RootState instead of undefined.
const rootReducer: Reducer = (state: RootState, action: AnyAction) => {
if (action.type === "counter/logout") {
state = {} as RootState;
}
return combinedReducer(state, action);
};
I forked Ajeet's answer on CodeSandbox, added the required types, and you can view it here.
If you're looking to reset each slice to its initial state (unlike setting the entire state to an empty object) you can use extraReducers to respond to a logout action and return the initial state.
In auth.tsx:
const logout = createAction('auth/logout')
In foo.tsx:
const initialState = {
bar: false,
}
const fooSlice = createSlice({
name: 'foo',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(logout, () => {
return initialState
})
},
})
A simplified example with two reducers:
// actions and reducer for state.first
const resetFirst = () => ({ type: 'FIRST/RESET' });
const firstReducer = (state = initialState, action) => {
switch (action.type) {
// other action types here
case 'FIRST/RESET':
return initialState;
default:
return state;
}
};
// actions and reducer for state.second
const resetSecond = () => ({ type: 'SECOND/RESET' });
const secondReducer = (state = initialState, action) => {
switch (action.type) {
// other action types here
case 'SECOND/RESET':
return initialState;
default:
return state;
}
};
const rootReducer = combineReducers({
first: firstReducer,
second: secondReducer
});
// thunk action to do global logout
const logout = () => (dispatch) => {
// do other logout stuff here, for example logging out user with backend, etc..
dispatch(resetFirst());
dispatch(resetSecond());
// Let every one of your reducers reset here.
};
The simple solution - just add a reducer like this...
resetList: (state) => {
return (state = []);
},
... and call it with a button:
const handleResetList = () => {
dispatch(resetList());
};
return (
<div>
<div>List</div>
<button onClick={handleResetList}>Reset</button>
I have two reducers, the first one fetches data and the second one is used to filter the data based on changes to the original data. I have read that using combineReducers is not an option.
My postReducer
import {FETCH_POST} from '../actions/types';
const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action){
switch (action.type){
case FETCH_POST:
return Object.assign({}, state, {items: action.payload});
default:
return state;
}
}
My pinReducer
import {PIN_POST} from '../actions/types';
const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action){
switch (action.type){
case PIN_POST:
console.log(state.items);
const item = state.items.map(value => value.data.id === action.id ?
{data: Object.assign({}, value.data, {pinned: action.val})} : value
);
//console.log(item);
return Object.assign({}, state, {items: item });
default:
return state;
}
}
Main
import {combineReducers} from 'redux';
import postReducer from './postReducer';
import pinReducer from './pinReducer';
export default combineReducers({
post: postReducer,
pin: pinReducer
});
How can I share the state between the two reducers, as state.items in the pinReducer is empty
You should not have a reducers that has state derived on the basis of other reducers. What you need in your case is a selector(more efficiently a memoizedSelector), for which you can use reselect library
My postReducer
import {FETCH_POST} from '../actions/types';
const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action){
switch (action.type){
case FETCH_POST:
return Object.assign({}, state, {items: action.payload});
default:
return state;
}
}
Main
import {combineReducers} from 'redux';
import postReducer from './postReducer';
export default combineReducers({
post: postReducer,
});
and then where you want to use pinItem, you can do that in mapStateToProps
getPinItem = (state, props) => {
const item = state.items.map(value => value.data.id === action.id ?
{data: Object.assign({}, value.data, {pinned: action.val})} : value
);
return item;
}
const mapStateToProps = (state, props) => {
const pinItem = getPinItem(state, props);
return {
pinItem
}
}
Reducer does not access to store's state, nor other reducers' previous state. Only to its own previous state and dispatched action. You can think of it as a single brick in the wall of redux' state.
In your case, we have to tackle the logic and decide what is an actual state here.
Requirements:
fetch a list of posts: state is an array of posts;
pin one of these posts: state is an id of currently pinned post;
get currently pinned post: find a post in an array by pinned post id.
Solution:
1 - initial posts reducer:
const initialState = { items: [] }
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return { items: action.payload }
default:
return state
}
}
2 - add info about pinned post:
const initialState = { items: [], pinnedId: null }
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return { ...state, items: action.payload.posts }
case PIN_POST:
return { ...state, pinnedId: action.payload.id }
default:
return state
}
}
3 - get a currently pinned post:
// assuming that store is configured...
export default combineReducers({ posts: postsReducer })
// ...
// somewhere in `connect`
const mapStateToProps = (state, ownProps) => {
return {
pinnedPost: state.posts.find(post => post.id === state.posts.pinnedId),
}
}
So now we store a minimum information about a pinned post (only its id) and it is enough to get an actual pinned post.
Also, as mentioned in another answer by #shubham-khatri, you can use optimized selectors. With current shape of the state storing pinned id separately, it is even more efficient since your selector will depend on two inputs: posts array (is re-created on fetch, so shallow comparable) and pinned id (is a primitive number or string, also easy to compare).
I'd encourage you to store as minimum information in store as possible. Also, store it in simple and flat shape. Move everything that's can be calculated to selectors.
I'm trying to get the store from the reducer.
i saw the redux architecture is not supporting sharing between reducers.
but its really needed in my case.
const initState = {
schemas: [],
};
const myReducer = (state , action) => {
state = state || initState;
switch (action.type) {
case 'redux-form/CHANGE':
const schemaId = getStore().context.schema;
let modifier = state.schemas.get(schemaId);
modifier[action.key] = action.value;
return {
...state
};
}
};
my app reducers:
const iceApp = combineReducers({
form: formReducer,
context,
myReducer,
});
thanks ahead.
You can add reducer functions to any level of your state, consider:
const complexReducer = (state, action) {
const {context, myReducer} = state;
switch (action.type) {
case 'redux-form/CHANGE':
// do something with myReducer
return {context, myReducer};
default:
return state;
}
}
// a simple utility function to call reducers in sequence on the same state
function composeReducers(...reducers) {
return (state, action) => reducers.reduceRight((acc, reducer) => reducer(acc, action), state);
}
const iceApp = composeReducers(complexReducer, combineReducers({
form: formReducer,
context,
myReducer,
}));
This will apply the complexReducer to the whole state coming from the simple reducers.
A different approach is to access context in the action and pass it as payload of the action.
const changeAction = ... // the redux-form/change action
const changeActionWithContext = () => (dispatch, getState) => {
const state = getState();
const schemaId = state.context.schema;
dispatch(changeAction(schemaId));
}
I don't now redux-form so I don't know whether it's possible or not to add custom payload to redux-form actions.