How to get initialState into immer slice, from redux preloadedState? - redux

Say i have a store configured like so.
const buildStore = (formSettings) => {
// const isDevToolsRequired = process.env.NODE_ENV === 'development';
const sagaMiddleware = createSagaMiddleware();
const preloadedState = initialState(formSettings);
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => [
...getDefaultMiddleware({
thunk: false, // We use redux-saga i.e. objects, not the default, thunk i.e. callbacks
serializableCheck: false, // redux toolkit issues
immutableCheck: false, // TODO remove this if we can - after the full move to redux toolkit
}),
sagaMiddleware,
],
preloadedState,
devTools: true, // TODO turn off dev tools for prod once up and running
});
sagaMiddleware.run(rootSaga);
return store;
};
How do i get the preloadedState into initialState of an immerSlice in a next.js application? getStaticProps is used to get build time formSettings from another service, then its passed to configureStore... but i need that value (or parts of it) as my initial for the slices...
My rootReducer looks like:
const rootReducer = combineReducers({
components,
legalStatement,
navigation,
optionalFormConfig,
paging,
formConfig,
submission,
submissionStatus,
});
Removing initialState from any of the slices causes a runtime error, but anything added to initialState does not show in redux state.... my interpretation is that the initialState in the slice is overwritten as i provide preloadedState... so why is the initialState in the slice a required field?

Related

How do I invalidate the Redux Persist cache of an RTK Query I code-generated?

I have a React app where I've used the #rtk-query/codegen-openapi tool to generate my authentication queries and mutations. I'm also using Redux Persist to store some of this data.
I'm trying to figure out a way to invalidate this data programmatically when the user logs out. Is it possible to do this?
My redux store configuration looks something like this:
const persistConfig = {
key: "root",
storage,
blacklist: ["api"],
};
const authPersistConfig = {
key: "auth",
storage,
whitelist: [],
};
const rootReducer = combineReducers({
settings: settingsSlice.reducer,
[applicationApi.reducerPath]: applicationApi.reducer,
[enhancedAuthenticateApi.reducerPath]: persistReducer(
authPersistConfig,
enhancedAuthenticateApi.reducer
),
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const buildStore = () =>
configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat([enhancedAuthenticateApi.middleware]),
});
I'm pretty new to Redux and RTK Queries so any help is appreciated.

How to connect redux-state-sync to redux-next-wrapper

I am trying to connect redux-state-sync for syncronize state in different tabs to my next-redux project. But I have a server error (TypeError: dispatch is not a function) in this line: initStateWithPrevTab(makeStore). So how can I fix this bug? I try to add initStateWithPrevTab(makeStore) as a const or inside arrow func, but it still doesn't work. It's my store.js file where I configure store:
const config = {};
const middlewares = [createStateSyncMiddleware(config)];
const rootReducer = combineReducers({
booking: reducer,
});
const makeStore = () =>
createStore(
withReduxStateSync(rootReducer),
undefined,
applyMiddleware([config])
);
initStateWithPrevTab(makeStore);
export const wrapper = createWrapper(makeStore, { debug: true });

Why redux store doesn't receive an update from immer

Combining reducers
export default (injectedReducers = {}) => {
return combineReducers({
...injectedReducers,
memoizedStamps: memoizedStampsReducer, // <-- need to write here
});
};
Writing an action
const mapDispatch = (dispatch) => ({
addStamp: (payload) =>
dispatch({
type: ADD_STAMP,
payload,
}),
});
Writing the reducer
export const initialState = [];
const memoizedStampsReducer = produce((draft, action) => {
const { type, payload } = action;
switch (type) {
case ADD_STAMP:
draft.push(payload);
}
}, initialState);
export default memoizedStampsReducer;
Using in a react hook
const useMemoizedStamps = () => {
const [memStamps, dispatch] = useImmerReducer(reducer, initialState);
const { addStamp } = mapDispatch(dispatch);
useEffect(() => {
addStamp({ body: 'body', coords: 'coords' });
}, []);
console.log(memStamps); // <-- gives [{ body: 'body', coords: 'coords' }] all good here
return null;
};
export default useMemoizedStamps;
But it gets never saved into injected reducer "memoizedStamps". The array is always empty. It works perfectly will with connect(null, mapDispatchToProps), but can't use connect() in my custom hook.
What do I do wrong? What is the answer here?
--- UPD 1 ---
#phry, like so?
const useMemoizedStamps = (response = null, error = null) => {
const dispatch = useDispatch();
const [memStamps] = useImmerReducer(reducer, initialState);
const { addStamp } = mapDispatch(dispatch);
useEffect(() => {
addStamp({ body: 'body', coords: 'coords' });
}, []);
console.log(memStamps);
return null;
};
And now if I need them to be local, I need to use immer's dispatcher? Any way to merge these two dispatchers? P.S. this dispatcher really saved it to global state.
Rereading this, I think you just have a misconception.
Stuff is never "saved into a reducer". A reducer only manages how state changes.
In Redux, it would be "saved into the store", but for that you would have to actually use a store. useImmerReducer has nothing to do with Redux though - it is just a version of useReducer, which like useState just manages isolated component-local state with a reducer. This state will not be shared with other components.
If you want to use Redux (and use it with immer), please look into the official Redux Toolkit, which already comes with immer integrated. It is taught by the official Redux tutorial.

