@@ -102,76 +102,92 @@ long as the code adheres to core Redux principles:
102
102
103
103
### Context
104
104
105
- As long as you use the [ current Context
105
+ We're on board with the current API where possible; there's a
106
+ third-party library we use that isn't there yet.
107
+
108
+ #### Current Context API
109
+
110
+ We should use the [ current Context
106
111
API] ( https://reactjs.org/docs/context.html ) instead of the [ legacy
107
- one] ( https://reactjs.org/docs/legacy-context.html ) , there's not much
108
- to worry about here. From the doc for the current one:
112
+ one] ( https://reactjs.org/docs/legacy-context.html ) wherever possible.
113
+ The new API aggressively ensures consumers will be updated
114
+ (re-` render ` ed) on context changes, and the old one doesn't (see
115
+ below). From the [ new API's
116
+ doc] ( https://reactjs.org/docs/context.html ) :
109
117
110
118
> All consumers that are descendants of a Provider will re-render
111
119
> whenever the Provider’s ` value ` prop changes.
112
120
113
- Then, anticipating the question "What about ` shouldComponentUpdate ` ?"
114
- (and therefore, "What about ` PureComponent ` s?", because
115
- ` PureComponents `
116
- [ implement] ( https://reactjs.org/docs/react-api.html#reactpurecomponent )
117
- ` shouldComponentUpdate ` ):
121
+ It's so aggressive that there's a potential "gotcha" with the new API:
122
+ context consumers are the first occurrence of the following that we're
123
+ aware of (from the [ doc on
124
+ ` shouldComponentUpdate ` ] ( https://reactjs.org/docs/react-component.html#shouldcomponentupdate ) ):
125
+
126
+ > In the future React may treat ` shouldComponentUpdate() ` as a hint
127
+ > rather than a strict directive, and returning ` false ` may still
128
+ > result in a re-rendering of the component.
129
+
130
+ We gather this from the following (in the [ new API's
131
+ doc] ( https://reactjs.org/docs/context.html ) ):
118
132
119
133
> The propagation from Provider to its descendant consumers (including
120
134
> [ ` .contextType ` ] ( https://reactjs.org/docs/context.html#classcontexttype )
121
- > and
122
- > [ ` useContext ` ] ( https://reactjs.org/docs/hooks-reference.html#usecontext ) )
123
- > is not subject to the shouldComponentUpdate method, so the consumer is
124
- > updated even when an ancestor component skips an update.
125
-
126
- (I think word "even" means to emphasize "ancestor" -- i.e., it's not
127
- only the case that a consumer's * own* ` shouldComponentUpdate ` is
128
- ignored, but that, remarkably, so is the ` shouldComponentUpdate ` of
129
- ancestor components. I think it's just as remarkable that a
130
- component's own ` shouldComponentUpdate ` is ignored: this sounds like a
131
- clear instance of the
132
- [ following] ( https://reactjs.org/docs/react-component.html#shouldcomponentupdate ) :
133
- "In the future React may treat ` shouldComponentUpdate() ` as a hint
134
- rather than a strict directive, and returning ` false ` may still result
135
- in a re-rendering of the component.")
135
+ > [ ...] )
136
+ > is not subject to the shouldComponentUpdate method
137
+
138
+ Concretely, this means that our ` MessageList ` component updates
139
+ (re-` render ` s) when the theme changes, since it's a ` ThemeContext `
140
+ consumer, * even though its ` shouldComponentUpdate ` always returns
141
+ ` false ` * . So far, this hasn't been a problem because the UI doesn't
142
+ allow changing the theme while a ` MessageList ` is in the navigation
143
+ stack. If it were possible, it would be a concern: setting a short
144
+ interval to automatically toggle the theme, we see that the message
145
+ list's color scheme changes as we'd want it to, but we also see the
146
+ effects that ` shouldComponentUpdate ` returning ` false ` is meant to
147
+ prevent: losing the scroll position, mainly (but also, we expect,
148
+ discarding the image cache, etc.).
149
+
150
+ #### Legacy Context API
136
151
137
152
The legacy Context API is
138
153
[ declared] ( https://reactjs.org/docs/legacy-context.html#updating-context )
139
- fundamentally broken because ` shouldComponentUpdate ` and
140
- ` PureComponent ` s can block updates.
141
-
142
- We're forced to reckon with the legacy Context API in one place: the
143
- ` react-intl ` library uses it for the ` intl ` context. We "translate"
144
- the old API to the new one ASAP, in ` TranslationContextTranslator ` , so
145
- we can get used to using Context in the new way. But we've made
146
- ` TranslationContextTranslator ` a ` PureComponent ` , exposing ourselves
147
- to the legacy API's flaw. So we have to use a "` key ` hack", which is
148
- the following:
149
-
150
- If ` locale ` changes, we make the entire React component tree at and
151
- below ` IntlProvider ` remount. (Not merely re-` render ` : completely
152
- vanish and start over with ` componentDidMount ` ; see the note at [ this
153
- doc] ( https://5d4b5feba32acd0008d0df98--reactjs.netlify.app/docs/reconciliation.html )
154
- starting with "Keys should be stable, predictable, and unique".) We do
155
- this with the [ ` key `
156
- attribute] ( https://reactjs.org/docs/lists-and-keys.html ) , which isn't
157
- recommended for use except in lists.
158
-
159
- Previously, we used the same ` key ` hack for our own styles data, to
160
- get changes in the theme to propagate. No longer! :)
154
+ fundamentally broken because consumers could be blocked from receiving
155
+ updates to the context, and not just by the consumer's own
156
+ ` shouldComponentUpdate ` :
157
+
158
+ > The problem is, if a context value provided by component changes,
159
+ > descendants that use that value won’t update if an intermediate
160
+ > parent returns ` false ` from ` shouldComponentUpdate ` . This is totally
161
+ > out of control of the components using context, so there’s basically
162
+ > no way to reliably update the context.
163
+
164
+ We have to think about the legacy Context API in just one place. The
165
+ ` react-intl ` library's ` IntlProvider ` uses it to provide the ` intl `
166
+ context. The only consumer of ` intl ` is
167
+ ` TranslationContextTranslator ` , which "speaks" the old API by being
168
+ the direct child of ` IntlProvider ` and by being a ` Component ` , not a
169
+ ` PureComponent ` (whose under-the-hood ` shouldComponentUpdate ` would
170
+ suppress updates on context changes)—all to make sure that it
171
+ re-` render ` s whenever ` intl ` changes. Then,
172
+ ` TranslationContextTranslator ` is itself a provider, and it provides
173
+ the same value, but it does so in the new Context API way. All its
174
+ consumers are updated appropriately, which is what we want.
161
175
162
176
### The exception: ` MessageList `
163
177
164
- We have one React component that we wrote (beyond ` connect ` calls) that
165
- deviates from the above design: ` MessageList ` . This is the only
166
- component that extends plain ` Component ` rather than ` PureComponent ` , and
167
- the only component that implements ` shouldComponentUpdate ` .
178
+ We have one React component that we wrote (beyond ` connect ` calls)
179
+ that deviates from the above design: ` MessageList ` . It extends plain
180
+ ` Component ` rather than ` PureComponent ` , and it's the only component
181
+ in which we implement ` shouldComponentUpdate ` .
168
182
169
183
In fact, ` MessageList ` does adhere to the Pure Component Principle -- its
170
184
` render ` method is a pure function of ` this.props ` and ` this.context ` . So
171
185
it could use ` PureComponent ` , but it doesn't -- instead we have a
172
186
` shouldComponentUpdate ` that always returns ` false ` , so even when ` props `
173
187
change quite materially (e.g., a new Zulip message arrives which should be
174
- displayed) we don't have React re-render the component.
188
+ displayed) we don't have React re-render the component. (See the note
189
+ on the current Context API, above, for a known case where our
190
+ ` shouldComponentUpdate ` is ignored.)
175
191
176
192
The specifics of why not, and what we do instead, deserve an architecture
177
193
doc of their own. In brief: ` render ` returns a single React element, a
0 commit comments