replaceReducer cause reducer called many times in redux - redux

I use store.replaceReducer to load async module reducers.
const reducers = store.reducers; // reducer dictionary to cache
if (reducers.indexOf(reducer) === -1 && reducer !== defaultReducer) {
reducers.push(reducer);
const reducerObj = {};
reducers.forEach((reducerItem) => {
Object.assign(reducerObj, reducerItem);
});
store.replaceReducer(combineReducers(reducerObj));
when I dispath an action in first module, reducer called as expected.
But when replaceReducer is used to append new reducers to store, previous reducer will be called again,and will call after replaceReducer every time .
how can I resolve this problem ?

Actually this happens only if you are using the dev tools. See https://github.com/reduxjs/redux-devtools/issues/291

Related

Proper way of using Redux and RTKQ in NextJs with code-splitting

This is a topic that's been discussed a lot through github issues and by now I've noticed two main opinions: It's not possible or it should not be done at all.
The argument for both sides is that redux is not meant for it, that the .replaceReducer function is only meant for the purposes of hot-reloading (even though redux itself mentions it as a possibility for code-splitting).
The goal
Anyway, what I would like to achieve (ideally) is a system that only sends the relevant slices and relevant redux code for a specific route in NextJs. And (even more ideally) when navigating between pages the store should just get extended and not re-created.
My initial approach
My first idea was to implement a recipe from the link above, attaching and exposing the injectReducer function onto my store during the store setup:
const store = configureStore({
reducer: {
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(rtkqApi.middleware)
});
store.dynamicReducers = {};
store.injectDynamicReducer = (name, reducer) => {
if (Object.keys(store.dynamicReducers).includes(name)) {
return;
}
store.dynamicReducers[name] = reducer;
store.replaceReducer(
combineReducers({
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer,
...store.dynamicReducers
})
);
};
const makeStore = () => store;
export const wrapper = createWrapper(makeStore);
export const injectReducer = (sliceName, reducer) => store.injectDynamicReducer(sliceName, reducer);
So basically every page would have a globalsSlice, containing the user info and some other global data, and Redux Toolkit Query API slice (which would then be code-split using RTKQ injectEndpoints functionality).
With this setup, each page that wants to inject its own custom slice (reducer) would do something like this:
const SomePage = () => {
const someData = useSelector(somePageSliceSelectors.selectSomeData);
return (
<Fragment>
<Head>
<title>Some Page</title>
</Head>
</Fragment>
)
};
export default SomeRoute;
injectReducer('somePageSlice', somePageReducer);
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
// Whatever necessary logic we need
});
Initially this seemed to have worked fine, but then when I realized that next-redux-wrapper works by calling the makeStore factory on every request, and I'm manipulating and mutating a global store object, there has to be something wrong with this, ie a race condition that I haven't been able to cause by testing. Also another problem occurres when using Redux Toolkit Query. For example, if I need to get a cookie from the original request (the one that nextjs receives) and then re-send it to another API endpoint that is handled by redux toolkit query, I would need to extract the cookie from the request context, to which I don't have access unless I do something like this:
export const makeStore = (ctx) => {
return configureStore({
reducer: ...,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: ctx,
},
}).concat(...),
});
};
which further implies that I should definitely not be mutating the global store object.
So then I thought alright, instead of manipulating the global store I could try doing it in GSSP:
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
store.injectDynamicReducer('somePageSlice', somePageReducer);
});
But no luck here, the slice does not get loaded and the state does not get constructed. It is my guess that the Provider in the _app gets rendered before this, but I'm not sure.
In conclusion, I'd like to know whether anyone has tried and succeeded in implementing redux code splitting using RTK, RTKQ and NextJs. Also, I would like to ask an addition question: Is it necessary? What I mean by this is, if I were to not code-split at all, and send all slices on every request, how performance impactful would this be? Also, since I'm not sure exactly how the NextJs bundler works and how code chunking is done: If a certain page receives a slice it doesn't use at all, will it only receive its initial state or all of its logic (all the selectors, reducers and actions)? If not then maybe this isn't so bad, since initial states are just empty objects.
I hope I've presented the problem clearly enough, as it is a very complex problem, but feel free to ask follow up questions if something doesn't make sense.
Thanks in advance.

