Coder Social home page Coder Social logo

react-boilerplate / redux-injectors Goto Github PK

View Code? Open in Web Editor NEW
127.0 127.0 29.0 775 KB

Asynchronous injectors for Redux reducers and sagas. As used by react-boilerplate.

License: MIT License

JavaScript 92.88% Shell 0.78% HTML 3.21% CSS 3.13%

redux-injectors's Issues

installing redux-injectors without --force or --legacy-peer-deps

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

Unmet peer dependencies with Redux 8

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

update to react 17

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

Typescript error: implicit "any"

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?)

useInjectReducer clears the state

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?

[React Testing Library] Integration tests with preloadedState

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?

incorrect typing for function `createManager` ?

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?

Feature Request: useInjectSagas ie useInjectSaga for multiple sagas

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);
}

Typing issues with latest @reduxjs/toolkit

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)

Persist state of redux

Using redux-injectors makes it imposible to persist the state because the injectorsEnhancer regularly replaces the rootReducer with a new one - and only the initial one of those is persisted. Am I wrong? How can we persist the state of redux?
image
image

Typescript support?

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.

Feature request: reducer ejection

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?

How do I clear state on logout

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();

How to create reducer with Immer ?

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.

Not able to get the store value

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

React 16.13 throws a warning when "nesting" useInjectReducer calls

After upgrading to React 16.13, we started getting this warning in many places:

image

More info about it in the 16.13 announcement post.

Here's how it happens in our case:

  • Have ParentContainer with useInjectReducer
  • Have ChildContainer with useInjectReducer
  • When 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?

Change in the order of Hooks when injecting a reducer, calling it, and selector-ing it withing a single component

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.

Feature Request: Ability to change the saga registered on a component using Hooks

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:

const isInjected = React.useRef(false);

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:

FrozenKiwi@9efd162

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.