react-boilerplate / redux-injectors Goto Github PK
View Code? Open in Web Editor NEWAsynchronous injectors for Redux reducers and sagas. As used by react-boilerplate.
License: MIT License
Asynchronous injectors for Redux reducers and sagas. As used by react-boilerplate.
License: MIT License
According to the source code of createManager
, we can not pass reducer
and saga
in the parameter options
:
const isReducerInjected = reducer // <-- check
? useInjectReducer({ key, reducer })
: true;
const isSagaInjected = saga // <-- check
? useInjectSaga({ key, saga })
: true;
But in typing, these properties are required:
export function createManager(options: { name: string, key: string, saga: Saga, /* <- required */ reducer: Reducer /* <- required */ }): ...;
What is the mistake? in typing or implementation?
ERROR in C:/src/TheCoin/node_modules/redux-injectors/index.d.ts(16,17):
TS7010: 'forceReducerReload', which lacks return-type annotation, implicitly has an 'any' return type.
Sure enough, what I have is:
export function forceReducerReload(store: {});
I'd really like to have this compiler option turned on, as it can catch quite a few genuine bugs, but this issue is preventing me at the moment.
I'd be happy to open a PR, but I don't see where your TS types come from (only JS in this repo?)
I have the same problem as addressed in the following question.
https://stackoverflow.com/questions/35622588/how-to-reset-the-state-of-a-redux-store
I need to clear all the redux state on logout, but since my logout is on a separate global slice it can only affect the global slice and not other redux states. How do I clear all other states calling an action from global state?
Here's how I combine reducers:
import { combineReducers } from '@reduxjs/toolkit';
export function createReducer(injectedReducers = {}) {
if (Object.keys(injectedReducers).length === 0) {
return state => state;
}
return combineReducers({
...injectedReducers
});
}
my globalReducer:
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
loading: false
};
const globalSlice = createSlice({
name: 'global',
initialState,
reducers: {
logout(state) {
return state;
}
}
});
export const { actions, reducer, name: sliceKey } = globalSlice;
my store/index.js
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { createInjectorsEnhancer, forceReducerReload } from 'redux-injectors';
import createSagaMiddleware from 'redux-saga';
import { createReducer } from './reducers';
function configureAppStore() {
const reduxSagaMonitorOptions = {
onError: (error, { sagaStack }) => {
// console.log(error, sagaStack);
}
};
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
const { run: runSaga } = sagaMiddleware;
// Create the store with saga middleware
const middlewares = [sagaMiddleware];
const enhancers = [
createInjectorsEnhancer({
createReducer,
runSaga
})
];
const store = configureStore({
reducer: createReducer(),
middleware: [
...getDefaultMiddleware({
serializableCheck: {
ignoredActionPaths: ['payload.history']
},
immutableCheck: false
}),
...middlewares
],
devTools:
process.env.NODE_ENV !== 'production'
|| process.env.PUBLIC_URL.length > 0,
enhancers
});
// Make reducers hot reloadable, see http://mxs.is/googmo
if (module.hot) {
module.hot.accept('./reducers', () => {
forceReducerReload(store);
});
}
return store;
}
export const store = configureAppStore();
After upgrading to React 16.13, we started getting this warning in many places:
More info about it in the 16.13 announcement post.
Here's how it happens in our case:
ParentContainer
with useInjectReducer
ChildContainer
with useInjectReducer
ChildContainer
's useInjectReducer
is called, the warning appears.This happens because calling useInjectReducer
calls store.replaceReducer
synchronously during the render of the ChildContainer
.
From what I understand, we're not technically updating the state of a component during the render of another component but we are updating the ReactReduxContext
- which contains the store
and is also consumed by ParentContainer
via useStore
inside useInjectReducer
- so that must be enough to trigger the warning.
The suggestion by the React team is to "wrap the setState
call into useEffect
".
Up until September of last year, this is exactly how useInjectReducer
worked: injection happened inside an effect. However, doing the injection inside an effect caused a different issue: we couldn't guarantee that the reducer or saga was always injected before a relevant action is dispatched. (Discussed in RBP here.)
Does anyone have any thoughts or suggestions regarding how we could fix this?
I am trying to work with redux saga (React 18.2.0), I configured the store, the reducer and the request, so to do the API call I decided to inject the reducer and saga using useInjectReducer
and useInjectSaga
from redux-injectors
:
import { useInjectReducer, useInjectSaga } from 'redux-injectors';
the problem is that when I try to install redux-injectors
I got this error:
npm install redux-injectors
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"^18.2.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.6.0 || ^17.0.0" from [email protected]
npm ERR! node_modules/redux-injectors
npm ERR! redux-injectors@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR! See C:\Users\Admin\AppData\Local\npm-cache\eresolve-report.txt for a full report.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Admin\AppData\Local\npm-cache\_logs\2022-09-25T09_19_46_303Z-debug-0.log
I locked up for the solution, and I found that I need to add --legacy-peer-deps
or --force
while installing, this solution works fine in my local machine, but it will not work at the dev environment because at this environment we install dependencies only using npm install
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.6.0" from [email protected]
npm ERR! node_modules/redux-injectors
npm ERR! redux-injectors@"*" from the root project
had to do this.. also react-boilerplate-cra still on version 16
npm i redux-injectors --force
Given a functional component, I would like the ability to use multiple sagas.
An example of this may be a settings page where button1 dispatches action1 and button2 dispatches action2. These actions would correspond to different sagas.
Alternatively, what is another way of doing this? Would it be the code below?
export function* settingsPageSaga() {
yield takeLatest(actions.action1.type, functionForAction1);
yield takeLatest(actions.action2.type, functionForAction2);
}
When trying to create a new app with redux v8 installed I get the following error:
npm ERR! Found: react-redux@8.0.4
npm ERR! node_modules/react-redux
npm ERR! react-redux@"8.0.4" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react-redux@"^7.1.0" from redux-injectors@2.1.0
npm ERR! node_modules/redux-injectors
npm ERR! redux-injectors@"2.1.0" from the root project
I'm wondering if this is tangentially related to #34 - If I use --force
or --legacy-peer-deps
the install does finish, and the app does run and work without issue.
I think this could easily be fixed by adding redux 8 as a peer dep, but i know v7 to v8 is a major version bump
Hi
The problem I am working on is making apps work under micro frontend architecture. One challenge I am working on is I like to completely reject a reducer from the Redux store when users leave the page aka componentWillUnmount
I've done the PoC as following below
// injectReducer.js
const useInjectReducer = ({ key, reducer }) => {
const store = useStore();
const [isInjected, setIsInjected] = React.useState(false);
React.useLayoutEffect(() => {
getInjectors(store).injectReducer(key, reducer);
setIsInjected(true);
return () => {
// To remove a reducer from Redux store
getInjectors(store).rejectReducer(key);
};
}, []);
return isInjected;
};
// reducerInjectors.js
export function ejectReducerFactory(store, isValid) {
return function ejectReducer(key, reducer) {
if (!isValid) checkStore(store)
if (
!Reflect.has(store.injectedReducers, key)
)
return;
delete store.injectedReducers[key]
store.replaceReducer(store.createReducer(store.injectedReducers));
};
}
export default function getInjectors(store) {
checkStore(store);
return {
injectReducer: injectReducerFactory(store, true),
ejectReducer: ejectReducerFactory(store, true),
};
}
Do you think, does this worth a pull request?
I suspect this is an issue with react itself rather than with redux-injectors, but posting here first as it's the first link in the chain.
I have a component that injects it's reducers and sagas, calls the saga to fetch from an API, and then uses the returned data all within the same component. When I do this, I get the following error:
Warning; React has detected a change in the order of hooks called by <Component>
The code did look like this:
const HelpDocs = () => {
// useReducer & useSaga work together to populate state.documents
usePrismic();
// dispatcher to trigger saga
const actions = usePrismicActions();
// fetch said documents from redux
const docs = useSelector((s: ApplicationRootState) => s.documents);
// onLoad, trigger the actions to fetch the doc's from API
useEffect(() => {
actions.fetchFaqs();
}, []);
I fixed it by pulling the useReducer etc bit out into a wrapping component:
export const HelpDocs = (props: RouteComponentProps) => {
usePrismic();
return <HelpDocsInternal {...props} /> // The rest of the code
}
As I said, I suspect this isn't in your domain, but you know this project better than I do so perhaps it might ring some bells before posting in react itself.
We are doing integration tests with our react app but couldn't create the store with preloadedState
Unexpected key "injectedSlice" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: ....
As I understand because we don't initialize the reducer when configure the store so it could not understand in test. Maybe I have to create another version of configureStore
for testing only?
Updating to @reduxjs/toolkit 1.6.0 gives the following type errors with the following code :
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import { createInjectorsEnhancer } from 'redux-injectors'
// ...
const enhancers = [
createInjectorsEnhancer({
createReducer,
runSaga,
}),
]
const store = configureStore({
reducer: createReducer(),
middleware: [...getDefaultMiddleware(), ...middlewares],
devTools:
/* istanbul ignore next line */
process.env.NODE_ENV !== 'production' || process.env.PUBLIC_URL.length > 0,
enhancers,
preloadedState: state,
})
Type 'StoreEnhancer<{}, {}>[]' is not assignable to type 'StoreEnhancer<{}, {}>[] | ConfigureEnhancersCallback | undefined'.
Type 'import("/home/esya/Git/oasis-wallet/node_modules/redux/index").StoreEnhancer<{}, {}>[]' is not assignable to type 'import("/home/esya/Git/oasis-wallet/node_modules/@reduxjs/toolkit/node_modules/redux/index").StoreEnhancer<{}, {}>[]'.
Type 'import("/home/esya/Git/oasis-wallet/node_modules/redux/index").StoreEnhancer<{}, {}>' is not assignable to type 'import("/home/esya/Git/oasis-wallet/node_modules/@reduxjs/toolkit/node_modules/redux/index").StoreEnhancer<{}, {}>'.
Types of parameters 'next' and 'next' are incompatible.
Types of parameters 'preloadedState' and 'preloadedState' are incompatible.
Type 'import("/home/esya/Git/oasis-wallet/node_modules/redux/index").PreloadedState<S> | undefined' is not assignable to type 'import("/home/esya/Git/oasis-wallet/node_modules/@reduxjs/toolkit/node_modules/redux/index").PreloadedState<S> | undefined'.
Type 'PreloadedState<S>' is not assignable to type 'PreloadedState<S> | undefined'.
Type '(S extends CombinedState<infer S1> ? { [K in keyof S1]?: (S1[K] extends object ? PreloadedState<S1[K]> : S1[K]) | undefined; } : never) | { [K in keyof S]: S[K] extends object ? PreloadedState<...> : S[K]; }' is not assignable to type 'PreloadedState<S> | undefined'.
Type 'S extends CombinedState<infer S1> ? { [K in keyof S1]?: (S1[K] extends object ? PreloadedState<S1[K]> : S1[K]) | undefined; } : never' is not assignable to type 'PreloadedState<S> | undefined'.
Type '{}' is not assignable to type 'PreloadedState<S>'.
Type 'S extends CombinedState<infer S1> ? { [K in keyof S1]?: (S1[K] extends object ? PreloadedState<S1[K]> : S1[K]) | undefined; } : never' is not assignable to type 'PreloadedState<S>'.
Type 'import("/home/esya/Git/oasis-wallet/node_modules/redux/index").PreloadedState<S>' is not assignable to type 'import("/home/esya/Git/oasis-wallet/node_modules/@reduxjs/toolkit/node_modules/redux/index").PreloadedState<S>'.
Type '(S extends CombinedState<infer S1> ? { [K in keyof S1]?: (S1[K] extends object ? PreloadedState<S1[K]> : S1[K]) | undefined; } : never) | { [K in keyof S]: S[K] extends object ? PreloadedState<...> : S[K]; }' is not assignable to type 'PreloadedState<S>'.
Type 'S extends CombinedState<infer S1> ? { [K in keyof S1]?: (S1[K] extends object ? PreloadedState<S1[K]> : S1[K]) | undefined; } : never' is not assignable to type 'PreloadedState<S>'.
Type '{}' is not assignable to type 'PreloadedState<S>'.ts(2322)
Hi @BenLorantfy !
When I tried to create a reducer with Immer https://github.com/immerjs/immer.
const reducer = (state = initialState, action) => produce(state, draft => { switch (action.type) { case SIGNUP_REQUEST: draft.isFetching = true; break; } })
I got this error :
injectReducer: Expected reducer
to be a reducer function
I think it shouldn't get the error.
Please help, thanks you.
I know RBP isn't a TS project, but there isn't any reason RI couldn't go that route (rather than building types).
I'd be happy to port the code.
Why I care - I'm using RBP on TS monorepo - I'd already split out the injectors similar to this but would prefer to use this project if possible.
This one is probably bit of an edge case, so let me know if you're not interested in supporting it.
I store a dynamic map of user accounts in Redux, and the user has the ability to switch between accounts. The accounts might have long-running actions, and they should keep running when the user switches away. My components are written to be agnostic to all this - from the POV of a component there is only one account, the one they are acting on. When I switch to a new account, I want a component to be able to start the saga's associated with that account.
My hook looks like this:
export const useAccountApi = (address: string) => {
const { actions } = getAccountReducer(address);
useInjectSaga({ key: address, saga: buildSagas(address) });
const dispatch = useDispatch();
return bindActions(actions, dispatch);
}
This works the first time a user loads an account, however if the user switches accounts no new saga is injected, because useInjectSaga does not test the key, it uses a boolean to test whether this has been registered already:
redux-injectors/src/injectSaga.js
Line 87 in f4accd7
If you have a static list of saga's, this works fine. However, in my case I needed to test the key to see if the saga had been previously injected:
So... I feel like this request may be counter to the philosophy of hooks in general, but I haven't been able to figure out how to do it cleaner. I have a constant number of hooks, but I may start a different saga if I switch account (and remain on the same page).
If it is (counter to your philosophy) do you have any advice on how to do it cleaner? Ideally, without re-architecting my application? If not, would you accept this as a PR?
I am trying to get the initial state values from the Store directly using
const store = createStore(reducer, composeEnhancers(
...
));
store.getState()
I am getting undefined
I have two slices in my store. Each has one reducer which I inject at the root of my react app.
notificationSlice.ts
import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface notificationFormat {
type: 'system' | 'confirmation' | 'message' | 'chat';
title: string;
description: string;
avatarSrc?: string;
lessonID?: number;
}
export interface notificationsState {
notifications: notificationFormat[];
}
const initialState: notificationsState = {
notifications: [],
};
export const notificationsSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
addNotification: (state, action: PayloadAction<notificationFormat>) => {
state.notifications.push(action.payload);
},
removeNotification: (state, action: PayloadAction<number>) => {
delete state.notifications[action.payload];
},
clear: (state, action: AnyAction) => {
state.notifications.length = 0;
},
setNotifications: (state, action: PayloadAction<notificationFormat[]>) => {
state.notifications = action.payload;
},
},
});
export const { addNotification, removeNotification, setNotifications, clear } =
notificationsSlice.actions;
export default notificationsSlice.reducer;
accountSlice.ts
import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface accountState {
email: string;
password: string;
checked: boolean;
}
const initialState: accountState = {
email: '',
password: '',
checked: false,
};
export const accountSlice = createSlice({
name: 'account',
initialState,
reducers: {
setEmail: (state, action: PayloadAction<string>) => {
state.email = action.payload;
},
setPassword: (state, action: PayloadAction<string>) => {
state.password = action.payload;
},
setChecked: (state, action: PayloadAction<boolean>) => {
state.checked = action.payload;
},
logout: (state, action: AnyAction) => {
state.email = '';
state.password = '';
state.checked = false;
},
},
});
export const { setEmail, setPassword, setChecked, logout } =
accountSlice.actions;
export default accountSlice.reducer;
I want my store to be persistent so I save every change to local storage and load it back when the website gets refreshed.
However, every time my website gets refreshed and the reducers get injected again, it clears half of my state.
If I inject the notificationSlice reducer first -> it clears the accountSlice. And vice versa.
When I tried debugging it I found that just after the refresh the state loads just fine (from the local storage) but it breaks when the reducers are injected.
How to fix this?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.