-
Notifications
You must be signed in to change notification settings - Fork 558
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
Context.write #89
Context.write #89
Conversation
b69c699
to
2883daf
Compare
I was just thinking about it - per-root changeable defaultValue would allow "automatic Provider injection" effect, making some stuff(like React-Helmet, side-effect, etc) easier. |
We could also use this to store latest versions of component implementations in hot reloading, and instead of |
text/0000-context-write.md
Outdated
|
||
## How to handle multiple roots | ||
|
||
The trickiest question how to deal with multiple roots. React does not guarantee consistency across roots; each root has its own commit phase, and suspending inside one root has no effect on the others. This isn't observable in synchronous mode, since React will block the main thread (including paint) until every root's commit phase has finished. In concurrent mode, however, React may yield in between each commit. With Suspense, the time between each commit phase could vary by many seconds. |
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.
"The trickiest question is how"
7d96361
to
d3dd7be
Compare
d3dd7be
to
3bd121a
Compare
|
||
Still, although caching in component state isn't ideal, it's arguably "good enough" for many use cases today. | ||
|
||
Not so with Suspense. The Suspense model is that the first time React renders an IO-bound component, an exception is thrown when attempting to read the data. While React is waiting for the data to load, it continues rendering the rest of the tree, but it doesn't commit the result; the partially completed tree is _discarded_. Once the data has resolved, React attempts to render the entire tree over again from scratch. It may, as an optimiztion, reuse parts of the previous attempt. But it's not a guarantee. That means using local component state to cache data won't work, because that state will most likely not be persisted. The underlying priciple here is that you can't cache the intermediate results of a computation (server data) on the output of the computation itself (the React tree). |
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.
"The underlying principle"
In particular, consumer components could still be used with a provided context to replace the global default context with a fixture in tests, right? |
Great job @acdlite :) |
|
||
An alternative model is to treat all the roots as siblings and commit them at the same time. This could be viable for single page React apps (where there aren't that many roots, anyway). It doesn't work so well in cases where React is embedded inside another framework (e.g. progressive enhancement of a server rendered app), where temporary inconsistencies may be desirable. For example, the opt-in API for concurrent mode relies on roots committing separately so that you can upgrade some roots to concurrent mode without upgrading the entire app. In an app with mixed synchronous and concurrent roots, a unified commit would mean that every call to `Context.write` has to be synchronous, which probably makes this option a non-starter. | ||
|
||
In either of these models, React would need to track a global list of all roots in order to schedule updates on them. This isn't something we do currently, and it means roots would no longer be automatically garbage collected; discarded roots would need to be explicitly unmounted to remove them from the global list. |
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 by "discarded roots" you mean those which are just removed from DOM without calling unmountComponentAtNode()
, why not use MutationObserver
?
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.
How would that work? AFAIK MutationObserver only works for immediate children. It won't tell you if a tree is disconnected by an ancestor node.
|
||
### Immutable store (like React Redux) | ||
|
||
Implementing immutable stores is straightfoward. Actions are applied in the render phase using the functional form of `Context.write`. |
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 a point I'm misunderstanding. What does it mean concretely that
Actions are applied in the render phase using the functional form of Context.write
?
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.
Explanation of render phase versus commit phase: https://twitter.com/acdlite/status/977291318324948992
|
||
This doesn't address the multiple root problem, nor does it work with React's server renderer, which does not have updates. | ||
|
||
## Use Hooks |
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.
+1 for more symmetry between component state and context. Ideally, lifting component state & logic up to into a shared context wouldn't require rewriting the whole thing.
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.
Agreed. I've been doing stuff on this direction on https://github.com/diegohaz/constate
Ideally, we should be writing local state until we really need to lift it up. Then, refactoring it into global/shared/contextual state should be something really simple.
1f0a0a0
to
67a9fbb
Compare
67a9fbb
to
1cc318c
Compare
@acdlite fantastic proposal! What's the plan? Where do we go from here? Is there a concrete path forward like there is for the Hooks proposal? |
Is that possible to add const Context = React.createContext(initialValue, contextDidUpdate);
Context.freeze();
Context.write(newValue); // Error. This context has been freeze and can't change defaultValue. |
implement in https://codesandbox.io/s/react-context-next-xk1rk using redux |
I have submitted an RFC with alternative solution. I believe more inline with hooks direction. |
Looks like this either needs more work and/or won't work at all. |
This is a very early stage proposal. I'm opening this RFC as a way to explain the problem space. We're still exploring alternative solutions. If this were an ECMAScript proposal it'd be Stage 0.
Summary
Proposes an extension to the context API for updating the default context value. This would allow for React-managed state that lives outside the UI tree and is shared across roots.
Basic example
Rendered text