{"action":"created","comment":{"url":"https://api.github.com/repos/reactjs/rfcs/pulls/comments/235643799","pull_request_review_id":177554518,"id":235643799,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNTY0Mzc5OQ==","diff_hunk":"@@ -0,0 +1,242 @@\n+- Start Date: 2018-11-19\n+- RFC PR: (leave this empty)\n+- React Issue: (leave this empty)\n+\n+# Summary\n+\n+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.\n+\n+# Basic example\n+\n+```js\n+const Context = React.createContext(initialValue, contextDidUpdate);\n+// Update the global context value across all roots. Any context consumer that\n+// is not wrapped in a Provider will re-render with this value.\n+Context.write(newValue);\n+// Functional updates also supported for access to the previous value.\n+Context.write(prevValue => newValue);\n+\n+function contextDidUpdate(newContext) {\n+  // Optional callback that fires whenever context changes\n+}\n+```\n+\n+# Motivation\n+\n+### Caching external data\n+\n+Where should a React app store data fetched over IO?\n+\n+It may be tempting to cache it in component state. This is a common pattern for pre-Suspense React apps. But there are some drawbacks. For example, if a Comment component depends on data from the server, consider the implications of storing that data in the Comment component's local state. What if the same comment data is rendered by a different component on another part the page? I shouldn't have to fetch the same comment twice. What if I navigate away and my comment is unmounted, but then I later navigate back? I don't want to refetch the same comment again merely because the old instance was unmounted. The underlying principle here is that data fetched over IO typically does not semantically align with the lifetime of a component used to present that data. The data and its presentation are separate concepts.\n+\n+Still, although caching in component state isn't ideal, it's arguably \"good enough\" for many use cases today.\n+\n+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).\n+\n+One might argue our Suspense implementation is wrong and we should change it. However, the throw-it-out-and-try-again design is not primarily an implementation decision, but a modeling one. The main motivating use case is server rendering. If data were cached using the component tree, then the server would need to track the state of every component. By moving to an external cache, the React server renderer can remain stateless; all it needs to track is which parts of the tree have yet to complete. Caching on the component tree would also complicate client-side hydration. The server renderer would need to serialize the tree of component state and send it to the client. Not only is this complicated, it's bad practice. It's better to hydrate that data into in a normalized data cache, anyway, for the reasons described above.\n+\n+Ok, so storing in component state won't work with Suspense. Where then? React currently doesn't have a good answer to this question.\n+\n+Caching in a global singleton, or some other object that's not managed by React, will work up to a point. But it's tricky to do correctly in concurrent mode without inconsistencies (tearing). Even if you manage to do that, you can't take full advantage of concurrent rendering, where there may be interleaved pending updates, and persistent mutations are only permitted in the commit phase.\n+\n+React does provide an API that's similar to what we want: context. Context is already used to read values that semantically live outside the React tree. But many uses of context today are in fact backed by global singletons. We're missing a way to manage external state in an idiomatic way that works with concurrent rendering and Suspense.\n+\n+## Automatic dependency injection\n+\n+One of the most common uses of context in React is for dependency injection. Instead of relying on global mutable state, a framework may broadcast a value to a subtree using the context API. This preserves the option to wrap a tree of components in a nested context provider without changing the consumers. In practice, this means many React apps have a section near the root that contains all the provider components needed to render the app. Not only is this tedious, but it also means React has to load all the code for those libraries up front, even if the consumers haven't mounted yet.\n+\n+The new context API partially addresses this problem by allowing for a default context value. Any consumer that reads from context but is not wrapped in a provider receives the default value. But there's no mechanism to update that value, so it's insufficient for any type of context that is stateful. The only way to do this today is to wrap the app in a stateful component and pass state to a context provider.\n+\n+## Sharing state across roots\n+\n+Wrapping a tree with a stateful context provider component doesn't address apps that are comprised of multiple roots. Each root needs its own provider. If the data is stored locally in the provider component, then each root will have a separate cache, leading to duplicate requests. If you move the state outside of the providers to an external store, then the providers will need to subscribe to the store's updates.\n+\n+An external store's state is also not managed by React, so it can't be fully compatible with concurrent rendering. Short of full compatibility, even limited compatibility without tearing is difficult to implement correctly without deep knowledge of React's rendering model. An idiomatic API designed for this use case should at the very least make it possible to lift state out of React without causing tearing and without always falling back to synchronous mode.\n+\n+# Detailed design\n+\n+## Motivating examples\n+\n+### Immutable store (like React Redux)\n+\n+Implementing immutable stores is straightfoward. Actions are applied in the render phase using the functional form of `Context.write`.","path":"text/0000-context-write.md","position":62,"original_position":62,"commit_id":"3bd121a3b38cb16dffb6260ce07226f9a82bec9d","original_commit_id":"3bd121a3b38cb16dffb6260ce07226f9a82bec9d","user":{"login":"mfrachet","id":3874873,"node_id":"MDQ6VXNlcjM4NzQ4NzM=","avatar_url":"https://avatars2.githubusercontent.com/u/3874873?v=4","gravatar_id":"","url":"https://api.github.com/users/mfrachet","html_url":"https://github.com/mfrachet","followers_url":"https://api.github.com/users/mfrachet/followers","following_url":"https://api.github.com/users/mfrachet/following{/other_user}","gists_url":"https://api.github.com/users/mfrachet/gists{/gist_id}","starred_url":"https://api.github.com/users/mfrachet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mfrachet/subscriptions","organizations_url":"https://api.github.com/users/mfrachet/orgs","repos_url":"https://api.github.com/users/mfrachet/repos","events_url":"https://api.github.com/users/mfrachet/events{/privacy}","received_events_url":"https://api.github.com/users/mfrachet/received_events","type":"User","site_admin":false},"body":"There's a point I'm misunderstanding. What does it mean concretely that \r\n\r\nActions **are applied in the render phase** using the functional form of `Context.write`?","created_at":"2018-11-22T08:58:26Z","updated_at":"2018-11-22T08:58:26Z","html_url":"https://github.com/reactjs/rfcs/pull/89#discussion_r235643799","pull_request_url":"https://api.github.com/repos/reactjs/rfcs/pulls/89","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/reactjs/rfcs/pulls/comments/235643799"},"html":{"href":"https://github.com/reactjs/rfcs/pull/89#discussion_r235643799"},"pull_request":{"href":"https://api.github.com/repos/reactjs/rfcs/pulls/89"}}},"pull_request":{"url":"https://api.github.com/repos/reactjs/rfcs/pulls/89","id":232192774,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMyMTkyNzc0","html_url":"https://github.com/reactjs/rfcs/pull/89","diff_url":"https://github.com/reactjs/rfcs/pull/89.diff","patch_url":"https://github.com/reactjs/rfcs/pull/89.patch","issue_url":"https://api.github.com/repos/reactjs/rfcs/issues/89","number":89,"state":"open","locked":false,"title":"Context.write","user":{"login":"acdlite","id":3624098,"node_id":"MDQ6VXNlcjM2MjQwOTg=","avatar_url":"https://avatars0.githubusercontent.com/u/3624098?v=4","gravatar_id":"","url":"https://api.github.com/users/acdlite","html_url":"https://github.com/acdlite","followers_url":"https://api.github.com/users/acdlite/followers","following_url":"https://api.github.com/users/acdlite/following{/other_user}","gists_url":"https://api.github.com/users/acdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/acdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/acdlite/subscriptions","organizations_url":"https://api.github.com/users/acdlite/orgs","repos_url":"https://api.github.com/users/acdlite/repos","events_url":"https://api.github.com/users/acdlite/events{/privacy}","received_events_url":"https://api.github.com/users/acdlite/received_events","type":"User","site_admin":false},"body":"***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.***\r\n\r\n## Summary\r\n\r\nProposes 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.\r\n\r\n## Basic example\r\n\r\n```js\r\nconst Context = React.createContext(initialValue, contextDidUpdate);\r\n// Update the global context value across all roots. Any context consumer that\r\n// is not wrapped in a Provider will re-render with this value.\r\nContext.write(newValue);\r\n// Functional updates also supported for access to the previous value.\r\nContext.write(prevValue => newValue);\r\n\r\nfunction contextDidUpdate(newContext) {\r\n  // Optional callback that fires whenever context changes\r\n}\r\n```\r\n\r\n### [Rendered text](https://github.com/acdlite/rfcs/blob/context-write/text/0000-context-write.md)","created_at":"2018-11-20T04:59:27Z","updated_at":"2018-11-22T08:58:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"05b668e2bc9fa78b47fb253d2dc1643ae4ff5c7b","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":771673915,"node_id":"MDU6TGFiZWw3NzE2NzM5MTU=","url":"https://api.github.com/repos/reactjs/rfcs/labels/CLA%20Signed","name":"CLA Signed","color":"ededed","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/reactjs/rfcs/pulls/89/commits","review_comments_url":"https://api.github.com/repos/reactjs/rfcs/pulls/89/comments","review_comment_url":"https://api.github.com/repos/reactjs/rfcs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/reactjs/rfcs/issues/89/comments","statuses_url":"https://api.github.com/repos/reactjs/rfcs/statuses/3bd121a3b38cb16dffb6260ce07226f9a82bec9d","head":{"label":"acdlite:context-write","ref":"context-write","sha":"3bd121a3b38cb16dffb6260ce07226f9a82bec9d","user":{"login":"acdlite","id":3624098,"node_id":"MDQ6VXNlcjM2MjQwOTg=","avatar_url":"https://avatars0.githubusercontent.com/u/3624098?v=4","gravatar_id":"","url":"https://api.github.com/users/acdlite","html_url":"https://github.com/acdlite","followers_url":"https://api.github.com/users/acdlite/followers","following_url":"https://api.github.com/users/acdlite/following{/other_user}","gists_url":"https://api.github.com/users/acdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/acdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/acdlite/subscriptions","organizations_url":"https://api.github.com/users/acdlite/orgs","repos_url":"https://api.github.com/users/acdlite/repos","events_url":"https://api.github.com/users/acdlite/events{/privacy}","received_events_url":"https://api.github.com/users/acdlite/received_events","type":"User","site_admin":false},"repo":{"id":113368420,"node_id":"MDEwOlJlcG9zaXRvcnkxMTMzNjg0MjA=","name":"rfcs","full_name":"acdlite/rfcs","private":false,"owner":{"login":"acdlite","id":3624098,"node_id":"MDQ6VXNlcjM2MjQwOTg=","avatar_url":"https://avatars0.githubusercontent.com/u/3624098?v=4","gravatar_id":"","url":"https://api.github.com/users/acdlite","html_url":"https://github.com/acdlite","followers_url":"https://api.github.com/users/acdlite/followers","following_url":"https://api.github.com/users/acdlite/following{/other_user}","gists_url":"https://api.github.com/users/acdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/acdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/acdlite/subscriptions","organizations_url":"https://api.github.com/users/acdlite/orgs","repos_url":"https://api.github.com/users/acdlite/repos","events_url":"https://api.github.com/users/acdlite/events{/privacy}","received_events_url":"https://api.github.com/users/acdlite/received_events","type":"User","site_admin":false},"html_url":"https://github.com/acdlite/rfcs","description":"RFCs for changes to React","fork":true,"url":"https://api.github.com/repos/acdlite/rfcs","forks_url":"https://api.github.com/repos/acdlite/rfcs/forks","keys_url":"https://api.github.com/repos/acdlite/rfcs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/acdlite/rfcs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/acdlite/rfcs/teams","hooks_url":"https://api.github.com/repos/acdlite/rfcs/hooks","issue_events_url":"https://api.github.com/repos/acdlite/rfcs/issues/events{/number}","events_url":"https://api.github.com/repos/acdlite/rfcs/events","assignees_url":"https://api.github.com/repos/acdlite/rfcs/assignees{/user}","branches_url":"https://api.github.com/repos/acdlite/rfcs/branches{/branch}","tags_url":"https://api.github.com/repos/acdlite/rfcs/tags","blobs_url":"https://api.github.com/repos/acdlite/rfcs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/acdlite/rfcs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/acdlite/rfcs/git/refs{/sha}","trees_url":"https://api.github.com/repos/acdlite/rfcs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/acdlite/rfcs/statuses/{sha}","languages_url":"https://api.github.com/repos/acdlite/rfcs/languages","stargazers_url":"https://api.github.com/repos/acdlite/rfcs/stargazers","contributors_url":"https://api.github.com/repos/acdlite/rfcs/contributors","subscribers_url":"https://api.github.com/repos/acdlite/rfcs/subscribers","subscription_url":"https://api.github.com/repos/acdlite/rfcs/subscription","commits_url":"https://api.github.com/repos/acdlite/rfcs/commits{/sha}","git_commits_url":"https://api.github.com/repos/acdlite/rfcs/git/commits{/sha}","comments_url":"https://api.github.com/repos/acdlite/rfcs/comments{/number}","issue_comment_url":"https://api.github.com/repos/acdlite/rfcs/issues/comments{/number}","contents_url":"https://api.github.com/repos/acdlite/rfcs/contents/{+path}","compare_url":"https://api.github.com/repos/acdlite/rfcs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/acdlite/rfcs/merges","archive_url":"https://api.github.com/repos/acdlite/rfcs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/acdlite/rfcs/downloads","issues_url":"https://api.github.com/repos/acdlite/rfcs/issues{/number}","pulls_url":"https://api.github.com/repos/acdlite/rfcs/pulls{/number}","milestones_url":"https://api.github.com/repos/acdlite/rfcs/milestones{/number}","notifications_url":"https://api.github.com/repos/acdlite/rfcs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/acdlite/rfcs/labels{/name}","releases_url":"https://api.github.com/repos/acdlite/rfcs/releases{/id}","deployments_url":"https://api.github.com/repos/acdlite/rfcs/deployments","created_at":"2017-12-06T21:19:59Z","updated_at":"2018-04-24T12:12:15Z","pushed_at":"2018-11-21T00:37:19Z","git_url":"git://github.com/acdlite/rfcs.git","ssh_url":"git@github.com:acdlite/rfcs.git","clone_url":"https://github.com/acdlite/rfcs.git","svn_url":"https://github.com/acdlite/rfcs","homepage":null,"size":86,"stargazers_count":34,"watchers_count":34,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":6,"open_issues":0,"watchers":34,"default_branch":"master"}},"base":{"label":"reactjs:master","ref":"master","sha":"02eca7528613e66f29a4a105ad525c2ced6edcc5","user":{"login":"reactjs","id":6412038,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0MTIwMzg=","avatar_url":"https://avatars3.githubusercontent.com/u/6412038?v=4","gravatar_id":"","url":"https://api.github.com/users/reactjs","html_url":"https://github.com/reactjs","followers_url":"https://api.github.com/users/reactjs/followers","following_url":"https://api.github.com/users/reactjs/following{/other_user}","gists_url":"https://api.github.com/users/reactjs/gists{/gist_id}","starred_url":"https://api.github.com/users/reactjs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/reactjs/subscriptions","organizations_url":"https://api.github.com/users/reactjs/orgs","repos_url":"https://api.github.com/users/reactjs/repos","events_url":"https://api.github.com/users/reactjs/events{/privacy}","received_events_url":"https://api.github.com/users/reactjs/received_events","type":"Organization","site_admin":false},"repo":{"id":113273337,"node_id":"MDEwOlJlcG9zaXRvcnkxMTMyNzMzMzc=","name":"rfcs","full_name":"reactjs/rfcs","private":false,"owner":{"login":"reactjs","id":6412038,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0MTIwMzg=","avatar_url":"https://avatars3.githubusercontent.com/u/6412038?v=4","gravatar_id":"","url":"https://api.github.com/users/reactjs","html_url":"https://github.com/reactjs","followers_url":"https://api.github.com/users/reactjs/followers","following_url":"https://api.github.com/users/reactjs/following{/other_user}","gists_url":"https://api.github.com/users/reactjs/gists{/gist_id}","starred_url":"https://api.github.com/users/reactjs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/reactjs/subscriptions","organizations_url":"https://api.github.com/users/reactjs/orgs","repos_url":"https://api.github.com/users/reactjs/repos","events_url":"https://api.github.com/users/reactjs/events{/privacy}","received_events_url":"https://api.github.com/users/reactjs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/reactjs/rfcs","description":"RFCs for changes to React","fork":false,"url":"https://api.github.com/repos/reactjs/rfcs","forks_url":"https://api.github.com/repos/reactjs/rfcs/forks","keys_url":"https://api.github.com/repos/reactjs/rfcs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/reactjs/rfcs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/reactjs/rfcs/teams","hooks_url":"https://api.github.com/repos/reactjs/rfcs/hooks","issue_events_url":"https://api.github.com/repos/reactjs/rfcs/issues/events{/number}","events_url":"https://api.github.com/repos/reactjs/rfcs/events","assignees_url":"https://api.github.com/repos/reactjs/rfcs/assignees{/user}","branches_url":"https://api.github.com/repos/reactjs/rfcs/branches{/branch}","tags_url":"https://api.github.com/repos/reactjs/rfcs/tags","blobs_url":"https://api.github.com/repos/reactjs/rfcs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/reactjs/rfcs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/reactjs/rfcs/git/refs{/sha}","trees_url":"https://api.github.com/repos/reactjs/rfcs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/reactjs/rfcs/statuses/{sha}","languages_url":"https://api.github.com/repos/reactjs/rfcs/languages","stargazers_url":"https://api.github.com/repos/reactjs/rfcs/stargazers","contributors_url":"https://api.github.com/repos/reactjs/rfcs/contributors","subscribers_url":"https://api.github.com/repos/reactjs/rfcs/subscribers","subscription_url":"https://api.github.com/repos/reactjs/rfcs/subscription","commits_url":"https://api.github.com/repos/reactjs/rfcs/commits{/sha}","git_commits_url":"https://api.github.com/repos/reactjs/rfcs/git/commits{/sha}","comments_url":"https://api.github.com/repos/reactjs/rfcs/comments{/number}","issue_comment_url":"https://api.github.com/repos/reactjs/rfcs/issues/comments{/number}","contents_url":"https://api.github.com/repos/reactjs/rfcs/contents/{+path}","compare_url":"https://api.github.com/repos/reactjs/rfcs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/reactjs/rfcs/merges","archive_url":"https://api.github.com/repos/reactjs/rfcs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/reactjs/rfcs/downloads","issues_url":"https://api.github.com/repos/reactjs/rfcs/issues{/number}","pulls_url":"https://api.github.com/repos/reactjs/rfcs/pulls{/number}","milestones_url":"https://api.github.com/repos/reactjs/rfcs/milestones{/number}","notifications_url":"https://api.github.com/repos/reactjs/rfcs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/reactjs/rfcs/labels{/name}","releases_url":"https://api.github.com/repos/reactjs/rfcs/releases{/id}","deployments_url":"https://api.github.com/repos/reactjs/rfcs/deployments","created_at":"2017-12-06T05:32:51Z","updated_at":"2018-11-22T06:59:41Z","pushed_at":"2018-11-21T20:20:56Z","git_url":"git://github.com/reactjs/rfcs.git","ssh_url":"git@github.com:reactjs/rfcs.git","clone_url":"https://github.com/reactjs/rfcs.git","svn_url":"https://github.com/reactjs/rfcs","homepage":null,"size":102,"stargazers_count":1950,"watchers_count":1950,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":142,"mirror_url":null,"archived":false,"open_issues_count":41,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":142,"open_issues":41,"watchers":1950,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/reactjs/rfcs/pulls/89"},"html":{"href":"https://github.com/reactjs/rfcs/pull/89"},"issue":{"href":"https://api.github.com/repos/reactjs/rfcs/issues/89"},"comments":{"href":"https://api.github.com/repos/reactjs/rfcs/issues/89/comments"},"review_comments":{"href":"https://api.github.com/repos/reactjs/rfcs/pulls/89/comments"},"review_comment":{"href":"https://api.github.com/repos/reactjs/rfcs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/reactjs/rfcs/pulls/89/commits"},"statuses":{"href":"https://api.github.com/repos/reactjs/rfcs/statuses/3bd121a3b38cb16dffb6260ce07226f9a82bec9d"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"mfrachet"}}