@samitouri / QOS-React-2 / commits / cf45a623a1

[Fizz] Implement Classes (#21200)

* Legacy context * Port Classes from Fiber to Fizz

Sebastian Markbåge committed Apr 8, 2021 at 13:42 UTC cf45a623a1787cf57094eb7d2f66903d358647fa
3 files changed +1042 -13
packages/react-server/src/ReactFizzClassComponent.js new
+673
@@ -0,0 +1,673 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 {emptyContextObject} from './ReactFizzContext';
11 +
12 +import {disableLegacyContext} from 'shared/ReactFeatureFlags';
13 +import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
14 +import getComponentNameFromType from 'shared/getComponentNameFromType';
15 +import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
16 +import isArray from 'shared/isArray';
17 +
18 +const didWarnAboutNoopUpdateForComponent = {};
19 +
20 +let didWarnAboutUninitializedState;
21 +let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
22 +let didWarnAboutLegacyLifecyclesAndDerivedState;
23 +let didWarnAboutUndefinedDerivedState;
24 +let warnOnUndefinedDerivedState;
25 +let warnOnInvalidCallback;
26 +let didWarnAboutDirectlyAssigningPropsToState;
27 +let didWarnAboutContextTypeAndContextTypes;
28 +let didWarnAboutInvalidateContextType;
29 +
30 +if (__DEV__) {
31 + didWarnAboutUninitializedState = new Set();
32 + didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
33 + didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
34 + didWarnAboutDirectlyAssigningPropsToState = new Set();
35 + didWarnAboutUndefinedDerivedState = new Set();
36 + didWarnAboutContextTypeAndContextTypes = new Set();
37 + didWarnAboutInvalidateContextType = new Set();
38 +
39 + const didWarnOnInvalidCallback = new Set();
40 +
41 + warnOnInvalidCallback = function(callback: mixed, callerName: string) {
42 + if (callback === null || typeof callback === 'function') {
43 + return;
44 + }
45 + const key = callerName + '_' + (callback: any);
46 + if (!didWarnOnInvalidCallback.has(key)) {
47 + didWarnOnInvalidCallback.add(key);
48 + console.error(
49 + '%s(...): Expected the last optional `callback` argument to be a ' +
50 + 'function. Instead received: %s.',
51 + callerName,
52 + callback,
53 + );
54 + }
55 + };
56 +
57 + warnOnUndefinedDerivedState = function(type, partialState) {
58 + if (partialState === undefined) {
59 + const componentName = getComponentNameFromType(type) || 'Component';
60 + if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
61 + didWarnAboutUndefinedDerivedState.add(componentName);
62 + console.error(
63 + '%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
64 + 'You have returned undefined.',
65 + componentName,
66 + );
67 + }
68 + }
69 + };
70 +}
71 +
72 +function warnNoop(
73 + publicInstance: React$Component<any, any>,
74 + callerName: string,
75 +) {
76 + if (__DEV__) {
77 + const constructor = publicInstance.constructor;
78 + const componentName =
79 + (constructor && getComponentNameFromType(constructor)) || 'ReactClass';
80 + const warningKey = componentName + '.' + callerName;
81 + if (didWarnAboutNoopUpdateForComponent[warningKey]) {
82 + return;
83 + }
84 +
85 + console.error(
86 + '%s(...): Can only update a mounting component. ' +
87 + 'This usually means you called %s() outside componentWillMount() on the server. ' +
88 + 'This is a no-op.\n\nPlease check the code for the %s component.',
89 + callerName,
90 + callerName,
91 + componentName,
92 + );
93 + didWarnAboutNoopUpdateForComponent[warningKey] = true;
94 + }
95 +}
96 +
97 +type InternalInstance = {
98 + queue: null | Array<Object>,
99 + replace: boolean,
100 +};
101 +
102 +const classComponentUpdater = {
103 + isMounted(inst) {
104 + return false;
105 + },
106 + enqueueSetState(inst, payload, callback) {
107 + const internals: InternalInstance = getInstance(inst);
108 + if (internals.queue === null) {
109 + warnNoop(inst, 'setState');
110 + } else {
111 + internals.queue.push(payload);
112 + if (__DEV__) {
113 + if (callback !== undefined && callback !== null) {
114 + warnOnInvalidCallback(callback, 'setState');
115 + }
116 + }
117 + }
118 + },
119 + enqueueReplaceState(inst, payload, callback) {
120 + const internals: InternalInstance = getInstance(inst);
121 + internals.replace = true;
122 + internals.queue = [payload];
123 + if (__DEV__) {
124 + if (callback !== undefined && callback !== null) {
125 + warnOnInvalidCallback(callback, 'setState');
126 + }
127 + }
128 + },
129 + enqueueForceUpdate(inst, callback) {
130 + const internals: InternalInstance = getInstance(inst);
131 + if (internals.queue === null) {
132 + warnNoop(inst, 'forceUpdate');
133 + } else {
134 + if (__DEV__) {
135 + if (callback !== undefined && callback !== null) {
136 + warnOnInvalidCallback(callback, 'setState');
137 + }
138 + }
139 + }
140 + },
141 +};
142 +
143 +function applyDerivedStateFromProps(
144 + instance: any,
145 + ctor: any,
146 + getDerivedStateFromProps: (props: any, state: any) => any,
147 + prevState: any,
148 + nextProps: any,
149 +) {
150 + const partialState = getDerivedStateFromProps(nextProps, prevState);
151 +
152 + if (__DEV__) {
153 + warnOnUndefinedDerivedState(ctor, partialState);
154 + }
155 + // Merge the partial state and the previous state.
156 + const newState =
157 + partialState === null || partialState === undefined
158 + ? prevState
159 + : Object.assign({}, prevState, partialState);
160 + return newState;
161 +}
162 +
163 +export function constructClassInstance(
164 + ctor: any,
165 + props: any,
166 + maskedLegacyContext: any,
167 +): any {
168 + let context = emptyContextObject;
169 + const contextType = ctor.contextType;
170 +
171 + if (__DEV__) {
172 + if ('contextType' in ctor) {
173 + const isValid =
174 + // Allow null for conditional declaration
175 + contextType === null ||
176 + (contextType !== undefined &&
177 + contextType.$$typeof === REACT_CONTEXT_TYPE &&
178 + contextType._context === undefined); // Not a <Context.Consumer>
179 +
180 + if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
181 + didWarnAboutInvalidateContextType.add(ctor);
182 +
183 + let addendum = '';
184 + if (contextType === undefined) {
185 + addendum =
186 + ' However, it is set to undefined. ' +
187 + 'This can be caused by a typo or by mixing up named and default imports. ' +
188 + 'This can also happen due to a circular dependency, so ' +
189 + 'try moving the createContext() call to a separate file.';
190 + } else if (typeof contextType !== 'object') {
191 + addendum = ' However, it is set to a ' + typeof contextType + '.';
192 + } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
193 + addendum = ' Did you accidentally pass the Context.Provider instead?';
194 + } else if (contextType._context !== undefined) {
195 + // <Context.Consumer>
196 + addendum = ' Did you accidentally pass the Context.Consumer instead?';
197 + } else {
198 + addendum =
199 + ' However, it is set to an object with keys {' +
200 + Object.keys(contextType).join(', ') +
201 + '}.';
202 + }
203 + console.error(
204 + '%s defines an invalid contextType. ' +
205 + 'contextType should point to the Context object returned by React.createContext().%s',
206 + getComponentNameFromType(ctor) || 'Component',
207 + addendum,
208 + );
209 + }
210 + }
211 + }
212 +
213 + if (typeof contextType === 'object' && contextType !== null) {
214 + // TODO: Implement Context.
215 + // context = readContext((contextType: any));
216 + throw new Error('Context is not yet implemented.');
217 + } else if (!disableLegacyContext) {
218 + context = maskedLegacyContext;
219 + }
220 +
221 + const instance = new ctor(props, context);
222 +
223 + if (__DEV__) {
224 + if (
225 + typeof ctor.getDerivedStateFromProps === 'function' &&
226 + (instance.state === null || instance.state === undefined)
227 + ) {
228 + const componentName = getComponentNameFromType(ctor) || 'Component';
229 + if (!didWarnAboutUninitializedState.has(componentName)) {
230 + didWarnAboutUninitializedState.add(componentName);
231 + console.error(
232 + '`%s` uses `getDerivedStateFromProps` but its initial state is ' +
233 + '%s. This is not recommended. Instead, define the initial state by ' +
234 + 'assigning an object to `this.state` in the constructor of `%s`. ' +
235 + 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
236 + componentName,
237 + instance.state === null ? 'null' : 'undefined',
238 + componentName,
239 + );
240 + }
241 + }
242 +
243 + // If new component APIs are defined, "unsafe" lifecycles won't be called.
244 + // Warn about these lifecycles if they are present.
245 + // Don't warn about react-lifecycles-compat polyfilled methods though.
246 + if (
247 + typeof ctor.getDerivedStateFromProps === 'function' ||
248 + typeof instance.getSnapshotBeforeUpdate === 'function'
249 + ) {
250 + let foundWillMountName = null;
251 + let foundWillReceivePropsName = null;
252 + let foundWillUpdateName = null;
253 + if (
254 + typeof instance.componentWillMount === 'function' &&
255 + instance.componentWillMount.__suppressDeprecationWarning !== true
256 + ) {
257 + foundWillMountName = 'componentWillMount';
258 + } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
259 + foundWillMountName = 'UNSAFE_componentWillMount';
260 + }
261 + if (
262 + typeof instance.componentWillReceiveProps === 'function' &&
263 + instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
264 + ) {
265 + foundWillReceivePropsName = 'componentWillReceiveProps';
266 + } else if (
267 + typeof instance.UNSAFE_componentWillReceiveProps === 'function'
268 + ) {
269 + foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
270 + }
271 + if (
272 + typeof instance.componentWillUpdate === 'function' &&
273 + instance.componentWillUpdate.__suppressDeprecationWarning !== true
274 + ) {
275 + foundWillUpdateName = 'componentWillUpdate';
276 + } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
277 + foundWillUpdateName = 'UNSAFE_componentWillUpdate';
278 + }
279 + if (
280 + foundWillMountName !== null ||
281 + foundWillReceivePropsName !== null ||
282 + foundWillUpdateName !== null
283 + ) {
284 + const componentName = getComponentNameFromType(ctor) || 'Component';
285 + const newApiName =
286 + typeof ctor.getDerivedStateFromProps === 'function'
287 + ? 'getDerivedStateFromProps()'
288 + : 'getSnapshotBeforeUpdate()';
289 + if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
290 + didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
291 + console.error(
292 + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
293 + '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
294 + 'The above lifecycles should be removed. Learn more about this warning here:\n' +
295 + 'https://reactjs.org/link/unsafe-component-lifecycles',
296 + componentName,
297 + newApiName,
298 + foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
299 + foundWillReceivePropsName !== null
300 + ? `\n ${foundWillReceivePropsName}`
301 + : '',
302 + foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
303 + );
304 + }
305 + }
306 + }
307 + }
308 +
309 + return instance;
310 +}
311 +
312 +function checkClassInstance(instance: any, ctor: any, newProps: any) {
313 + if (__DEV__) {
314 + const name = getComponentNameFromType(ctor) || 'Component';
315 + const renderPresent = instance.render;
316 +
317 + if (!renderPresent) {
318 + if (ctor.prototype && typeof ctor.prototype.render === 'function') {
319 + console.error(
320 + '%s(...): No `render` method found on the returned component ' +
321 + 'instance: did you accidentally return an object from the constructor?',
322 + name,
323 + );
324 + } else {
325 + console.error(
326 + '%s(...): No `render` method found on the returned component ' +
327 + 'instance: you may have forgotten to define `render`.',
328 + name,
329 + );
330 + }
331 + }
332 +
333 + if (
334 + instance.getInitialState &&
335 + !instance.getInitialState.isReactClassApproved &&
336 + !instance.state
337 + ) {
338 + console.error(
339 + 'getInitialState was defined on %s, a plain JavaScript class. ' +
340 + 'This is only supported for classes created using React.createClass. ' +
341 + 'Did you mean to define a state property instead?',
342 + name,
343 + );
344 + }
345 + if (
346 + instance.getDefaultProps &&
347 + !instance.getDefaultProps.isReactClassApproved
348 + ) {
349 + console.error(
350 + 'getDefaultProps was defined on %s, a plain JavaScript class. ' +
351 + 'This is only supported for classes created using React.createClass. ' +
352 + 'Use a static property to define defaultProps instead.',
353 + name,
354 + );
355 + }
356 + if (instance.propTypes) {
357 + console.error(
358 + 'propTypes was defined as an instance property on %s. Use a static ' +
359 + 'property to define propTypes instead.',
360 + name,
361 + );
362 + }
363 + if (instance.contextType) {
364 + console.error(
365 + 'contextType was defined as an instance property on %s. Use a static ' +
366 + 'property to define contextType instead.',
367 + name,
368 + );
369 + }
370 +
371 + if (disableLegacyContext) {
372 + if (ctor.childContextTypes) {
373 + console.error(
374 + '%s uses the legacy childContextTypes API which is no longer supported. ' +
375 + 'Use React.createContext() instead.',
376 + name,
377 + );
378 + }
379 + if (ctor.contextTypes) {
380 + console.error(
381 + '%s uses the legacy contextTypes API which is no longer supported. ' +
382 + 'Use React.createContext() with static contextType instead.',
383 + name,
384 + );
385 + }
386 + } else {
387 + if (instance.contextTypes) {
388 + console.error(
389 + 'contextTypes was defined as an instance property on %s. Use a static ' +
390 + 'property to define contextTypes instead.',
391 + name,
392 + );
393 + }
394 +
395 + if (
396 + ctor.contextType &&
397 + ctor.contextTypes &&
398 + !didWarnAboutContextTypeAndContextTypes.has(ctor)
399 + ) {
400 + didWarnAboutContextTypeAndContextTypes.add(ctor);
401 + console.error(
402 + '%s declares both contextTypes and contextType static properties. ' +
403 + 'The legacy contextTypes property will be ignored.',
404 + name,
405 + );
406 + }
407 + }
408 +
409 + if (typeof instance.componentShouldUpdate === 'function') {
410 + console.error(
411 + '%s has a method called ' +
412 + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
413 + 'The name is phrased as a question because the function is ' +
414 + 'expected to return a value.',
415 + name,
416 + );
417 + }
418 + if (
419 + ctor.prototype &&
420 + ctor.prototype.isPureReactComponent &&
421 + typeof instance.shouldComponentUpdate !== 'undefined'
422 + ) {
423 + console.error(
424 + '%s has a method called shouldComponentUpdate(). ' +
425 + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
426 + 'Please extend React.Component if shouldComponentUpdate is used.',
427 + getComponentNameFromType(ctor) || 'A pure component',
428 + );
429 + }
430 + if (typeof instance.componentDidUnmount === 'function') {
431 + console.error(
432 + '%s has a method called ' +
433 + 'componentDidUnmount(). But there is no such lifecycle method. ' +
434 + 'Did you mean componentWillUnmount()?',
435 + name,
436 + );
437 + }
438 + if (typeof instance.componentDidReceiveProps === 'function') {
439 + console.error(
440 + '%s has a method called ' +
441 + 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
442 + 'If you meant to update the state in response to changing props, ' +
443 + 'use componentWillReceiveProps(). If you meant to fetch data or ' +
444 + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
445 + name,
446 + );
447 + }
448 + if (typeof instance.componentWillRecieveProps === 'function') {
449 + console.error(
450 + '%s has a method called ' +
451 + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
452 + name,
453 + );
454 + }
455 + if (typeof instance.UNSAFE_componentWillRecieveProps === 'function') {
456 + console.error(
457 + '%s has a method called ' +
458 + 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
459 + name,
460 + );
461 + }
462 + const hasMutatedProps = instance.props !== newProps;
463 + if (instance.props !== undefined && hasMutatedProps) {
464 + console.error(
465 + '%s(...): When calling super() in `%s`, make sure to pass ' +
466 + "up the same props that your component's constructor was passed.",
467 + name,
468 + name,
469 + );
470 + }
471 + if (instance.defaultProps) {
472 + console.error(
473 + 'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
474 + ' Instead, define defaultProps as a static property on %s.',
475 + name,
476 + name,
477 + );
478 + }
479 +
480 + if (
481 + typeof instance.getSnapshotBeforeUpdate === 'function' &&
482 + typeof instance.componentDidUpdate !== 'function' &&
483 + !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)
484 + ) {
485 + didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
486 + console.error(
487 + '%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
488 + 'This component defines getSnapshotBeforeUpdate() only.',
489 + getComponentNameFromType(ctor),
490 + );
491 + }
492 +
493 + if (typeof instance.getDerivedStateFromProps === 'function') {
494 + console.error(
495 + '%s: getDerivedStateFromProps() is defined as an instance method ' +
496 + 'and will be ignored. Instead, declare it as a static method.',
497 + name,
498 + );
499 + }
500 + if (typeof instance.getDerivedStateFromError === 'function') {
501 + console.error(
502 + '%s: getDerivedStateFromError() is defined as an instance method ' +
503 + 'and will be ignored. Instead, declare it as a static method.',
504 + name,
505 + );
506 + }
507 + if (typeof ctor.getSnapshotBeforeUpdate === 'function') {
508 + console.error(
509 + '%s: getSnapshotBeforeUpdate() is defined as a static method ' +
510 + 'and will be ignored. Instead, declare it as an instance method.',
511 + name,
512 + );
513 + }
514 + const state = instance.state;
515 + if (state && (typeof state !== 'object' || isArray(state))) {
516 + console.error('%s.state: must be set to an object or null', name);
517 + }
518 + if (
519 + typeof instance.getChildContext === 'function' &&
520 + typeof ctor.childContextTypes !== 'object'
521 + ) {
522 + console.error(
523 + '%s.getChildContext(): childContextTypes must be defined in order to ' +
524 + 'use getChildContext().',
525 + name,
526 + );
527 + }
528 + }
529 +}
530 +
531 +function callComponentWillMount(type, instance) {
532 + const oldState = instance.state;
533 +
534 + if (typeof instance.componentWillMount === 'function') {
535 + instance.componentWillMount();
536 + }
537 + if (typeof instance.UNSAFE_componentWillMount === 'function') {
538 + instance.UNSAFE_componentWillMount();
539 + }
540 +
541 + if (oldState !== instance.state) {
542 + if (__DEV__) {
543 + console.error(
544 + '%s.componentWillMount(): Assigning directly to this.state is ' +
545 + "deprecated (except inside a component's " +
546 + 'constructor). Use setState instead.',
547 + getComponentNameFromType(type) || 'Component',
548 + );
549 + }
550 + classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
551 + }
552 +}
553 +
554 +function processUpdateQueue(
555 + internalInstance: InternalInstance,
556 + inst: any,
557 + props: any,
558 + maskedLegacyContext: any,
559 +): void {
560 + if (internalInstance.queue !== null && internalInstance.queue.length > 0) {
561 + const oldQueue = internalInstance.queue;
562 + const oldReplace = internalInstance.replace;
563 + internalInstance.queue = null;
564 + internalInstance.replace = false;
565 +
566 + if (oldReplace && oldQueue.length === 1) {
567 + inst.state = oldQueue[0];
568 + } else {
569 + let nextState = oldReplace ? oldQueue[0] : inst.state;
570 + let dontMutate = true;
571 + for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
572 + const partial = oldQueue[i];
573 + const partialState =
574 + typeof partial === 'function'
575 + ? partial.call(inst, nextState, props, maskedLegacyContext)
576 + : partial;
577 + if (partialState != null) {
578 + if (dontMutate) {
579 + dontMutate = false;
580 + nextState = Object.assign({}, nextState, partialState);
581 + } else {
582 + Object.assign(nextState, partialState);
583 + }
584 + }
585 + }
586 + inst.state = nextState;
587 + }
588 + } else {
589 + internalInstance.queue = null;
590 + }
591 +}
592 +
593 +// Invokes the mount life-cycles on a previously never rendered instance.
594 +export function mountClassInstance(
595 + instance: any,
596 + ctor: any,
597 + newProps: any,
598 + maskedLegacyContext: any,
599 +): void {
600 + if (__DEV__) {
601 + checkClassInstance(instance, ctor, newProps);
602 + }
603 +
604 + const initialState = instance.state !== undefined ? instance.state : null;
605 +
606 + instance.updater = classComponentUpdater;
607 + instance.props = newProps;
608 + instance.state = initialState;
609 + // We don't bother initializing the refs object on the server, since we're not going to resolve them anyway.
610 +
611 + // The internal instance will be used to manage updates that happen during this mount.
612 + const internalInstance: InternalInstance = {
613 + queue: null,
614 + replace: false,
615 + };
616 + setInstance(instance, internalInstance);
617 +
618 + const contextType = ctor.contextType;
619 + if (typeof contextType === 'object' && contextType !== null) {
620 + // TODO: Implement Context.
621 + // instance.context = readContext(contextType);
622 + throw new Error('Context is not yet implemented.');
623 + } else if (disableLegacyContext) {
624 + instance.context = emptyContextObject;
625 + } else {
626 + instance.context = maskedLegacyContext;
627 + }
628 +
629 + if (__DEV__) {
630 + if (instance.state === newProps) {
631 + const componentName = getComponentNameFromType(ctor) || 'Component';
632 + if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
633 + didWarnAboutDirectlyAssigningPropsToState.add(componentName);
634 + console.error(
635 + '%s: It is not recommended to assign props directly to state ' +
636 + "because updates to props won't be reflected in state. " +
637 + 'In most cases, it is better to use props directly.',
638 + componentName,
639 + );
640 + }
641 + }
642 + }
643 +
644 + const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
645 + if (typeof getDerivedStateFromProps === 'function') {
646 + instance.state = applyDerivedStateFromProps(
647 + instance,
648 + ctor,
649 + getDerivedStateFromProps,
650 + initialState,
651 + newProps,
652 + );
653 + }
654 +
655 + // In order to support react-lifecycles-compat polyfilled components,
656 + // Unsafe lifecycles should not be invoked for components using the new APIs.
657 + if (
658 + typeof ctor.getDerivedStateFromProps !== 'function' &&
659 + typeof instance.getSnapshotBeforeUpdate !== 'function' &&
660 + (typeof instance.UNSAFE_componentWillMount === 'function' ||
661 + typeof instance.componentWillMount === 'function')
662 + ) {
663 + callComponentWillMount(ctor, instance);
664 + // If we had additional state updates during this life-cycle, let's
665 + // process them now.
666 + processUpdateQueue(
667 + internalInstance,
668 + instance,
669 + newProps,
670 + maskedLegacyContext,
671 + );
672 + }
673 +}
packages/react-server/src/ReactFizzContext.js new
+94
@@ -0,0 +1,94 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 {disableLegacyContext} from 'shared/ReactFeatureFlags';
11 +import getComponentNameFromType from 'shared/getComponentNameFromType';
12 +import invariant from 'shared/invariant';
13 +import checkPropTypes from 'shared/checkPropTypes';
14 +
15 +let warnedAboutMissingGetChildContext;
16 +
17 +if (__DEV__) {
18 + warnedAboutMissingGetChildContext = {};
19 +}
20 +
21 +export const emptyContextObject = {};
22 +if (__DEV__) {
23 + Object.freeze(emptyContextObject);
24 +}
25 +
26 +export function getMaskedContext(type: any, unmaskedContext: Object): Object {
27 + if (disableLegacyContext) {
28 + return emptyContextObject;
29 + } else {
30 + const contextTypes = type.contextTypes;
31 + if (!contextTypes) {
32 + return emptyContextObject;
33 + }
34 +
35 + const context = {};
36 + for (const key in contextTypes) {
37 + context[key] = unmaskedContext[key];
38 + }
39 +
40 + if (__DEV__) {
41 + const name = getComponentNameFromType(type) || 'Unknown';
42 + checkPropTypes(contextTypes, context, 'context', name);
43 + }
44 +
45 + return context;
46 + }
47 +}
48 +
49 +export function processChildContext(
50 + type: any,
51 + instance: any,
52 + parentContext: Object,
53 + childContextTypes: Object,
54 +): Object {
55 + if (disableLegacyContext) {
56 + return parentContext;
57 + } else {
58 + // TODO (bvaughn) Replace this behavior with an invariant() in the future.
59 + // It has only been added in Fiber to match the (unintentional) behavior in Stack.
60 + if (typeof instance.getChildContext !== 'function') {
61 + if (__DEV__) {
62 + const componentName = getComponentNameFromType(type) || 'Unknown';
63 +
64 + if (!warnedAboutMissingGetChildContext[componentName]) {
65 + warnedAboutMissingGetChildContext[componentName] = true;
66 + console.error(
67 + '%s.childContextTypes is specified but there is no getChildContext() method ' +
68 + 'on the instance. You can either define getChildContext() on %s or remove ' +
69 + 'childContextTypes from it.',
70 + componentName,
71 + componentName,
72 + );
73 + }
74 + }
75 + return parentContext;
76 + }
77 +
78 + const childContext = instance.getChildContext();
79 + for (const contextKey in childContext) {
80 + invariant(
81 + contextKey in childContextTypes,
82 + '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
83 + getComponentNameFromType(type) || 'Unknown',
84 + contextKey,
85 + );
86 + }
87 + if (__DEV__) {
88 + const name = getComponentNameFromType(type) || 'Unknown';
89 + checkPropTypes(childContextTypes, childContext, 'child context', name);
90 + }
91 +
92 + return {...parentContext, ...childContext};
93 + }
94 +}
packages/react-server/src/ReactFizzServer.js
+275 -13
@@ -47,14 +47,33 @@ import {
47 createSuspenseBoundaryID,
48 getChildFormatContext,
49 } from './ReactServerFormatConfig';
50 +import {
51 + constructClassInstance,
52 + mountClassInstance,
53 +} from './ReactFizzClassComponent';
54 +import {
55 + getMaskedContext,
56 + processChildContext,
57 + emptyContextObject,
58 +} from './ReactFizzContext';
59 import {REACT_ELEMENT_TYPE, REACT_SUSPENSE_TYPE} from 'shared/ReactSymbols';
60 import ReactSharedInternals from 'shared/ReactSharedInternals';
61 +import {
62 + disableLegacyContext,
63 + disableModulePatternComponents,
64 + warnAboutDefaultPropsOnFunctionComponents,
65 +} from 'shared/ReactFeatureFlags';
66
67 +import getComponentNameFromType from 'shared/getComponentNameFromType';
68 import invariant from 'shared/invariant';
69 import isArray from 'shared/isArray';
70
71 const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
72
73 +type LegacyContext = {
74 + [key: string]: any,
75 +};
76 +
77 type SuspenseBoundary = {
78 +id: SuspenseBoundaryID,
79 rootSegmentID: number,
@@ -72,6 +91,7 @@ type Task = {
91 blockedBoundary: Root | SuspenseBoundary,
92 blockedSegment: Segment, // the segment we'll write to
93 abortSet: Set<Task>, // the abortable set that this task belongs to
94 + legacyContext: LegacyContext, // the current legacy context that this task is executing in
95 assignID: null | SuspenseBoundaryID, // id to assign to the content
96 };
97
@@ -151,7 +171,7 @@ export function createRequest(
171 children: ReactNodeList,
172 destination: Destination,
173 responseState: ResponseState,
154 - rootContext: FormatContext,
174 + rootFormatContext: FormatContext,
175 progressiveChunkSize: number = DEFAULT_PROGRESSIVE_CHUNK_SIZE,
176 onError: (error: mixed) => void = defaultErrorHandler,
177 onCompleteAll: () => void = noop,
@@ -178,7 +198,7 @@ export function createRequest(
198 onReadyToStream,
199 };
200 // This segment represents the root fallback.
181 - const rootSegment = createPendingSegment(request, 0, null, rootContext);
201 + const rootSegment = createPendingSegment(request, 0, null, rootFormatContext);
202 // There is no parent so conceptually, we're unblocked to flush this segment.
203 rootSegment.parentFlushed = true;
204 const rootTask = createTask(
@@ -187,6 +207,7 @@ export function createRequest(
207 null,
208 rootSegment,
209 abortSet,
210 + emptyContextObject,
211 null,
212 );
213 pingedTasks.push(rootTask);
@@ -223,6 +244,7 @@ function createTask(
244 blockedBoundary: Root | SuspenseBoundary,
245 blockedSegment: Segment,
246 abortSet: Set<Task>,
247 + legacyContext: LegacyContext,
248 assignID: null | SuspenseBoundaryID,
249 ): Task {
250 request.allPendingTasks++;
@@ -237,6 +259,7 @@ function createTask(
259 blockedBoundary,
260 blockedSegment,
261 abortSet,
262 + legacyContext,
263 assignID,
264 };
265 abortSet.add(task);
@@ -350,7 +373,7 @@ function renderSuspenseBoundary(
373 task.blockedSegment = parentSegment;
374 }
375
353 - // We create suspended task for the fallback because we don't want to actually task
376 + // We create suspended task for the fallback because we don't want to actually work
377 // on it yet in case we finish the main content, so we queue for later.
378 const suspendedFallbackTask = createTask(
379 request,
@@ -358,9 +381,10 @@ function renderSuspenseBoundary(
381 parentBoundary,
382 boundarySegment,
383 fallbackAbortSet,
384 + task.legacyContext,
385 newBoundary.id, // This is the ID we want to give this fallback so we can replace it later.
386 );
363 - // TODO: This should be queued at a separate lower priority queue so that we only task
387 + // TODO: This should be queued at a separate lower priority queue so that we only work
388 // on preparing fallbacks if we don't have any more main content to task on.
389 request.pingedTasks.push(suspendedFallbackTask);
390 }
@@ -393,16 +417,247 @@ function renderHostElement(
417 pushEndInstance(segment.chunks, type, props);
418 }
419
396 -function renderFunctionComponent(
420 +function shouldConstruct(Component) {
421 + return Component.prototype && Component.prototype.isReactComponent;
422 +}
423 +
424 +function renderWithHooks<Props, SecondArg>(
425 + request: Request,
426 + task: Task,
427 + Component: (p: Props, arg: SecondArg) => any,
428 + props: Props,
429 + secondArg: SecondArg,
430 +): any {
431 + // TODO: Set up Hooks etc.
432 + const children = Component(props, secondArg);
433 + return children;
434 +}
435 +
436 +function finishClassComponent(
437 request: Request,
438 task: Task,
399 - type: (props: any) => ReactNodeList,
439 + instance: Object,
440 + Component: any,
441 + props: any,
442 +): ReactNodeList {
443 + const nextChildren = instance.render();
444 +
445 + if (__DEV__) {
446 + if (instance.props !== props) {
447 + if (!didWarnAboutReassigningProps) {
448 + console.error(
449 + 'It looks like %s is reassigning its own `this.props` while rendering. ' +
450 + 'This is not supported and can lead to confusing bugs.',
451 + getComponentNameFromType(Component) || 'a component',
452 + );
453 + }
454 + didWarnAboutReassigningProps = true;
455 + }
456 + }
457 +
458 + if (!disableLegacyContext) {
459 + const childContextTypes = Component.childContextTypes;
460 + if (childContextTypes !== null && childContextTypes !== undefined) {
461 + const previousContext = task.legacyContext;
462 + const mergedContext = processChildContext(
463 + instance,
464 + Component,
465 + previousContext,
466 + childContextTypes,
467 + );
468 + task.legacyContext = mergedContext;
469 + renderNodeDestructive(request, task, nextChildren);
470 + task.legacyContext = previousContext;
471 + return;
472 + }
473 + }
474 +
475 + renderNodeDestructive(request, task, nextChildren);
476 +}
477 +
478 +function renderClassComponent(
479 + request: Request,
480 + task: Task,
481 + Component: any,
482 props: any,
483 ): void {
402 - const result = type(props);
403 - // We're now successfully past this task, and we don't have to pop back to
404 - // the previous task every again, so we can use the destructive recursive form.
405 - renderNodeDestructive(request, task, result);
484 + const unmaskedContext = !disableLegacyContext
485 + ? task.legacyContext
486 + : undefined;
487 + const instance = constructClassInstance(Component, props, unmaskedContext);
488 + mountClassInstance(instance, Component, props, unmaskedContext);
489 + finishClassComponent(request, task, Component);
490 +}
491 +
492 +const didWarnAboutBadClass = {};
493 +const didWarnAboutModulePatternComponent = {};
494 +const didWarnAboutContextTypeOnFunctionComponent = {};
495 +const didWarnAboutGetDerivedStateOnFunctionComponent = {};
496 +let didWarnAboutReassigningProps = false;
497 +const didWarnAboutDefaultPropsOnFunctionComponent = {};
498 +
499 +// This would typically be a function component but we still support module pattern
500 +// components for some reason.
501 +function renderIndeterminateComponent(
502 + request: Request,
503 + task: Task,
504 + Component: any,
505 + props: any,
506 +): void {
507 + let legacyContext;
508 + if (!disableLegacyContext) {
509 + legacyContext = getMaskedContext(Component, task.legacyContext);
510 + }
511 +
512 + if (__DEV__) {
513 + if (
514 + Component.prototype &&
515 + typeof Component.prototype.render === 'function'
516 + ) {
517 + const componentName = getComponentNameFromType(Component) || 'Unknown';
518 +
519 + if (!didWarnAboutBadClass[componentName]) {
520 + console.error(
521 + "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
522 + 'This is likely to cause errors. Change %s to extend React.Component instead.',
523 + componentName,
524 + componentName,
525 + );
526 + didWarnAboutBadClass[componentName] = true;
527 + }
528 + }
529 + }
530 +
531 + const value = renderWithHooks(request, task, Component, props, legacyContext);
532 +
533 + if (__DEV__) {
534 + // Support for module components is deprecated and is removed behind a flag.
535 + // Whether or not it would crash later, we want to show a good message in DEV first.
536 + if (
537 + typeof value === 'object' &&
538 + value !== null &&
539 + typeof value.render === 'function' &&
540 + value.$$typeof === undefined
541 + ) {
542 + const componentName = getComponentNameFromType(Component) || 'Unknown';
543 + if (!didWarnAboutModulePatternComponent[componentName]) {
544 + console.error(
545 + 'The <%s /> component appears to be a function component that returns a class instance. ' +
546 + 'Change %s to a class that extends React.Component instead. ' +
547 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
548 + "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
549 + 'cannot be called with `new` by React.',
550 + componentName,
551 + componentName,
552 + componentName,
553 + );
554 + didWarnAboutModulePatternComponent[componentName] = true;
555 + }
556 + }
557 + }
558 +
559 + if (
560 + // Run these checks in production only if the flag is off.
561 + // Eventually we'll delete this branch altogether.
562 + !disableModulePatternComponents &&
563 + typeof value === 'object' &&
564 + value !== null &&
565 + typeof value.render === 'function' &&
566 + value.$$typeof === undefined
567 + ) {
568 + if (__DEV__) {
569 + const componentName = getComponentNameFromType(Component) || 'Unknown';
570 + if (!didWarnAboutModulePatternComponent[componentName]) {
571 + console.error(
572 + 'The <%s /> component appears to be a function component that returns a class instance. ' +
573 + 'Change %s to a class that extends React.Component instead. ' +
574 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
575 + "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
576 + 'cannot be called with `new` by React.',
577 + componentName,
578 + componentName,
579 + componentName,
580 + );
581 + didWarnAboutModulePatternComponent[componentName] = true;
582 + }
583 + }
584 +
585 + mountClassInstance(value, Component, props, legacyContext);
586 + finishClassComponent(request, task, value, Component);
587 + } else {
588 + // Proceed under the assumption that this is a function component
589 + if (__DEV__) {
590 + if (disableLegacyContext && Component.contextTypes) {
591 + console.error(
592 + '%s uses the legacy contextTypes API which is no longer supported. ' +
593 + 'Use React.createContext() with React.useContext() instead.',
594 + getComponentNameFromType(Component) || 'Unknown',
595 + );
596 + }
597 + }
598 + if (__DEV__) {
599 + validateFunctionComponentInDev(Component);
600 + }
601 + // We're now successfully past this task, and we don't have to pop back to
602 + // the previous task every again, so we can use the destructive recursive form.
603 + renderNodeDestructive(request, task, value);
604 + }
605 +}
606 +
607 +function validateFunctionComponentInDev(Component: any): void {
608 + if (__DEV__) {
609 + if (Component) {
610 + if (Component.childContextTypes) {
611 + console.error(
612 + '%s(...): childContextTypes cannot be defined on a function component.',
613 + Component.displayName || Component.name || 'Component',
614 + );
615 + }
616 + }
617 +
618 + if (
619 + warnAboutDefaultPropsOnFunctionComponents &&
620 + Component.defaultProps !== undefined
621 + ) {
622 + const componentName = getComponentNameFromType(Component) || 'Unknown';
623 +
624 + if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
625 + console.error(
626 + '%s: Support for defaultProps will be removed from function components ' +
627 + 'in a future major release. Use JavaScript default parameters instead.',
628 + componentName,
629 + );
630 + didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
631 + }
632 + }
633 +
634 + if (typeof Component.getDerivedStateFromProps === 'function') {
635 + const componentName = getComponentNameFromType(Component) || 'Unknown';
636 +
637 + if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
638 + console.error(
639 + '%s: Function components do not support getDerivedStateFromProps.',
640 + componentName,
641 + );
642 + didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
643 + }
644 + }
645 +
646 + if (
647 + typeof Component.contextType === 'object' &&
648 + Component.contextType !== null
649 + ) {
650 + const componentName = getComponentNameFromType(Component) || 'Unknown';
651 +
652 + if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
653 + console.error(
654 + '%s: Function components do not support contextType.',
655 + componentName,
656 + );
657 + didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
658 + }
659 + }
660 + }
661 }
662
663 function renderElement(
@@ -413,7 +668,11 @@ function renderElement(
668 node: ReactNodeList,
669 ): void {
670 if (typeof type === 'function') {
416 - renderFunctionComponent(request, task, type, props);
671 + if (shouldConstruct(type)) {
672 + renderClassComponent(request, task, type, props);
673 + } else {
674 + renderIndeterminateComponent(request, task, type, props);
675 + }
676 } else if (typeof type === 'string') {
677 renderHostElement(request, task, type, props);
678 } else if (type === REACT_SUSPENSE_TYPE) {
@@ -505,6 +764,7 @@ function spawnNewSuspendedTask(
764 task.blockedBoundary,
765 newSegment,
766 task.abortSet,
767 + task.legacyContext,
768 task.assignID,
769 );
770 // We've delegated the assignment.
@@ -521,7 +781,8 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
781
782 // Snapshot the current context in case something throws to interrupt the
783 // process.
524 - const previousContext = task.blockedSegment.formatContext;
784 + const previousFormatContext = task.blockedSegment.formatContext;
785 + const previousLegacyContext = task.legacyContext;
786 try {
787 return renderNodeDestructive(request, task, node);
788 } catch (x) {
@@ -529,7 +790,8 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
790 spawnNewSuspendedTask(request, task, x);
791 // Restore the context. We assume that this will be restored by the inner
792 // functions in case nothing throws so we don't use "finally" here.
532 - task.blockedSegment.formatContext = previousContext;
793 + task.blockedSegment.formatContext = previousFormatContext;
794 + task.legacyContext = previousLegacyContext;
795 } else {
796 // We assume that we don't need the correct context.
797 // Let's terminate the rest of the tree and don't render any siblings.