-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Deprecate context object as a consumer and add a warning message #13829
Deprecate context object as a consumer and add a warning message #13829
Conversation
packages/react/src/ReactContext.js
Outdated
context.Consumer = { | ||
$$typeof: REACT_CONTEXT_TYPE, | ||
_context: context, | ||
get _calculateChangedBits() { |
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.
I think getters don't work on www, can you rewrite this as Object.defineProperty
calls?
Worth checking tho
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.
Really? They work in IE9 the last time I checked.
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.
I changed over to Object.defineProperties
. It's a shame Flow has such bad support for them though :(
React: size: -0.0%, gzip: 0.0% Details of bundled changes.Comparing: 0af8199...9149b17 react
react-dom
react-art
react-test-renderer
react-reconciler
react-native-renderer
scheduler
Generated by 🚫 dangerJS |
} | ||
} else { | ||
// Context.Consumer is a separate object in DEV, where | ||
// _context points back to the original context |
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 comment confuses me. It talks about DEV but is in PROD code path?
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.
It's a DEV path.
false, | ||
'You are using the Context from React.createContext() as a consumer.' + | ||
'The correct way is to use Context.Consumer as the consumer instead. ' + | ||
'This usage is deprecated and will be removed in the next major version.', |
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.
"in a future major release"
} else { | ||
// Context.Consumer is a separate object in DEV, where | ||
// _context points back to the original context | ||
context = workInProgress.type._context; |
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.
Can you explain why we read it differently in DEV and PROD? It would help to have a written explanation of DEV/PROD matrix for Consumer/Context object and how behavior changes for each.
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.
Can you reference me to another matrix? I updated the comment
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.
There's none. I just meant if you could write up of what's supposed to change.
- dev when using consumer: (before) and (after)
- dev when using context: (before) and (after)
- prod when using consumer: (before) and (after)
- prod when using context: (before) and (after)
That would be easier for me to review since I can focus on intent separately from code.
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.
I've put a big nice comment in there that explains things better now. I'll aadd the matrix to this PR.
packages/react/src/ReactContext.js
Outdated
Provider: context.Provider, | ||
}; | ||
// $FlowFixMe: Flow complains about not setting a value, which is intentional here | ||
Object.defineProperties(consumer, { |
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.
Is it better to have consumer proxy to context, or the other way around? Why?
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.
Whatever we read/write from/to most. I’m actually not sure which one that is.
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.
I checked and the context gets written to the most in our tests.
let context: ReactContext<any> = workInProgress.type; | ||
// The logic below for Context differs depending on PROD or DEV mode. In | ||
// DEV mode, we create a separate object for Context.Consumer that acts | ||
// like a proxy to Context. This proxy object adds unecessary code in PROD |
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.
unnecessary
// a property called "_context", which also gives us the ability to check | ||
// in DEV mode if this property exists or not and warn if it does not. | ||
if (__DEV__) { | ||
if (workInProgress.type._context === undefined) { |
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.
Can this just be context._context
? To avoid extra reads (even in DEV).
); | ||
} | ||
} else { | ||
context = workInProgress.type._context; |
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 can also be just context._context
.
ReactNoop.flush(); | ||
}).toLowPriorityWarnDev( | ||
'You are using the Context from React.createContext() as a consumer.' + | ||
'The correct way is to use Context.Consumer as the consumer instead. ' + |
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 message might confuse people a little bit. Maybe make it more visual?
Rendering <Context> directly is not supported and will be removed in a future major release. Did you mean to render <Context.Consumer> instead?
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 message is still not clear @trueadm . I'm already calling Context.Consumer as a consumer. But I'm still getting the error message.
import React from "react"
const defaultState = {
dark: false,
toggleDark: () => {},
}
const ThemeContext = Context.Consumer(defaultState)
// Getting dark mode information from OS!
// You need macOS Mojave + Safari Technology Preview Release 68 to test this currently.
const supportsDarkMode = () =>
window.matchMedia("(prefers-color-scheme: dark)").matches === true
class ThemeProvider extends React.Component {
state = {
dark: false,
}
toggleDark = () => {
let dark = !this.state.dark
localStorage.setItem("dark", JSON.stringify(dark))
this.setState({ dark })
}
componentDidMount() {
// Getting dark mode value from localStorage!
const lsDark = JSON.parse(localStorage.getItem("dark"))
if (lsDark) {
this.setState({ dark: lsDark })
} else if (supportsDarkMode()) {
this.setState({ dark: true })
}
}
render() {
const { children } = this.props
const { dark } = this.state
return (
<ThemeContext.Provider
value={{
dark,
toggleDark: this.toggleDark,
}}
>
{children}
</ThemeContext.Provider>
)
}
}
export default ThemeContext
export { ThemeProvider }
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.
@laurosilvacom you should be using ThemeContext = createContext(...) and then, doing ThemeContext.Consumer and ThemeContext.Provider
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.
I'm not calling const ThemeContext = React.createContext(defaultState)
but still getting the same error. Am I missing something about how I'm suppose to call <Context.Consumer>
instead?
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.
I was suggesting you should call it and use it that way. I can help more when I’m back in the office in a few days.
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.
Sounds good. I appreciate the help.
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.
I don't understand what you're trying to do.
You should create context like this:
const ThemeContext = React.createContext(defaultState) // NOT "Context.Consumer(defaultState)"
and then subscribe to it either like this:
class Button extends Component {
static contextType = ThemeContext; // NOT ThemeContext.Consumer
render() {
// ...
}
}
or like this:
class Button extends Component {
render() {
return (
// NOT <ThemeContext>
<ThemeContext.Consumer>
{theme => ...}
</ThemeContext.Consumer>
);
}
}
Does that help?
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.
Yes!
I did not realize I needed to subscribe to it by calling <ThemeContext.Consumer>
.
I had:
<ThemeContext>
{theme => ...}
</ThemeContext>
Thanks @gaearon!
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.
Right, which is why the warning says:
Rendering
<Context>
directly is not supported and will be removed in a future major release. Did you mean to render<Context.Consumer>
instead?
Do you still feel it was unclear? Which part could be improved? Thanks!
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.
An error message that's direct would help.
Rendering directly is not supported and will be removed in a future major release. You want to use
<Context.Consumer>
instead.
'You are using the Context from React.createContext() as a consumer.' + | ||
'The correct way is to use Context.Consumer as the consumer instead. ' + | ||
'This usage is deprecated and will be removed in a future major release.', | ||
{withoutStack: true}, |
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.
Why no stack? This is exactly the kind of warning where I think the stack would be highly valuable.
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.
n00b question: how does one generate a stack with lowPriorityWarning
? I know you can with warning
but then the APIs are consistent then. I'll just switch to warning
to unblock this.
}, | ||
Consumer: { | ||
get() { | ||
return context.Consumer; |
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.
What if you render <Context.Consumer.Consumer>
? Seems like that should also warn because it also relies on objects being shared.
Which makes me think: should the warning move into these getters instead? Fire for first getter accessed, ignore the rest. This could also nicely let us warn once per context type.
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.
Moving the warning into the getters will stop the error from coming up for just using <Context />
as React's internals will always read/write from the context and never the consumer – the backwards compatibility is there for cases where libraries might try and read/write to the consumer for whatever reason. I'll address the nested Consumer.Consumer
issue.
packages/react/src/ReactContext.js
Outdated
}; | ||
// $FlowFixMe: Flow complains about not setting a value, which is intentional here | ||
Object.defineProperties(consumer, { | ||
_calculateChangedBits: { |
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.
_calculateChangedBits
and unstable_read
never change, can we directly point to the original without getters for them?
packages/react/src/ReactContext.js
Outdated
Provider: context.Provider, | ||
unstable_read: context.unstable_read, |
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.
It's assigned right after, no? This is undefined.
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.
Moved the line above, my bad.
packages/react/src/ReactContext.js
Outdated
_context: context, | ||
_calculateChangedBits: context._calculateChangedBits, | ||
Provider: context.Provider, | ||
unstable_read: context.unstable_read, |
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.
Now that I think of it Context.Consumer.unstable_read()
should also warn.
React v16.6 had some breaking changes to the way context works for SSR libraries. I'm trying to fix jaydenseric/graphql-react#11 but finding it pretty hard to work out whats going on… any tips? Here are some relevant locations:
|
The fixes in response to facebook/react#13829 break support for react < v16.6.
I thought I'd worked it out, but discovered (after a release and deployment 😞) that context consumer behavior is pretty different when
vs
It's midnight now, I give up. |
@jaydenseric Did you have any luck fixing your issue? I wonder if the follow up PR fixes the issues your experienced: #14033. |
@trueadm I did eventually get it working, there were a few things that had to change; here is a diff. Looking over it a now, a few days later it's hard to remember exactly the issues…
I live in fear non semver major React releases will break this logic again; It's happened twice now. Do you think the changes coming in #14033 will cause this implementation to break again? It's kinda hard for me to tell looking at it.
|
…ebook#13829) * Deprecate context object as a consumer and add various warning messages for unsupported usage.
The fixes in response to facebook/react#13829 break support for react < v16.6.
The fixes in response to facebook/react#13829 break support for react < v16.6.
This PR deprecates usage of React context as a consumer in DEV mode, along with a warning message informing the user of this change. Under the hood, we create a new object for
context.consumer
rather than having a cyclic reference tocontext
. This object proxies values from the original context so it remains backwards compatible in React 16.dev when using consumer:
Before: When using
Context.Consumer
, it's the same asContext
so nothing special is handled.After: When
Context.Consumer
is used the at the beginning of reconciliation, the fiber is given the_context
field from theConsumer
, that references toContext
.dev when using context:
Before: When using
Context
, it's the same as the previous case (but the wrong behaviour).After: When
Context
we check if_context
field exists on it (which we added to the new proxy object) and because it won't (only Context.Consumer does) we trigger a new warning.prod when using consumer:
Context.Consumer
, it's the same asContext
so nothing special is handled.prod when using context:
Context
, it's the same as the previous case so nothing special is handled.