How to work with Redux reducers and server API

What is a better way to create reducers with handleActions in redux-actions:
1. Create reducers for each CRUD operations (like add data, delete data) and combine it. How set initialState in this case?
2. Set actions in one reducer (fetchDeleteDataRequest, fetchDeleteDataSuccess, fetchAddDataRequest, fetchAddDataSuccess by example)?
You can have sperate reducers and or common reducers to add or delete data that is up to you. If you are considering separate actions for add and delete you will need to keep the data consistent. But having a common function to deal with the CRUD operations is ideal since this will reduce the amount of code that you have to use but you will need a way to distinguish them as well (maybe bypassing some variable ADD or DELETE). let's consider and list of items that you will be adding or deleting. This list can be an empty array in the beginning (initialState) and pass it as props to your component.
Actions
export const addDeleteItem = data => dispatch => {
// you can perform REST calls here
dispatch({
type: ADD_REMOVE_DATA,
payload: data
});
};
Reducers
let initialState = {
items:[]
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_REMOVE_DATA:
if(action.payload.event === 'ADD'){
return {...state,items:[...state.items,action.payload.item]}
}else if(action.payload.event === 'DELETE'){
return {...state,items:state.items.filter(item=>item.id !== action.payload.item.id)}
}else if (action.payload.event === 'UPDATE'){
//handle your update code
}
}
}
This is just an example you can follow something like this to avoid code duplication.

Redux Middleware understanding guidance

