-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 16.13 throws a warning when "nesting" useInjectReducer calls #19
Comments
It's a hack and probably not a good idea to use it long term but might it be possible to fix this with |
Might be our best option... will it run before other |
I don't know. Would that be an issue? TBH, the more I think about it, the more I dislike this solution. For now, in my team at least, I think we're gonna use the HOC on ChildContainer instead of the hook (if that fixes the issue, haven't tried it yet). |
Hi, anyone found a solution for this warning? It become very annoying this errors in the console. |
@julienben @vrbarros Do either of you have a short example you could share with me? I'm having trouble re-producing. I tried this code and it doesn't log a warning: const ChildComponent = () => {
useInjectReducer({ key: "books", reducer: booksReducer })
return null;
}
const ParentComponent = () => {
useInjectReducer({ key: "users", reducer: usersReducer })
return (
<React.Fragment>
<ChildComponent myProp={{}} />
</React.Fragment>
);
}
render(
<Provider store={store}>
<ParentComponent />
</Provider>
); |
That sample looks right to me... Are you on React v16.13.0 or 16.13.1? They reverted the warning in certain cases in the last version. More here. |
So I've done some digging and realized this warning can happen when selectors don't return the same value (when compared referentially) when the top-level state changes. For example, this selector will always produce a new referential value when called:
However, this selector would be fine:
This selector would also cause the warning (if using
The reason is because:
@julienben @vrbarros Any chance one of your selectors is doing this? |
@BenLorantfy my selectors follow the same standard: import { createSelector } from 'reselect';
import { initialState } from './reducer';
/**
* Direct selector to the changeContextMenu state domain
*/
const selectChangeContextMenuDomain = state => state.changeContextMenu || initialState;
/**
* Other specific selectors
*/
/**
* Default selector used by ChangeContextMenu
*/
const makeSelectChangeContextMenu = () =>
createSelector(selectChangeContextMenuDomain, substate => substate);
export default makeSelectChangeContextMenu;
export { selectChangeContextMenuDomain }; |
@vrbarros That example looks like it should be fine. The only way I can re-produce this warning is with a selector that returns a new value when the top-level state changes. I wonder if there's any selectors in your app that do this? It would be helpful if possible if you could link to a repository that I could debug further. It's hard to debug without all the files (reducer, selectors, etc.) |
@BenLorantfy unfortunately not, all the selectors follows the same pattern here, and I'm getting this warning for all the containers that uses |
@BenLorantfy I figure out that I was using resolve: {
modules: ['node_modules', 'app'],
extensions: ['.js', '.jsx', '.react.js'],
alias: {
moment$: path.resolve(process.cwd(), 'node_modules/moment/moment.js'),
'styled-components': path.resolve(process.cwd(), 'node_modules/styled-components'),
react: path.resolve(process.cwd(), 'node_modules/react'),
'react-dom': path.resolve(process.cwd(), 'node_modules/@hot-loader/react-dom'),
...options.resolve.alias,
},
}, Now I got a better view of the problem in the stack trace. |
@BenLorantfy I found this PR, but I didn't check yet if this solution works. |
I think that solution can cause the reducer to not being injected before an action is dispatched, similar to this issue: react-boilerplate/react-boilerplate#2757 |
bump as this is still an issue; will try and debug myself and see if I can come up with a solution |
@d-pollard If you could create a github repo that has the issue and share it with me that would be very helpful. It's hard to reproduce this otherwise. |
@BenLorantfy - should have one up in ~15min |
interesting, I can't replicate in a fresh repo, so it's gotta be a pattern in my repo causing it, will update when I have more info; I do know that I can in fact fix the issue by editing the source of redux-injectors useInjectReducer to utilize |
@BenLorantfy - it looks like it's related to async actions; If I don't attach the sagas, it mounts perfectly fine, but once I mount the sagas, it throws the aforementioned error. I wonder if parent-state updating is causing an issue; seems like it could possibly be an attempt to inject a new reducer while an existing reducer is actively updating its state |
@BenLorantfy - here's your working error repo: https://github.com/d-pollard/inject-reducer-error-poc to see the issue, run the app then visit: http://localhost:3000/the-issue |
I have this issue too on all the components that use the hooks of redux-injectors |
@BenLorantfy - if it helps any; using |
@d-pollard Won't that affect actions, For example, actions dispatched even before the reducer mounts |
It didn't mess with my actions that fired; would need to do more extensive testing though |
Okay, Did you keep any timeout though like a second or 500ms or just a 0 did the job? |
0 did the job |
essentially acts like a "nextTick" |
Yeah, right. This could actually do the job until the package itself gets updated with the solution! |
This is what the function ended up looking like: const useInjectReducer = ({ key, reducer }) => {
const store = useStore();
const isInjected = React.useRef(false);
if (!isInjected.current) {
isInjected.current = true;
setTimeout(() => {
getInjectors(store).injectReducer(key, reducer);
}, 0);
}
}; |
@julienben @BenLorantfy If the above solution works just fine, Can you please update the package with this solution? |
I don't understand the react internals enough to know if this is a good idea. My suspicion is that this is just getting around the error and will probably be an issue in a future react version.
I think we'll probably end up changing this to |
@BenLorantfy I agree with you, I'm in the same situation and I need to refactor parts of my code related to the hooks pattern that are still not 100% clear to me. |
Yeah I've been getting the same error
Running react version 16.13.1 |
Any update on the fix? @BenLorantfy @julienben |
@mdaffan, we have created a StateManager component that injects all the application's reducers and sagas in the meantime; we render the component as part of the App render. Not sure if this might be an okay solution for now. |
Can you show an example implementation of how you are doing it @martinezjose ? |
Ok after much investigation I’ve found a few scenarios where this can happen:
So we have two options:
Opinion
More resources: |
@BenLorantfy Wow, just confirmed redux-devtools was causing the error messages in my current situation. Nice catch.. I've spent so long trying to figure out the root issue. Going to keep looking into this as well. |
The underlying warning is for scenarios when
Now, under the hood redux-injectors uses redux's When replaceReducer is called it fires an internal REPLACE action in order to populate the store state according to the new reducers (i.e. so that you immediately have access to the initial state of new reducers passed in) Thus if you call replaceReducer in a component as it is mounting i.e.
It causes the store state to change. Finally the way that Thus, for all intents and purposes in any app with non-trivial state subscriptions using One of the major constraints in all of this is that to access the store instance redux-injectors goes through the React context. The end result is this key question:
It seems that the HOC approach solves this problem by doing this in the constructor, but I'm not actually convinced this is appropriate? The React docs say to avoid side effects in the constructor. The hooks approach doesn't give us access to that constructor to be able to call replaceReducer before the render phase, thus the only available phase is the commit phase. The remaining issue is the ordering of the commit phase means that child components may run their effects which could rely on the injected reducers before the parent component that is supposed to inject the reducer. I imagine this is what is leading @BenLorantfy to go down the path of "we either do this at the first possible moment in the commit phase, or simply render no children until such moment as the reducer has been injected". It is also what @d-pollard is going for in their comment here . I'm equally unclear on whether either of these options is any good. |
@danielrob your comment aligns with my understanding of this issue as well... The main problem with implementing Children can use So my approach in #22 is to change the implementation to I'm not sure what else we can do 🤷♂️ |
I was thinking we could introduce a new api like this:
And then used like this:
Internally, This causes a double-render (one render with |
Yes, tackling this in my use case, I just ended up opting for an HOC that null's one render. It just seems the safest option with the minuscule downside of a null render as you say:
Which is along the same lines of what you are suggesting with createManager. Providing booleans from the injector hooks would allow people to do this similarly in a once-off DIY wrapper component fashion. |
Any update on this issue? |
Any updates? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Small update: I've released a pre-release (2.0.0-rc) that fixes this issue. It's not production ready, but if any volunteers want to test it with their apps, that would be helpful. If we don't find any unexpected issues we should be able to release a full release soon. |
@BenLorantfy I attempted migrating our (rather large) frontend project to the 2.0.0-rc release. Unfortunately it looks like some of the patterns we were using are no longer working. For example, every feature page is set up with a "loader" type of component that makes the network calls and checks for any errors on them before rendering the feature page with the retrieved data. At the top of these loader components, we typically inject our reducers and sagas here, and then attempt to extract from them via a
Currently I get an |
@joejordan this pattern is unfortunately not possible anymore without getting the warning here. The reason this used to work was because the reducer was being injected during the render. Injecting a reducer is usually a side-effect, which is what react is warning about here. You would have to re-structure the code in this case so |
This was fixed in #28 |
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
withuseInjectReducer
ChildContainer
withuseInjectReducer
ChildContainer
'suseInjectReducer
is called, the warning appears.This happens because calling
useInjectReducer
callsstore.replaceReducer
synchronously during the render of theChildContainer
.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 thestore
and is also consumed byParentContainer
viauseStore
insideuseInjectReducer
- so that must be enough to trigger the warning.The suggestion by the React team is to "wrap the
setState
call intouseEffect
".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?
The text was updated successfully, but these errors were encountered: