Skip to content

Commit 21dd6d0

Browse files
authored
forwardport from v10 (#5053)
* Fix crash where a synchronous effect render unmounts the tree (#5026) (cherry picked from commit 8cbed5f) * Golf down compat (#5025) * Make useFlushSync do sync flushes * Golf bytes to offset change * Some more golfing * Revert "Make useFlushSync do sync flushes" This reverts commit 001bbce. (cherry picked from commit af4c459) * Implement flushSync (#5036) (cherry picked from commit 978c0b9) * undefined prototype (#5041) * undefined prototype * Add test (cherry picked from commit b341bfe) * Ensure we reset renderCount (#5017) * Golf some bytes and ensure we reset renderCount * Apply suggestion from @JoviDeCroock * Add test (cherry picked from commit 55254ef) * fix(compat): export StrictMode directly
1 parent 2459326 commit 21dd6d0

13 files changed

Lines changed: 255 additions & 175 deletions

File tree

compat/src/index.js

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,45 @@
11
import {
2-
createElement,
3-
render as preactRender,
4-
cloneElement as preactCloneElement,
5-
createRef,
62
Component,
3+
Fragment,
74
createContext,
8-
Fragment
5+
createElement,
6+
createRef,
7+
options,
8+
cloneElement as preactCloneElement,
9+
render as preactRender
910
} from 'preact';
1011
import {
11-
useState,
12-
useId,
13-
useReducer,
12+
useCallback,
13+
useContext,
14+
useDebugValue,
1415
useEffect,
15-
useLayoutEffect,
16-
useRef,
16+
useId,
1717
useImperativeHandle,
18+
useLayoutEffect,
1819
useMemo,
19-
useCallback,
20-
useContext,
21-
useDebugValue
20+
useReducer,
21+
useRef,
22+
useState
2223
} from 'preact/hooks';
24+
import { Children } from './Children';
25+
import { PureComponent } from './PureComponent';
26+
import { forwardRef } from './forwardRef';
2327
import {
24-
useInsertionEffect,
2528
startTransition,
2629
useDeferredValue,
30+
useInsertionEffect,
2731
useSyncExternalStore,
2832
useTransition
2933
} from './hooks';
30-
import { PureComponent } from './PureComponent';
3134
import { memo } from './memo';
32-
import { forwardRef } from './forwardRef';
33-
import { Children } from './Children';
34-
import { Suspense, lazy } from './suspense';
3535
import { createPortal } from './portals';
3636
import {
37-
hydrate,
38-
render,
3937
REACT_ELEMENT_TYPE,
40-
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
38+
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
39+
hydrate,
40+
render
4141
} from './render';
42+
import { Suspense, lazy } from './suspense';
4243

4344
const version = '18.3.1'; // trick libraries to think we are react
4445

@@ -77,7 +78,7 @@ function isMemo(element) {
7778
return (
7879
!!element &&
7980
typeof element.displayName == 'string' &&
80-
element.displayName.startsWith('Memo(')
81+
element.displayName.indexOf('Memo(') == 0
8182
);
8283
}
8384

@@ -121,15 +122,20 @@ function findDOMNode(component) {
121122
}
122123

123124
/**
124-
* In React, `flushSync` flushes the entire tree and forces a rerender. It's
125-
* implmented here as a no-op.
125+
* In React, `flushSync` flushes the entire tree and forces a rerender.
126126
* @template Arg
127127
* @template Result
128128
* @param {(arg: Arg) => Result} callback function that runs before the flush
129129
* @param {Arg} [arg] Optional argument that can be passed to the callback
130130
* @returns
131131
*/
132-
const flushSync = (callback, arg) => callback(arg);
132+
const flushSync = (callback, arg) => {
133+
const prevDebounce = options.debounceRendering;
134+
options.debounceRendering = cb => cb();
135+
const res = callback(arg);
136+
options.debounceRendering = prevDebounce;
137+
return res;
138+
};
133139

134140
/**
135141
* In React, `unstable_batchedUpdates` is a legacy feature that was made a no-op
@@ -144,12 +150,6 @@ function unstable_batchedUpdates(callback, arg) {
144150
return callback(arg);
145151
}
146152

147-
/**
148-
* Strict Mode is not implemented in Preact, so we provide a stand-in for it
149-
* that just renders its children without imposing any restrictions.
150-
*/
151-
const StrictMode = Fragment;
152-
153153
// compat to react-is
154154
export const isElement = isValidElement;
155155

@@ -182,7 +182,7 @@ export {
182182
useDeferredValue,
183183
useSyncExternalStore,
184184
useTransition,
185-
StrictMode,
185+
Fragment as StrictMode,
186186
Suspense,
187187
lazy,
188188
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -229,7 +229,7 @@ export default {
229229
forwardRef,
230230
flushSync,
231231
unstable_batchedUpdates,
232-
StrictMode,
232+
StrictMode: Fragment,
233233
Suspense,
234234
lazy,
235235
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED

compat/src/memo.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,22 @@ import { shallowDiffers } from './util';
1010
*/
1111
export function memo(c, comparer) {
1212
function shouldUpdate(nextProps) {
13-
let ref = this.props.ref;
14-
let updateRef = ref == nextProps.ref;
15-
if (!updateRef && ref) {
16-
ref.call ? ref(null) : (ref.current = null);
13+
const ref = this.props.ref;
14+
if (ref != nextProps.ref && ref) {
15+
typeof ref == 'function' ? ref(null) : (ref.current = null);
1716
}
1817

19-
if (!comparer) {
20-
return shallowDiffers(this.props, nextProps);
21-
}
22-
23-
return !comparer(this.props, nextProps) || !updateRef;
18+
return comparer
19+
? !comparer(this.props, nextProps) || ref != nextProps.ref
20+
: shallowDiffers(this.props, nextProps);
2421
}
2522

2623
function Memoed(props) {
2724
this.shouldComponentUpdate = shouldUpdate;
2825
return createElement(c, props);
2926
}
3027
Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')';
31-
Memoed.prototype.isReactComponent = true;
28+
Memoed._forwarded = Memoed.prototype.isReactComponent = true;
3229
Memoed.type = c;
3330
return Memoed;
3431
}

compat/src/render.js

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const IS_DOM = typeof document !== 'undefined';
3737
const onChangeInputType = type => /fil|che|rad/.test(type);
3838

3939
// Some libraries like `react-virtualized` explicitly check for this.
40-
Component.prototype.isReactComponent = {};
40+
Component.prototype.isReactComponent = true;
4141

4242
// `UNSAFE_*` lifecycle hooks
4343
// Preact only ever invokes the unprefixed methods.
@@ -97,24 +97,17 @@ let oldEventHook = options.event;
9797
options.event = e => {
9898
if (oldEventHook) e = oldEventHook(e);
9999

100-
e.persist = empty;
101-
e.isPropagationStopped = isPropagationStopped;
102-
e.isDefaultPrevented = isDefaultPrevented;
100+
e.persist = () => {};
101+
e.isPropagationStopped = function isPropagationStopped() {
102+
return this.cancelBubble;
103+
};
104+
e.isDefaultPrevented = function isDefaultPrevented() {
105+
return this.defaultPrevented;
106+
};
103107
return (e.nativeEvent = e);
104108
};
105109

106-
function empty() {}
107-
108-
function isPropagationStopped() {
109-
return this.cancelBubble;
110-
}
111-
112-
function isDefaultPrevented() {
113-
return this.defaultPrevented;
114-
}
115-
116110
const classNameDescriptorNonEnumberable = {
117-
enumerable: false,
118111
configurable: true,
119112
get() {
120113
return this.class;
@@ -124,9 +117,9 @@ const classNameDescriptorNonEnumberable = {
124117
function handleDomVNode(vnode) {
125118
let props = vnode.props,
126119
type = vnode.type,
127-
normalizedProps = {};
120+
normalizedProps = {},
121+
isNonDashedType = type.indexOf('-') == -1;
128122

129-
let isNonDashedType = type.indexOf('-') === -1;
130123
for (let i in props) {
131124
let value = props[i];
132125

@@ -198,30 +191,28 @@ function handleDomVNode(vnode) {
198191
normalizedProps[i] = value;
199192
}
200193

201-
// Add support for array select values: <select multiple value={[]} />
202-
if (
203-
type == 'select' &&
204-
normalizedProps.multiple &&
205-
Array.isArray(normalizedProps.value)
206-
) {
207-
// forEach() always returns undefined, which we abuse here to unset the value prop.
208-
normalizedProps.value = toChildArray(props.children).forEach(child => {
209-
child.props.selected =
210-
normalizedProps.value.indexOf(child.props.value) != -1;
211-
});
212-
}
213-
214-
// Adding support for defaultValue in select tag
215-
if (type == 'select' && normalizedProps.defaultValue != null) {
216-
normalizedProps.value = toChildArray(props.children).forEach(child => {
217-
if (normalizedProps.multiple) {
194+
if (type == 'select') {
195+
// Add support for array select values: <select multiple value={[]} />
196+
if (normalizedProps.multiple && Array.isArray(normalizedProps.value)) {
197+
// forEach() always returns undefined, which we abuse here to unset the value prop.
198+
normalizedProps.value = toChildArray(props.children).forEach(child => {
218199
child.props.selected =
219-
normalizedProps.defaultValue.indexOf(child.props.value) != -1;
220-
} else {
221-
child.props.selected =
222-
normalizedProps.defaultValue == child.props.value;
223-
}
224-
});
200+
normalizedProps.value.indexOf(child.props.value) != -1;
201+
});
202+
}
203+
204+
// Adding support for defaultValue in select tag
205+
if (normalizedProps.defaultValue != null) {
206+
normalizedProps.value = toChildArray(props.children).forEach(child => {
207+
if (normalizedProps.multiple) {
208+
child.props.selected =
209+
normalizedProps.defaultValue.indexOf(child.props.value) != -1;
210+
} else {
211+
child.props.selected =
212+
normalizedProps.defaultValue == child.props.value;
213+
}
214+
});
215+
}
225216
}
226217

227218
if (props.class && !props.className) {

compat/src/suspense.js

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { Component, createElement, options, Fragment } from 'preact';
1+
import { Component, Fragment, createElement, options } from 'preact';
22
import {
3-
MODE_HYDRATE,
3+
COMPONENT_FORCE,
44
FORCE_PROPS_REVALIDATE,
5-
COMPONENT_FORCE
5+
MODE_HYDRATE
66
} from '../../src/constants';
77
import { assign } from './util';
88

99
const oldCatchError = options._catchError;
10-
options._catchError = function (error, newVNode, oldVNode, errorInfo) {
10+
options._catchError = (error, newVNode, oldVNode, errorInfo) => {
1111
if (error.then) {
1212
/** @type {import('./internal').Component} */
1313
let component;
1414
let vnode = newVNode;
1515

16-
for (; (vnode = vnode._parent); ) {
16+
while ((vnode = vnode._parent)) {
1717
if ((component = vnode._component) && component._childDidSuspend) {
1818
if (newVNode._dom == null) {
1919
newVNode._dom = oldVNode._dom;
@@ -28,7 +28,7 @@ options._catchError = function (error, newVNode, oldVNode, errorInfo) {
2828
};
2929

3030
const oldUnmount = options.unmount;
31-
options.unmount = function (vnode) {
31+
options.unmount = vnode => {
3232
/** @type {import('./internal').Component} */
3333
const component = vnode._component;
3434
if (component) component._unmounted = true;
@@ -118,17 +118,14 @@ Suspense.prototype = new Component();
118118
Suspense.prototype._childDidSuspend = function (promise, suspendingVNode) {
119119
const suspendingComponent = suspendingVNode._component;
120120

121-
/** @type {import('./internal').SuspenseComponent} */
122-
const c = this;
123-
124-
if (c._suspenders == null) {
125-
c._suspenders = [];
121+
if (this._suspenders == null) {
122+
this._suspenders = [];
126123
}
127-
c._suspenders.push(suspendingComponent);
124+
this._suspenders.push(suspendingComponent);
128125

129126
let resolved = false;
130127
const onResolved = () => {
131-
if (resolved || c._unmounted) return;
128+
if (resolved || this._unmounted) return;
132129

133130
resolved = true;
134131
suspendingComponent._onResolve = null;
@@ -145,22 +142,22 @@ Suspense.prototype._childDidSuspend = function (promise, suspendingVNode) {
145142
suspendingComponent._parentDom = null;
146143

147144
const onSuspensionComplete = () => {
148-
if (!--c._pendingSuspensionCount) {
145+
if (!--this._pendingSuspensionCount) {
149146
// If the suspension was during hydration we don't need to restore the
150147
// suspended children into the _children array
151-
if (c.state._suspended) {
152-
const suspendedVNode = c.state._suspended;
153-
c._vnode._children[0] = removeOriginal(
148+
if (this.state._suspended) {
149+
const suspendedVNode = this.state._suspended;
150+
this._vnode._children[0] = removeOriginal(
154151
suspendedVNode,
155152
suspendedVNode._component._parentDom,
156153
suspendedVNode._component._originalParentDom
157154
);
158155
}
159156

160-
c.setState({ _suspended: (c._detachOnNextRender = null) });
157+
this.setState({ _suspended: (this._detachOnNextRender = null) });
161158

162159
let suspended;
163-
while ((suspended = c._suspenders.pop())) {
160+
while ((suspended = this._suspenders.pop())) {
164161
// Restore _parentDom before forceUpdate so render can proceed
165162
suspended._parentDom = originalParentDom;
166163
suspended.forceUpdate();
@@ -174,10 +171,12 @@ Suspense.prototype._childDidSuspend = function (promise, suspendingVNode) {
174171
* While in non-hydration cases the usual fallback -> component flow would occour.
175172
*/
176173
if (
177-
!c._pendingSuspensionCount++ &&
174+
!this._pendingSuspensionCount++ &&
178175
!(suspendingVNode._flags & MODE_HYDRATE)
179176
) {
180-
c.setState({ _suspended: (c._detachOnNextRender = c._vnode._children[0]) });
177+
this.setState({
178+
_suspended: (this._detachOnNextRender = this._vnode._children[0])
179+
});
181180
}
182181
promise.then(onResolved, onResolved);
183182
};

compat/test/browser/PureComponent.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,6 @@ describe('PureComponent', () => {
152152

153153
it('should have "isPureReactComponent" property', () => {
154154
let Pure = new React.PureComponent();
155-
expect(Pure.isReactComponent).to.deep.equal({});
155+
expect(Pure.isReactComponent).to.deep.equal(true);
156156
});
157157
});

compat/test/browser/component.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('components', () => {
2121

2222
it('should have "isReactComponent" property', () => {
2323
let Comp = new React.Component();
24-
expect(Comp.isReactComponent).to.deep.equal({});
24+
expect(Comp.isReactComponent).to.deep.equal(true);
2525
});
2626

2727
it('should be sane', () => {

0 commit comments

Comments
 (0)