-
Notifications
You must be signed in to change notification settings - Fork 560
RFC: New version of context #1
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
Conversation
text/0000-new-version-of-context.md
Outdated
```js | ||
type Theme = 'light' | 'dark'; | ||
// Pass a default theme to ensure type correctness | ||
const ThemeContext: Context<Theme> = new React.createContext('light'); |
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 “new”?
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 is the purpose of the string argument to createContext
? Is that its initial value?
react-broadcast currently uses a channel
prop that determines the key name that it uses inside context.broadcasts
, but I think this is different.
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.
That's the default value, which just happens to be a string in this example. The default value provides type safety in the case where a consumer is rendered without a provider.
text/0000-new-version-of-context.md
Outdated
render() { | ||
return ( | ||
// Pass the current context value to the Provider's `value` prop. | ||
// Changes are detected using strict comparison |
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.
Maybe Object.is? Thinking of NaN.
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 point, that's what shallowEqual
uses. I think I'll still refer to it as strict comparison in most places, with an explanation in the design section.
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 recently added a compareValues
prop to react-broadcast that defaults to making a strict comparison but lets users swap it out if they really need to. Seems like a good spot for an escape hatch.
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.
One of our goals with this API is to encourage immutability, so making it slightly less ergonomic for libraries that rely on mutation is a feature in my view, not a bug. The escape hatch is to create a shadow copy of the container to trigger the change.
text/0000-new-version-of-context.md
Outdated
// Changes are detected using strict comparison | ||
<ThemeContext.Provider value={this.state.theme}> | ||
<button | ||
onClick={state => |
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 should be event?
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.
Seriously, Andrew? "Oh this should be an updater, I'll just add a state
argument..."
} | ||
``` | ||
|
||
`createContext` requires a default value to ensure type correctness. |
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.
Nice
text/0000-new-version-of-context.md
Outdated
|
||
If a consumer is rendered without a matching provider as its ancestor, it | ||
receives the default value passed to `createContext`, ensuring type safety. We | ||
will log a warning in development. |
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.
Should context objects be named? Otherwise it’s pretty tricky to show a reasonable warning message.
Also, is default value always bad? I could see people wanting to rely on it (without seeing the warning).
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.
Should context objects be named? Otherwise it’s pretty tricky to show a reasonable warning message.
Good idea. We could use a transform similar to the one we have for createClass
? I'd rather not have to pass it manually.
Also, is default value always bad? I could see people wanting to rely on it (without seeing the warning).
Yeah, I considered this. I think there are some valid use cases (a themed component that has a default look). But it's usually it's a mistake. I'm thinking about the case where a user renders a Consumer with a Provider, gets the default value, and it takes them a while to realize why the value isn't what they expect.
What if we keep the warning, and add an allowDetached
option that suppresses it?
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.
We could use a transform similar to the one we have for createClass?
That one was a bit convoluted IMO. I'd prefer we avoid creating more transforms like this in the future. It's annoying to handle all different ways people might import something, also makes non-JS languages second class. I also don't think in practice you'd create contexts so often that it's hard to name them.
What if we keep the warning, and add an allowDetached option that suppresses it?
Maybe suppressMissingContextWarning
on the 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.
Maybe suppressMissingContextWarning on the Consumer?
I think that's a great idea. We recently added a <Subscriber quiet>
prop to prevent <Subscriber>
s from complaining when they're rendered out of 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.
Nice! Don't really care what we name it
Regarding the debug name, I would just rather not make it required, especially since it's only for dev. That's how createClass
worked and why I suggested using a Babel transform.
text/0000-new-version-of-context.md
Outdated
|
||
We will deprecate the existing API either simultaneous to the new API's | ||
introduction, or after some period of time to allow users to migrate. We can't | ||
use a codemod to automatically convert, so this may take a while. |
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.
We definitely don’t want to deprecate until big players have converted. We’ll need to chat to popular libraries first and work with them.
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 should be a simple naming swap for us. We're already planning on cutting a new release that uses react-broadcast soon. Could possibly be suggested as something to help people to get ready for this change until it's fully baked.
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.
If you update the React Broadcast API to match the proposal (once it's finalized), it could even serve as a drop-in polyfill :D
I like it, its nearly identical API to our first version of https://unpkg.com/[email protected]/README.md const { GeoEmitter, GeoSubscriber } = createContextEmission('geo') |
A proposal for a new version of context addressing existing limitations.
@ryanflorence Pretty cool! Just so nobody reading this is confused, one difference is that the argument passed to type Provider<T> = React.Component<{
value: T,
children?: React.Node,
}>;
type Consumer<T> = React.Component<{
children: (value: T) => React.Node,
}>;
type Context<T> = {
Provider: Provider<T>,
Consumer: Consumer<T>,
};
interface React {
createContext<T>(defaultValue: T): Context<T>;
}; |
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 this is a drastic improvement over the current context implementation. The one thing I'm concerned about is the render prop API for the Consumer.
const ThemeContext: Context<Theme> = React.createContext('light'); | ||
|
||
class ThemeToggler extends React.Component { | ||
state = {theme: 'red'}; |
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 doesn't match the theme type. Maybe that was intentional?
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.
Oops, oversight. Good catch!
Couldn't the type just be I love the render prop API. |
@jlongster See the "Unresolved questions" section:
|
I saw that but it doesn't actually list the valid use cases :) I would argue we should treat it always as a mistake and avoid an additional |
I think the use cases are similar to |
We're also considering the ability to set |
``` | ||
|
||
To update the context value, the parent re-renders and passes a different value. | ||
Changes to context are detected using `Object.is` comparison. (Referred to as |
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.
May have overlooked it, but how exactly is a change detected in the 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.
When the provider receives a new value, React re-renders the consumers.
I'll add more details about the implementation shortly.
Weird, I pushed a commit but it's not showing up here... |
This is awesome. I like the render prop API as it just seems like less ceremony and typing. I agree with @jlongster about |
|
||
In the following major release, we'll remove the old API. | ||
|
||
# Unresolved questions |
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 a life-cycle hook in regards to context changes should be discussed. Relying on a child function render for detecting this isn't exactly feasible for all situations. Perhaps another prop on the consumer?
<Consumer onChange={this.handleChange} />
Hmm, looks like my previous comments got buried in the "show outdated" view. Please pardon for re-posting down here...
|
The difference to me is props are meant to be used within the component. Context is meant to be used with a Provider, always, even if the provider just used the default value. Making this strict might be a way to keep the API simple and usage clear. |
Sorry folks... I hit a weird GitHub bug. GitHub doesn't recognize changes to this PR since I switched the repo from private to public. Going to close and open a new one. Sorry for the disturbance, let's continue over there! |
@mjackson Interesting. Personally I was happy to add a top-level MemoryRouter for tests/etc and I thought that was quite elegant. In fact it allowed me to specify the initial state of the Router which was very useful, like |
@jlongster You and me both! But it really bothered some people. I can see how the default context value will help with that, though. Now you've got the default value built into the component, instead of needing to get it from another one further up the tree. |
Locking since the discussion has moved to #2. Please continue there! |
Superseded by #2. Please go there instead. I encountered a GitHub bug and had to re-open.
A proposal for a new version of context addressing existing limitations.
Rendered
TODO: