Skip to content

Commit 2d6811d

Browse files
authored
Revert state settling and unsafe (#4829)
* Revert "Look at impact of removing deprecated lifecycles (#4656)" This reverts commit 4e74070. * Revert "Fix mistake in sCU this binding" This reverts commit b3e8fa0. * Revert "Implement deferring state updates (#4760)" This reverts commit d4d41f2.
1 parent d21722e commit 2d6811d

File tree

9 files changed

+365
-226
lines changed

9 files changed

+365
-226
lines changed

compat/src/render.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,33 @@ const onChangeInputType = type => /fil|che|rad/.test(type);
4040
// Some libraries like `react-virtualized` explicitly check for this.
4141
Component.prototype.isReactComponent = {};
4242

43+
// `UNSAFE_*` lifecycle hooks
44+
// Preact only ever invokes the unprefixed methods.
45+
// Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method.
46+
// - If a component defines its own `componentDidMount()` (including via defineProperty), use that.
47+
// - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter.
48+
// - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property.
49+
// See https://github.com/preactjs/preact/issues/1941
50+
[
51+
'componentWillMount',
52+
'componentWillReceiveProps',
53+
'componentWillUpdate'
54+
].forEach(key => {
55+
Object.defineProperty(Component.prototype, key, {
56+
configurable: true,
57+
get() {
58+
return this['UNSAFE_' + key];
59+
},
60+
set(v) {
61+
Object.defineProperty(this, key, {
62+
configurable: true,
63+
writable: true,
64+
value: v
65+
});
66+
}
67+
});
68+
});
69+
4370
/**
4471
* Proxy render() since React returns a Component reference.
4572
* @param {import('./internal').VNode} vnode VNode tree to render

compat/test/browser/component.test.js

Lines changed: 250 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { setupRerender } from 'preact/test-utils';
22
import { setupScratch, teardown } from '../../../test/_util/helpers';
3-
import React, { createElement } from 'preact/compat';
3+
import React, { createElement, Component } from 'preact/compat';
4+
import { vi } from 'vitest';
45

56
describe('components', () => {
67
/** @type {HTMLDivElement} */
@@ -75,4 +76,252 @@ describe('components', () => {
7576
children: 'second'
7677
});
7778
});
79+
80+
describe('UNSAFE_* lifecycle methods', () => {
81+
it('should support UNSAFE_componentWillMount', () => {
82+
let spy = vi.fn();
83+
84+
class Foo extends React.Component {
85+
// eslint-disable-next-line camelcase
86+
UNSAFE_componentWillMount() {
87+
spy();
88+
}
89+
90+
render() {
91+
return <h1>foo</h1>;
92+
}
93+
}
94+
95+
React.render(<Foo />, scratch);
96+
97+
expect(spy).toHaveBeenCalledOnce();
98+
});
99+
100+
it('should support UNSAFE_componentWillMount #2', () => {
101+
let spy = vi.fn();
102+
103+
class Foo extends React.Component {
104+
render() {
105+
return <h1>foo</h1>;
106+
}
107+
}
108+
109+
Object.defineProperty(Foo.prototype, 'UNSAFE_componentWillMount', {
110+
value: spy
111+
});
112+
113+
React.render(<Foo />, scratch);
114+
expect(spy).toHaveBeenCalledOnce();
115+
});
116+
117+
it('should support UNSAFE_componentWillReceiveProps', () => {
118+
let spy = vi.fn();
119+
120+
class Foo extends React.Component {
121+
// eslint-disable-next-line camelcase
122+
UNSAFE_componentWillReceiveProps() {
123+
spy();
124+
}
125+
126+
render() {
127+
return <h1>foo</h1>;
128+
}
129+
}
130+
131+
React.render(<Foo />, scratch);
132+
// Trigger an update
133+
React.render(<Foo />, scratch);
134+
expect(spy).toHaveBeenCalledOnce();
135+
});
136+
137+
it('should support UNSAFE_componentWillReceiveProps #2', () => {
138+
let spy = vi.fn();
139+
140+
class Foo extends React.Component {
141+
render() {
142+
return <h1>foo</h1>;
143+
}
144+
}
145+
146+
Object.defineProperty(Foo.prototype, 'UNSAFE_componentWillReceiveProps', {
147+
value: spy
148+
});
149+
150+
React.render(<Foo />, scratch);
151+
// Trigger an update
152+
React.render(<Foo />, scratch);
153+
expect(spy).toHaveBeenCalledOnce();
154+
});
155+
156+
it('should support UNSAFE_componentWillUpdate', () => {
157+
let spy = vi.fn();
158+
159+
class Foo extends React.Component {
160+
// eslint-disable-next-line camelcase
161+
UNSAFE_componentWillUpdate() {
162+
spy();
163+
}
164+
165+
render() {
166+
return <h1>foo</h1>;
167+
}
168+
}
169+
170+
React.render(<Foo />, scratch);
171+
// Trigger an update
172+
React.render(<Foo />, scratch);
173+
expect(spy).toHaveBeenCalledOnce();
174+
});
175+
176+
it('should support UNSAFE_componentWillUpdate #2', () => {
177+
let spy = vi.fn();
178+
179+
class Foo extends React.Component {
180+
render() {
181+
return <h1>foo</h1>;
182+
}
183+
}
184+
185+
Object.defineProperty(Foo.prototype, 'UNSAFE_componentWillUpdate', {
186+
value: spy
187+
});
188+
189+
React.render(<Foo />, scratch);
190+
// Trigger an update
191+
React.render(<Foo />, scratch);
192+
expect(spy).toHaveBeenCalledOnce();
193+
});
194+
195+
it('should alias UNSAFE_* method to non-prefixed variant', () => {
196+
let inst;
197+
class Foo extends React.Component {
198+
// eslint-disable-next-line camelcase
199+
UNSAFE_componentWillMount() {}
200+
// eslint-disable-next-line camelcase
201+
UNSAFE_componentWillReceiveProps() {}
202+
// eslint-disable-next-line camelcase
203+
UNSAFE_componentWillUpdate() {}
204+
render() {
205+
inst = this;
206+
return <div>foo</div>;
207+
}
208+
}
209+
210+
React.render(<Foo />, scratch);
211+
212+
expect(inst.UNSAFE_componentWillMount).to.equal(inst.componentWillMount);
213+
expect(inst.UNSAFE_componentWillReceiveProps).to.equal(
214+
inst.UNSAFE_componentWillReceiveProps
215+
);
216+
expect(inst.UNSAFE_componentWillUpdate).to.equal(
217+
inst.UNSAFE_componentWillUpdate
218+
);
219+
});
220+
221+
it('should call UNSAFE_* methods through Suspense with wrapper component #2525', () => {
222+
class Page extends React.Component {
223+
UNSAFE_componentWillMount() {}
224+
render() {
225+
return <h1>Example</h1>;
226+
}
227+
}
228+
229+
const Wrapper = () => <Page />;
230+
231+
vi.spyOn(Page.prototype, 'UNSAFE_componentWillMount');
232+
233+
React.render(
234+
<React.Suspense fallback={<div>fallback</div>}>
235+
<Wrapper />
236+
</React.Suspense>,
237+
scratch
238+
);
239+
240+
expect(scratch.innerHTML).to.equal('<h1>Example</h1>');
241+
expect(Page.prototype.UNSAFE_componentWillMount).toHaveBeenCalled();
242+
});
243+
});
244+
245+
describe('defaultProps', () => {
246+
it('should apply default props on initial render', () => {
247+
class WithDefaultProps extends Component {
248+
constructor(props, context) {
249+
super(props, context);
250+
expect(props).to.be.deep.equal({
251+
fieldA: 1,
252+
fieldB: 2,
253+
fieldC: 1,
254+
fieldD: 2
255+
});
256+
}
257+
render() {
258+
return <div />;
259+
}
260+
}
261+
WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
262+
React.render(
263+
<WithDefaultProps fieldA={1} fieldB={2} fieldD={2} />,
264+
scratch
265+
);
266+
});
267+
268+
it('should apply default props on rerender', () => {
269+
let doRender;
270+
class Outer extends Component {
271+
constructor() {
272+
super();
273+
this.state = { i: 1 };
274+
}
275+
componentDidMount() {
276+
doRender = () => this.setState({ i: 2 });
277+
}
278+
render(props, { i }) {
279+
return <WithDefaultProps fieldA={1} fieldB={i} fieldD={i} />;
280+
}
281+
}
282+
class WithDefaultProps extends Component {
283+
constructor(props, context) {
284+
super(props, context);
285+
this.ctor(props, context);
286+
}
287+
ctor() {}
288+
componentWillReceiveProps() {}
289+
render() {
290+
return <div />;
291+
}
292+
}
293+
WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
294+
295+
let proto = WithDefaultProps.prototype;
296+
vi.spyOn(proto, 'ctor');
297+
vi.spyOn(proto, 'componentWillReceiveProps');
298+
vi.spyOn(proto, 'render');
299+
300+
React.render(<Outer />, scratch);
301+
doRender();
302+
303+
const PROPS1 = {
304+
fieldA: 1,
305+
fieldB: 1,
306+
fieldC: 1,
307+
fieldD: 1
308+
};
309+
310+
const PROPS2 = {
311+
fieldA: 1,
312+
fieldB: 2,
313+
fieldC: 1,
314+
fieldD: 2
315+
};
316+
317+
expect(proto.ctor).toHaveBeenCalledWith(PROPS1, {});
318+
expect(proto.render).toHaveBeenCalledWith(PROPS1, {}, {});
319+
320+
rerender();
321+
322+
// expect(proto.ctor).to.have.been.calledWith(PROPS2);
323+
expect(proto.componentWillReceiveProps).toHaveBeenCalledWith(PROPS2, {});
324+
expect(proto.render).toHaveBeenCalledWith(PROPS2, {}, {});
325+
});
326+
});
78327
});

0 commit comments

Comments
 (0)