-
Notifications
You must be signed in to change notification settings - Fork 46.7k
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
Fixed incompatibility between react-debug-tools and useContext() #14940
Fixed incompatibility between react-debug-tools and useContext() #14940
Conversation
Great! Thanks a lot! |
Well done! Thx! |
@@ -93,6 +93,7 @@ function useContext<T>( | |||
context: ReactContext<T>, | |||
observedBits: void | number | boolean, | |||
): T { | |||
nextHook(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if relevant, but we add the workinprogresshook to the list only when DEV. so hook.next
behaves differently in prod vs dev https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js#L527-L529
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. My test had a blindspot here since the null hook would just re-initialize the state. I'll change it to throw if that happens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... 🤔 I think this might be trickier than a simple __DEV__
check can account for. It's not a matter of whether react-debug-tools
is itself in production or development mode, but whether the renderer it's interacting with is.
d8b9115
to
73a1cae
Compare
73a1cae
to
e777462
Compare
Maybe we can change it to add the hook to the list whether dev or prod. I remember adding it only so we could do the warning for when the order doesn’t match across renders. It doesn’t serve any other purpose. |
Yeah, I'm looking into that now. |
Previously in production mode, If this change is unacceptable, then we have a few alternatives:
The above two alternatives have a couple of benefits:
I would be interested in hearing @sebmarkbage's thoughts on this. |
1e9f755
to
3f5cde5
Compare
I'd err on the side of keeping context calls very cheap and accepting some integration fragility (DevTools pass the DEV flag) as a tradeoff. With a test that shouldn't even be that fragile. |
My M.O. in any case like this is to error on the side of adding more implementation complexity to the DEV side and keep the prod side clean. In this case, let’s take a step back and ask why DEV differs. It’s easy to assume that context is the odd one out here and that all other hooks goes into the hooks list. That’s not true conceptually though. Some of the hooks we have now don’t have to be stateful (eg useEffect without a dep list nor destructor). In the future, the hooks we want to add will also not be stateful. So the list we have right now is not so much the canonical “hooks list”. It’s only the “stateful hooks list”. In fact, in the future some of them might have more than one entry. I don’t know exactly but I’m guessing that the only reason @acdlite added context to this set in DEV is that it makes it easier to issue warnings that enforce call order and preserve our option to keep them. However, by reusing the production mechanism for this, I think we’ve also accidentally introduced a bug (prod/dev behavior) that is observable through the invariant calls that only happen in one path. The lesson learned from “current owner” is that it’s a bad idea to rely on subtle production behavior for dev warnings because they’re not always going to overlap. That’s why we have “current debug frame” instead for the warning info. I think it would be appropriate to do the same here if we want to preserve some of the dev warnings. If we want to conceptually model the warnings as the user facing api contract (every primitive hook call is one hook in that exact order). Then we should explicitly model that in dev. Which requires dev to have a separate hooks list that overlaps mostly with the stateful hooks list (but not exactly). |
Interesting. I hadn't considered this option, but I dig it. |
I'll expand our hook ordering test to make sure it covers all of the hook types. There are definitely cases where we'll warn about other things as well when the order changes (e.g. inputs array changing) but at least we can ensure that we're warning about the order changing. |
Okay. I've refactored this to a DEV-only list of hook names to verify ordering. I've also updated our tests to cover more cases (including several that failed prior to this change). There are a couple of potential follow up things we could do (which I left TODO comments for). |
ReactDOM: size: 0.0%, gzip: 0.0% Details of bundled changes.Comparing: 0b8efb2...3899681 react-dom
react-art
react-native-renderer
react-test-renderer
react-reconciler
Generated by 🚫 dangerJS |
This adds a small amount of overhead, so I'm not sure if it will fly. I think it might be necessary though in order to support the react-debug-tools package.
This enables us to warn about more cases (e.g. useContext, useDebugValue) withou the need to add any overhead to production bundles.
ef8bd2c
to
c2fc659
Compare
Rebased and updated DEV mode to also check for mount vs update using the |
'1. useReducer useReducer\n' + | ||
'2. useState useRef\n' + | ||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n', | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change wasn't strictly necessary but it seems like this test is a bit more robust (and reads better) if it uses our toWarnDev
check.
An issue was reported in facebookarchive/redux-react-hook/issues/34 where DevTools caused a runtime error when inspecting a component that used a
useContext
hook. I determined the cause to be due to the fact that theuseContext
hook inside ofReactDebugHooks
doesn't callnextHook()
to advance the list, which causes subsequent hooks to be mismatched.Initially, I thought the fix would be to simply add that call– but React itself is inconsistent with how it treats
useContext
between development and production builds. This presents a problem for thereact-debug-tools
package– it either breaks in development or production mode.I've addressed this by refactoring our hooks ordering checks to use a separate, DEV only list (stored on fibers as
_debugHookTypes
). This way we don't have to rely on a hook being added to the hooks list in order to be validated, and code likeReactDebugHooks
does not have to worry about inconsistent behavior between DEV and PROD bundles, (and we avoid adding additional overhead to PROD bundles).This changed caught a couple of additional warnings in existing tests. I've beefed out or test coverage in this area as well for going forward.
A few alternative fixes were considered:
useContext
implementation in React to usemountContext
/updateContext
(instead of callingreadContext
directly) so that it's consistent between modes.react-debug-tools
will work with earlier hook releases too).buildType
flag toinspectHooks
(telling it whether React is running in production mode).ReactDebugHooks.useContext
then conditional callsnextHook()
based on this flag.