| 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 | |
| 8 | import ReactNoopUpdateQueue from './ReactNoopUpdateQueue'; |
| 9 | import assign from 'shared/assign'; |
| 10 | |
| 11 | const emptyObject = {}; |
| 12 | if (__DEV__) { |
| 13 | Object.freeze(emptyObject); |
| 14 | } |
| 15 | |
| 16 | /** |
| 17 | * Base class helpers for the updating state of a component. |
| 18 | */ |
| 19 | function Component(props, context, updater) { |
| 20 | this.props = props; |
| 21 | this.context = context; |
| 22 | // If a component has string refs, we will assign a different object later. |
| 23 | this.refs = emptyObject; |
| 24 | // We initialize the default updater but the real one gets injected by the |
| 25 | // renderer. |
| 26 | this.updater = updater || ReactNoopUpdateQueue; |
| 27 | } |
| 28 | |
| 29 | Component.prototype.isReactComponent = {}; |
| 30 | |
| 31 | /** |
| 32 | * Sets a subset of the state. Always use this to mutate |
| 33 | * state. You should treat `this.state` as immutable. |
| 34 | * |
| 35 | * There is no guarantee that `this.state` will be immediately updated, so |
| 36 | * accessing `this.state` after calling this method may return the old value. |
| 37 | * |
| 38 | * There is no guarantee that calls to `setState` will run synchronously, |
| 39 | * as they may eventually be batched together. You can provide an optional |
| 40 | * callback that will be executed when the call to setState is actually |
| 41 | * completed. |
| 42 | * |
| 43 | * When a function is provided to setState, it will be called at some point in |
| 44 | * the future (not synchronously). It will be called with the up to date |
| 45 | * component arguments (state, props, context). These values can be different |
| 46 | * from this.* because your function may be called after receiveProps but before |
| 47 | * shouldComponentUpdate, and this new state, props, and context will not yet be |
| 48 | * assigned to this. |
| 49 | * |
| 50 | * @param {object|function} partialState Next partial state or function to |
| 51 | * produce next partial state to be merged with current state. |
| 52 | * @param {?function} callback Called after state is updated. |
| 53 | * @final |
| 54 | * @protected |
| 55 | */ |
| 56 | Component.prototype.setState = function (partialState, callback) { |
| 57 | if ( |
| 58 | typeof partialState !== 'object' && |
| 59 | typeof partialState !== 'function' && |
| 60 | partialState != null |
| 61 | ) { |
| 62 | throw new Error( |
| 63 | 'takes an object of state variables to update or a ' + |
| 64 | 'function which returns an object of state variables.', |
| 65 | ); |
| 66 | } |
| 67 | |
| 68 | this.updater.enqueueSetState(this, partialState, callback, 'setState'); |
| 69 | }; |
| 70 | |
| 71 | /** |
| 72 | * Forces an update. This should only be invoked when it is known with |
| 73 | * certainty that we are **not** in a DOM transaction. |
| 74 | * |
| 75 | * You may want to call this when you know that some deeper aspect of the |
| 76 | * component's state has changed but `setState` was not called. |
| 77 | * |
| 78 | * This will not invoke `shouldComponentUpdate`, but it will invoke |
| 79 | * `componentWillUpdate` and `componentDidUpdate`. |
| 80 | * |
| 81 | * @param {?function} callback Called after update is complete. |
| 82 | * @final |
| 83 | * @protected |
| 84 | */ |
| 85 | Component.prototype.forceUpdate = function (callback) { |
| 86 | this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); |
| 87 | }; |
| 88 | |
| 89 | /** |
| 90 | * Deprecated APIs. These APIs used to exist on classic React classes but since |
| 91 | * we would like to deprecate them, we're not going to move them over to this |
| 92 | * modern base class. Instead, we define a getter that warns if it's accessed. |
| 93 | */ |
| 94 | if (__DEV__) { |
| 95 | const deprecatedAPIs = { |
| 96 | isMounted: [ |
| 97 | 'isMounted', |
| 98 | 'Instead, make sure to clean up subscriptions and pending requests in ' + |
| 99 | 'componentWillUnmount to prevent memory leaks.', |
| 100 | ], |
| 101 | replaceState: [ |
| 102 | 'replaceState', |
| 103 | 'Refactor your code to use setState instead (see ' + |
| 104 | 'https://github.com/facebook/react/issues/3236).', |
| 105 | ], |
| 106 | }; |
| 107 | const defineDeprecationWarning = function (methodName, info) { |
| 108 | Object.defineProperty(Component.prototype, methodName, { |
| 109 | get: function () { |
| 110 | console.warn( |
| 111 | '%s(...) is deprecated in plain JavaScript React classes. %s', |
| 112 | info[0], |
| 113 | info[1], |
| 114 | ); |
| 115 | return undefined; |
| 116 | }, |
| 117 | }); |
| 118 | }; |
| 119 | for (const fnName in deprecatedAPIs) { |
| 120 | if (deprecatedAPIs.hasOwnProperty(fnName)) { |
| 121 | defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | function ComponentDummy() {} |
| 127 | ComponentDummy.prototype = Component.prototype; |
| 128 | |
| 129 | /** |
| 130 | * Convenience component with default shallow equality check for sCU. |
| 131 | */ |
| 132 | function PureComponent(props, context, updater) { |
| 133 | this.props = props; |
| 134 | this.context = context; |
| 135 | // If a component has string refs, we will assign a different object later. |
| 136 | this.refs = emptyObject; |
| 137 | this.updater = updater || ReactNoopUpdateQueue; |
| 138 | } |
| 139 | |
| 140 | const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); |
| 141 | pureComponentPrototype.constructor = PureComponent; |
| 142 | // Avoid an extra prototype jump for these methods. |
| 143 | assign(pureComponentPrototype, Component.prototype); |
| 144 | pureComponentPrototype.isPureReactComponent = true; |
| 145 | |
| 146 | export {Component, PureComponent}; |