-
-
Notifications
You must be signed in to change notification settings - Fork 676
Stop using legacy Context API for styles (continued) #4199
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
Changes from all commits
8b30007
5835d06
53b4c0a
1a12293
855de67
68ca746
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,38 +102,93 @@ long as the code adheres to core Redux principles: | |
|
|
||
| ### Context | ||
|
|
||
| Our "Pure Component Principle" says `render` is a pure function of props, | ||
| state, *and context*. But `PureComponent` only checks for changes to props | ||
| and state, and skips re-render when just those two are unchanged. Doesn't | ||
| that open up bugs if just `this.context` changes? | ||
|
|
||
| [Yes, it | ||
| would](https://reactjs.org/docs/legacy-context.html#updating-context). For | ||
| this reason, when something provided in context is updated, we force the | ||
| entire React component tree under that point (in our usage of `context`, | ||
| this is nearly the entire tree) to re-render. This means we use `context` | ||
| sparingly -- only for things where its benefit for code readability is very | ||
| large, and where updates are rare so we're OK with that global re-render. | ||
|
|
||
| In `StylesProvider`, for example, this is done with a `key`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so s/Styles/Translation/ -- and maybe replace "for example" with a mention that this is now the one place we do this 🙂 |
||
|
|
||
| Relatedly, the `this.context` API we use is a legacy API. Recent React | ||
| versions offer a [new context API](https://reactjs.org/docs/context.html) | ||
| that works much more like Redux and `connect`, above. | ||
| We're on board with the current API where possible; there's a | ||
| third-party library we use that isn't there yet, but we deal with that | ||
| at the edge by "translating" the old API to the new one. | ||
|
|
||
| #### Current Context API | ||
|
|
||
| We should use the [current Context | ||
| API](https://reactjs.org/docs/context.html) instead of the [legacy | ||
| one](https://reactjs.org/docs/legacy-context.html) wherever possible. | ||
| The new API aggressively ensures consumers will be updated | ||
| (re-`render`ed) on context changes, and the old one doesn't (see | ||
| below). From the [new API's | ||
| doc](https://reactjs.org/docs/context.html): | ||
|
|
||
| > All consumers that are descendants of a Provider will re-render | ||
| > whenever the Provider’s `value` prop changes. | ||
|
|
||
| It's so aggressive that there's a potential "gotcha" with the new API: | ||
| context consumers are the first occurrence of the following that we're | ||
| aware of (from the [doc on | ||
| `shouldComponentUpdate`](https://reactjs.org/docs/react-component.html#shouldcomponentupdate)): | ||
|
|
||
| > In the future React may treat `shouldComponentUpdate()` as a hint | ||
| > rather than a strict directive, and returning `false` may still | ||
| > result in a re-rendering of the component. | ||
|
|
||
| We gather this from the following (in the [new API's | ||
| doc](https://reactjs.org/docs/context.html)): | ||
|
|
||
| > The propagation from Provider to its descendant consumers (including | ||
| > [`.contextType`](https://reactjs.org/docs/context.html#classcontexttype) | ||
| > [...]) | ||
| > is not subject to the shouldComponentUpdate method | ||
|
|
||
| Concretely, this means that our `MessageList` component updates | ||
| (re-`render`s) when the theme changes, since it's a `ThemeContext` | ||
| consumer, *even though its `shouldComponentUpdate` always returns | ||
| `false`*. So far, this hasn't been a problem because the UI doesn't | ||
| allow changing the theme while a `MessageList` is in the navigation | ||
| stack. If it were possible, it would be a concern: setting a short | ||
| interval to automatically toggle the theme, we see that the message | ||
| list's color scheme changes as we'd want it to, but we also see the | ||
| bad effects that `shouldComponentUpdate` returning `false` is meant to | ||
| prevent: losing the scroll position, mainly (but also, we expect, | ||
| discarding the image cache, etc.). | ||
|
Comment on lines
+144
to
+149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh interesting. That seems like a bug in the implementation of |
||
|
|
||
| #### Legacy Context API | ||
|
|
||
| The legacy Context API is | ||
| [declared](https://reactjs.org/docs/legacy-context.html#updating-context) | ||
| fundamentally broken because consumers could be blocked from receiving | ||
| updates to the context, and not just by the consumer's own | ||
| `shouldComponentUpdate`: | ||
|
|
||
| > The problem is, if a context value provided by component changes, | ||
| > descendants that use that value won’t update if an intermediate | ||
| > parent returns `false` from `shouldComponentUpdate`. This is totally | ||
| > out of control of the components using context, so there’s basically | ||
| > no way to reliably update the context. | ||
|
|
||
| We have to think about the legacy Context API in just one place. The | ||
| `react-intl` library's `IntlProvider` uses it to provide the `intl` | ||
| context. The only consumer of `intl` is | ||
| `TranslationContextTranslator`, which "speaks" the old API by being | ||
| the direct child of `IntlProvider` and by being a `Component`, not a | ||
| `PureComponent` (whose under-the-hood `shouldComponentUpdate` would | ||
| suppress updates on context changes)—all to make sure that it | ||
| re-`render`s whenever `intl` changes. Then, | ||
| `TranslationContextTranslator` is itself a provider, and it provides | ||
| the same value, but it does so in the new Context API way. All its | ||
| consumers are updated appropriately, which is what we want. | ||
|
|
||
| ### The exception: `MessageList` | ||
|
|
||
| We have one React component that we wrote (beyond `connect` calls) that | ||
| deviates from the above design: `MessageList`. This is the only | ||
| component that extends plain `Component` rather than `PureComponent`, and | ||
| the only component that implements `shouldComponentUpdate`. | ||
| We have one React component that we wrote (beyond `connect` calls) | ||
| that deviates from the above design: `MessageList`. It extends plain | ||
| `Component` rather than `PureComponent`, and it's the only component | ||
| in which we implement `shouldComponentUpdate`. | ||
|
|
||
| In fact, `MessageList` does adhere to the Pure Component Principle -- its | ||
| `render` method is a pure function of `this.props` and `this.context`. So | ||
| it could use `PureComponent`, but it doesn't -- instead we have a | ||
| `shouldComponentUpdate` that always returns `false`, so even when `props` | ||
| change quite materially (e.g., a new Zulip message arrives which should be | ||
| displayed) we don't have React re-render the component. | ||
| displayed) we don't have React re-render the component. (See the note | ||
| on the current Context API, above, for a known case where our | ||
| `shouldComponentUpdate` is ignored.) | ||
|
|
||
| The specifics of why not, and what we do instead, deserve an architecture | ||
| doc of their own. In brief: `render` returns a single React element, a | ||
|
|
||
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 docs discussion sadly can't quite be deleted yet -- grepping for
contextTypes, there's one place we still use it, namely to consume theintlcontext provided by the react-intl library. (In ourTranslationContextTranslatorcomponent, which exists to... "translate" that legacy context into the new context API.)And for that reason we do still use the
keyhack inTranslationProvider.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.
Ah, right—I wonder if I wasn't thinking of this way back when I first created this PR. Anyway, do you think this means I should remove the marker that the PR fixes #1946?