main
js 370 lines 9.8 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import * as React from 'react';
11 import {createContext, Component, useContext, useState} from 'react';
12 import PropTypes from 'prop-types';
13
14 import type {ReactContext} from 'shared/ReactTypes';
15
16 function someNamedFunction() {}
17
18 function formatContextForDisplay(name: string, value: any | string) {
19 return (
20 <li>
21 {name}: <pre>{JSON.stringify(value, null, 2)}</pre>
22 </li>
23 );
24 }
25
26 const contextData = {
27 array: ['first', 'second', 'third'],
28 bool: true,
29 func: someNamedFunction,
30 number: 123,
31 object: {outer: {inner: {} as {...}}},
32 string: 'abc',
33 symbol: Symbol.for('symbol'),
34 null: null,
35 undefined: undefined,
36 };
37
38 class LegacyContextProvider extends Component<any> {
39 static childContextTypes: {
40 array: any,
41 bool: any,
42 func: any,
43 null: any,
44 number: any,
45 object: any,
46 string: any,
47 symbol: any,
48 undefined: any,
49 } = {
50 array: PropTypes.array,
51 bool: PropTypes.bool,
52 func: PropTypes.func,
53 number: PropTypes.number,
54 object: PropTypes.object,
55 string: PropTypes.string,
56 symbol: PropTypes.symbol,
57 null: PropTypes.any,
58 undefined: PropTypes.any,
59 };
60
61 getChildContext(): {
62 array: Array<string>,
63 bool: boolean,
64 func: () => void,
65 null: null,
66 number: number,
67 object: {outer: {inner: {...}}},
68 string: string,
69 symbol: symbol,
70 undefined: void,
71 } {
72 return contextData;
73 }
74
75 render(): any {
76 return this.props.children;
77 }
78 }
79
80 class LegacyContextConsumer extends Component<any> {
81 static contextTypes: {
82 array: any,
83 bool: any,
84 func: any,
85 null: any,
86 number: any,
87 object: any,
88 string: any,
89 symbol: any,
90 undefined: any,
91 } = {
92 array: PropTypes.array,
93 bool: PropTypes.bool,
94 func: PropTypes.func,
95 number: PropTypes.number,
96 object: PropTypes.object,
97 string: PropTypes.string,
98 symbol: PropTypes.symbol,
99 null: PropTypes.any,
100 undefined: PropTypes.any,
101 };
102
103 render(): any {
104 return formatContextForDisplay('LegacyContextConsumer', this.context);
105 }
106 }
107
108 class LegacyContextProviderWithUpdates extends Component<any> {
109 constructor(props: any) {
110 super(props);
111 this.state = {type: 'desktop'};
112 }
113
114 getChildContext(): {type: any} {
115 return {type: this.state.type};
116 }
117
118 // $FlowFixMe[missing-local-annot]
119 handleChange = event => {
120 this.setState({type: event.target.value});
121 };
122
123 render(): any {
124 return (
125 <>
126 <LegacyFunctionalContextConsumer />
127 <div>
128 <input value={this.state.type} onChange={this.handleChange} />
129 </div>
130 </>
131 );
132 }
133 }
134
135 LegacyContextProviderWithUpdates.childContextTypes = {
136 type: PropTypes.string,
137 };
138
139 // $FlowFixMe[missing-local-annot]
140 function LegacyFunctionalContextConsumer(props: any, context) {
141 return formatContextForDisplay('LegacyFunctionContextConsumer', context.type);
142 }
143 LegacyFunctionalContextConsumer.contextTypes = {
144 type: PropTypes.string,
145 };
146
147 const ModernContext = createContext();
148 ModernContext.displayName = 'ModernContext';
149 const ArrayContext = createContext(contextData.array);
150 ArrayContext.displayName = 'ArrayContext';
151 const BoolContext = createContext(contextData.bool);
152 BoolContext.displayName = 'BoolContext';
153 const FuncContext = createContext(contextData.func);
154 FuncContext.displayName = 'FuncContext';
155 const NumberContext = createContext(contextData.number);
156 NumberContext.displayName = 'NumberContext';
157 const StringContext = createContext(contextData.string);
158 StringContext.displayName = 'StringContext';
159 const SymbolContext = createContext(contextData.symbol);
160 SymbolContext.displayName = 'SymbolContext';
161 const NullContext = createContext(null);
162 NullContext.displayName = 'NullContext';
163 const UndefinedContext = createContext(undefined);
164 UndefinedContext.displayName = 'UndefinedContext';
165
166 class ModernContextType extends Component<any> {
167 static contextType: ReactContext<void> = ModernContext;
168
169 render(): any {
170 return formatContextForDisplay('ModernContextType', this.context);
171 }
172 }
173
174 function FunctionalContextConsumer() {
175 const value = useContext(StringContext);
176 return formatContextForDisplay('FunctionalContextConsumer', value);
177 }
178
179 const StringContextWithUpdates = createContext({
180 string: contextData.string,
181 setString: (string: string) => {},
182 });
183 const StringContextWithUpdates2 = createContext({
184 string2: contextData.string,
185 setString2: (string: string) => {},
186 });
187
188 function FunctionalContextProviderWithContextUpdates() {
189 const [string, setString] = useState(contextData.string);
190 const [string2, setString2] = useState(contextData.string);
191 const value = {string, setString};
192 const value2 = {string2, setString2};
193
194 return (
195 <StringContextWithUpdates.Provider value={value}>
196 <StringContextWithUpdates2.Provider value={value2}>
197 <FunctionalContextConsumerWithContextUpdates />
198 </StringContextWithUpdates2.Provider>
199 </StringContextWithUpdates.Provider>
200 );
201 }
202
203 function FunctionalContextConsumerWithContextUpdates() {
204 const {string, setString} = useContext(StringContextWithUpdates);
205 const {string2, setString2} = useContext(StringContextWithUpdates2);
206 const [state, setState] = useState('state');
207
208 // $FlowFixMe[missing-local-annot]
209 const handleChange = e => setString(e.target.value);
210 // $FlowFixMe[missing-local-annot]
211 const handleChange2 = e => setString2(e.target.value);
212
213 return (
214 <>
215 {formatContextForDisplay(
216 'FunctionalContextConsumerWithUpdates',
217 `context: ${string}, context 2: ${string2}`,
218 )}
219 <div>
220 context: <input value={string} onChange={handleChange} />
221 </div>
222 <div>
223 context 2: <input value={string2} onChange={handleChange2} />
224 </div>
225 <div>
226 {state}
227 <div>
228 test state:{' '}
229 <input value={state} onChange={e => setState(e.target.value)} />
230 </div>
231 </div>
232 </>
233 );
234 }
235
236 class ModernClassContextProviderWithUpdates extends Component<any> {
237 constructor(props: any) {
238 super(props);
239 this.setString = string => {
240 this.setState({string});
241 };
242
243 this.state = {
244 string: contextData.string,
245 setString: this.setString,
246 };
247 }
248
249 render(): any {
250 return (
251 <StringContextWithUpdates.Provider value={this.state}>
252 <ModernClassContextConsumerWithUpdates />
253 </StringContextWithUpdates.Provider>
254 );
255 }
256 }
257
258 class ModernClassContextConsumerWithUpdates extends Component<any> {
259 render(): any {
260 return (
261 <StringContextWithUpdates.Consumer>
262 {({string, setString}: {string: string, setString: string => void}) => (
263 <>
264 {formatContextForDisplay(
265 'ModernClassContextConsumerWithUpdates',
266 string,
267 )}
268 <input value={string} onChange={e => setString(e.target.value)} />
269 </>
270 )}
271 </StringContextWithUpdates.Consumer>
272 );
273 }
274 }
275
276 type LegacyContextState = {
277 supportsLegacyContext: boolean,
278 };
279 class LegacyContext extends React.Component {
280 state: LegacyContextState = {supportsLegacyContext: true};
281
282 static getDerivedStateFromError(error: any): LegacyContextState {
283 return {supportsLegacyContext: false};
284 }
285
286 componentDidCatch(error: any, info: any) {
287 console.info(
288 'Assuming legacy context is not supported in this React version due to: ',
289 error,
290 info,
291 );
292 }
293
294 render(): React.Node {
295 if (!this.state.supportsLegacyContext) {
296 return <p>This version of React does not support legacy context.</p>;
297 }
298
299 return (
300 <React.Fragment>
301 <LegacyContextProvider>
302 <LegacyContextConsumer />
303 </LegacyContextProvider>
304 <LegacyContextProviderWithUpdates />
305 </React.Fragment>
306 );
307 }
308 }
309
310 export default function Contexts(): React.Node {
311 return (
312 <div>
313 <h1>Contexts</h1>
314 <ul>
315 <LegacyContext />
316 <ModernContext.Provider value={contextData}>
317 <ModernContext.Consumer>
318 {(value: $FlowFixMe) =>
319 formatContextForDisplay('ModernContext.Consumer', value)
320 }
321 </ModernContext.Consumer>
322 <ModernContextType />
323 </ModernContext.Provider>
324 <FunctionalContextConsumer />
325 <FunctionalContextProviderWithContextUpdates />
326 <ModernClassContextProviderWithUpdates />
327 <ArrayContext.Consumer>
328 {(value: $FlowFixMe) =>
329 formatContextForDisplay('ArrayContext.Consumer', value)
330 }
331 </ArrayContext.Consumer>
332 <BoolContext.Consumer>
333 {(value: $FlowFixMe) =>
334 formatContextForDisplay('BoolContext.Consumer', value)
335 }
336 </BoolContext.Consumer>
337 <FuncContext.Consumer>
338 {(value: $FlowFixMe) =>
339 formatContextForDisplay('FuncContext.Consumer', value)
340 }
341 </FuncContext.Consumer>
342 <NumberContext.Consumer>
343 {(value: $FlowFixMe) =>
344 formatContextForDisplay('NumberContext.Consumer', value)
345 }
346 </NumberContext.Consumer>
347 <StringContext.Consumer>
348 {(value: $FlowFixMe) =>
349 formatContextForDisplay('StringContext.Consumer', value)
350 }
351 </StringContext.Consumer>
352 <SymbolContext.Consumer>
353 {(value: $FlowFixMe) =>
354 formatContextForDisplay('SymbolContext.Consumer', value)
355 }
356 </SymbolContext.Consumer>
357 <NullContext.Consumer>
358 {(value: $FlowFixMe) =>
359 formatContextForDisplay('NullContext.Consumer', value)
360 }
361 </NullContext.Consumer>
362 <UndefinedContext.Consumer>
363 {(value: $FlowFixMe) =>
364 formatContextForDisplay('UndefinedContext.Consumer', value)
365 }
366 </UndefinedContext.Consumer>
367 </ul>
368 </div>
369 );
370 }