const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk, api, wsMiddleware, createLogger()),
typeof window === 'object' && typeof window.devToolsExtension !==
'undefined'
? window.devToolsExtension()
: DevTools.instrument(),
),
);
So the above is how you would usually create a store, and then you have your middleware that starts like this:
export default store => next => (action) => {
I did read up the middleware portion from redux.org, but anyone able to better explain it to me what's going on there ?
so is the middleware is receiving the store ? and calling out the next function from the store and than finally using the parameters given as action (in this case). ?
The Redux middleware pipeline can be broken down like this...
store => {...}
The store API is the first argument given to the pipeline. This allows the middleware to get the current state at any point in the pipeline and/or dispatch new actions into the store.
Note: It has many of the same characteristics as the store returned from the createStore function, but it is not the same. Only the dispatch and getState functions are available.
next => {...}
The next argument is a reference to the next middleware in the chain. If there are no more middleware to go, the store handles the action (i.e. pass it into the the reducer).
If next is not called, the action will not make it to the reducer. This can be useful to suppressing something that is not a valid action on it's own, such as a function or a promise, as an error will be raised by Redux if it tries to handle it.
action => {...}
The action argument is the thing that gets dispatched into the store.
{...}
In here is where you will test the action to see if there is something special you want to do with it and whether you will pass it onto the next handler.
An example
For this example, we will create a simplified thunk middleware, explaining how it uses each part of the pipeline.
export default store => next => action => {
// if the action is a function, treat it as a thunk
if (typeof action === 'function') {
// give the store's dispatch and getState function to the thunk
// we want any actions dispatched by the thunk to go through the
// whole pipeline, so we use the store API dispatch instead of next
return action(store.dispatch, store.getState)
} else {
// we're not handling it, so let the next handler have a go
return next(action)
}
}
Functions that conform to the Redux middleware API. Each middleware receives Store's dispatch and getState functions as named arguments, and returns a function. That function will be given the next middleware's dispatch method, and is expected to return a function of action calling next(action) with a potentially different argument, or at a different time, or maybe not calling it at all. The last middleware in the chain will receive the real store's dispatch method as the next parameter, thus ending the chain. So, the middleware signature is ({ getState, dispatch }) => next => action.
The answer was found in the applymiddleware documentation.
https://redux.js.org/docs/api/applyMiddleware.html

Dynamically adding epics during runtime calls the newly added epics multiple times

My work is based on these directions: https://redux-observable.js.org/docs/recipes/AddingNewEpicsAsynchronously.html
So I am having an issue with getting this test to execute the epics, am I missing something? In any case the code explains what is going on...
I would expect my call of to store.dispatch({type:'GO'}) to chain all of my epics. I see my reducer is wired up but the Epics do not get called.
fyi I need this to work to test what I think is a bug. In my actual application when I dynamically load a new epic using epicLoader$.next(<someepic>) the dynamically loaded epic is now called twice... but I can't prove that until this test works.
As a workaround I have manually added the epics that I wanted to add dynamically and it is working fine but this isn't possible past my POC phase. I'll need to load the epics from separate files that will be pushed to the app.
Help me Obi wan...
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/do';
import { Observable } from 'rxjs/Observable';
import {combineReducers, createStore, applyMiddleware} from 'redux';
import { createEpicMiddleware,combineEpics } from 'redux-observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
test('dynamicly loading epic calls it twice',done => {
let epic1 = (action$)=>action$
.ofType('GO')
.do((action)=>console.log('epic1',action))
.mapTo({type:'EPIC1'});
let epic2 = (action$,{epicLoader$})=>action$
.ofType('EPIC1')
.do((action)=>console.log('epic2',action))
.do(()=>epicLoader$.next(epic3))
.do(()=>epicLoader$.next(epic4))
.mapTo({type:'EPIC2'});
let epic3 = (action$)=>action$
.ofType('EPIC2')
.do((action)=>console.log('epic3',action))
.mapTo({type:'EPIC3'});
let epic4 = (action$)=>action$
.ofType('EPIC2')
.do((action)=>console.log('epic4',action))
.do(()=>done())
.mapTo({type:'EPIC4'});
const epic$ = new BehaviorSubject(combineEpics({epic1,epic2}));
const rootEpic = (action$, store, args) =>
epic$.mergeMap(epic =>
epic(action$, store, args)
);
const epicMiddleware = createEpicMiddleware(rootEpic,
{ dependencies: {
epicLoader$: epic$,
}});
const reducer =(state = {},action)=>{
console.log('reducer',action);
return state;
};
const store = createStore(
combineReducers({
reducer
}),
applyMiddleware(epicMiddleware)
);
//start up the chain of events.
store.dispatch({type:'GO'});
});
I think I see two issues with the provided code:
Providing object to combineEpics instead of just arguments
You're passing an object to combineEpics, but it's supposed to just be plain old arguments. This is different than combineReducers
// bad
const epic$ = new BehaviorSubject(combineEpics({epic1,epic2}));
// good
const epic$ = new BehaviorSubject(combineEpics(epic1,epic2));
Support for passing an object has been discussed here: https://github.com/redux-observable/redux-observable/issues/58. It hasn't been supported yet because the keys would be meaningless, unlike with combineReducers.
This actually does throw an error when the rootEpic is subscribed to, but it's unfortunately swallowed because of a outstanding bug in rxjs
destructuring of store instead of the third dependencies argument
// bad
let epic2 = (action$,{epicLoader$})=>action$
// good
let epic2 = (action$,store,{epicLoader$})=>action$
Once these are fixed, it appears to work as desired:
I did notice that epic4 is listening for EPIC2, which you might have actually meant to listen for EPIC3 maybe?

Why a dispatch to a reducer causes all reducers get called?

In this github redux example, a dispatch of the event ADD_TODO is used to add a task. During the debugging, I found out that adding a task causes both the reducers todos and visibilityFilter being called.
How can I call just the todos reducer and not visibilityFilter reducer when I add a task. Also the visibilityFilter reducer if I sent an event of type SET_VISIBILITY_FILTER.
The combineReducers utility intentionally calls all attached reducer functions for every action, and gives them a chance to respond. This is because the suggested Redux reducer structure is "reducer composition", where many mostly-independent reducer functions can be combined into one structure, and many reducer functions could potentially respond to a single action and update their own slice of state.
As mentioned in other answers, combineReducers calls every reducer whenever a dispatch is called. You can avoid the other values changing, by making the default case equal to the state parameter passed in, so essentially they are reassigned their current value.
For example:
const individualReducer = (state = "initialState", action) => {
switch(action.type)
{
case "ACTION_TYPE":
return action.payload;
default:
return state;
}
}
export default individualReducer;

Resources