Error: Invariant failed: A state mutation was detected between dispatches, in the path 'emailReducer.emails.0'

I'm getting this error message about mutating state, but the purpose of Redux Toolkit is mutating state, so I'm confused...
The error is coming from addNewEmail, where I'm adding new emails to the array calling prevEmails using useSelector and the second parameter is a regular string.
import { createSlice } from "#reduxjs/toolkit";
import { AppThunk } from "./store";
const initialState = {
emails: [],
};
export const emailSlice = createSlice({
name: "email",
initialState,
reducers: {
setEmails: (state, action: any) => {
state.emails = action.payload;
},
},
});
export const { setEmails } = emailSlice.actions;
export const addNewEmail = (prevEmails: any, email: string): AppThunk => (
dispatch
) => {
const allEmails = prevEmails.push(email);
dispatch(setEmails(allEmails));
};
export default emailSlice.reducer;
export const selectEmails = (state: any) => state.emailReducer.emails;
I was also getting the same error, this is not how you disptach the
action. All you have to pass this middlewares in your store.
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
})
})
As #asaf-aviv said, the real problem is that you're attempting to mutate what is actually state.emails, outside of a reducer:
const allEmails = prevEmails.push(email);
dispatch(setEmails(allEmails));
The second problem is conceptual. You should model actions as "events", not "setters", and put as much logic as possible into reducers. If you follow those guidelines, this problem won't occur in the first place.
Also, this doesn't even need to be a thunk - just dispatch an action that contains the new email object.
The right way to handle this is:
export const emailSlice = createSlice({
name: "email",
initialState,
reducers: {
emailAdded: (state, action: PayloadAction<Email>) => {
state.emails.push(action.payload)
},
},
});
export const { emailAdded } = emailSlice.actions;
// later
dispatch(emailAdded(newEmail));
You are mutating the state before dispatching the action, you can do mutations inside the reducer but not outside of it.
You can change prevEmails.push(email) to prevEmails.concat(email) which will return a new array which you can then send as a payload.

What is the second argument for?

I find this code in a tutorial
...
import configureMockStore from 'redux-mock-store';
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
...
it('should create BEGIN_AJAX_CALL & LOAD_COURSES_SUCCESS', (done) => {
const expectedActions = [
{type: types.BEGIN_AJAX_CALL},
{type: types.LOAD_COURSES_SUCCESS, body: {
courses: [{id:'clean-code', title:'Clean Code'}]
}}
];
const store = mockStore({courses:[]}, expectedActions);
store
.dispatch(courseActions.loadCourses())
.then(() => {
const actions = store.getActions();
expect(actions[0].type).toEqual(types.BEGIN_AJAX_CALL);
expect(actions[1].type).toEqual(types.LOAD_COURSES_SUCCESS);
done();
});
});
and the whole bit with expectedActions doesn't make sense.
The docs say that if there is a second argument to store, it should be a function; (no explanation telling what that function would do though).
At first I thought it was forcing some actions into the store for some reason, but a quick console.log told me that wasn't the case.
Because only dispatch causes actions to accumulate.
So is it a mistake in the text or some wisdom to explore further?
This feature was removed in version 1, but you can find the example in the pre 1 docs.
The parameter expectedActions is used for testing. You can create a mock store with an array of actions, and then dispatch an the 1st action. This action will cause the other other actions to forwarded (dispatch / next) via thunks/api middleware/etc... The test will check if all of the actions in the expectedActions array have acted on the store:
import configureStore from 'redux-mock-store';
const middlewares = []; // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares);
// Test in mocha
it('should dispatch action', (done) => {
const getState = {}; // initial state of the store
const action = { type: 'ADD_TODO' };
const expectedActions = [action];
const store = mockStore(getState, expectedActions, done);
store.dispatch(action);
})

Resources