diff --git a/.changeset/little-spies-crash.md b/.changeset/little-spies-crash.md
new file mode 100644
index 0000000000..e3a3314894
--- /dev/null
+++ b/.changeset/little-spies-crash.md
@@ -0,0 +1,5 @@
+---
+'preact': major
+---
+
+Move `defaultProps` handling to preact/compat
diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts
index 304a074a57..64bfaafeaf 100644
--- a/compat/src/index.d.ts
+++ b/compat/src/index.d.ts
@@ -4,6 +4,17 @@ import { JSXInternal } from '../../src/jsx';
import * as _Suspense from './suspense';
import * as _SuspenseList from './suspense-list';
+declare module 'preact' {
+ export namespace preact {
+ interface FunctionComponent
{
+ defaultProps?: Partial
;
+ }
+ interface ComponentClass
{
+ defaultProps?: Partial
;
+ }
+ }
+}
+
// export default React;
export = React;
export as namespace React;
diff --git a/compat/src/render.js b/compat/src/render.js
index f2f8f06326..d0fbefb16b 100644
--- a/compat/src/render.js
+++ b/compat/src/render.js
@@ -210,6 +210,12 @@ options.vnode = vnode => {
if (props.className != null) normalizedProps.class = props.className;
Object.defineProperty(normalizedProps, 'className', classNameDescriptor);
}
+ } else if (typeof type === 'function' && type.defaultProps) {
+ for (i in type.defaultProps) {
+ if (normalizedProps[i] === undefined) {
+ normalizedProps[i] = type.defaultProps[i];
+ }
+ }
}
vnode.$$typeof = REACT_ELEMENT_TYPE;
diff --git a/compat/test/browser/component.test.js b/compat/test/browser/component.test.js
index de78a1fd20..8ca3b2cc32 100644
--- a/compat/test/browser/component.test.js
+++ b/compat/test/browser/component.test.js
@@ -1,6 +1,6 @@
import { setupRerender } from 'preact/test-utils';
import { setupScratch, teardown } from '../../../test/_util/helpers';
-import React, { createElement } from 'preact/compat';
+import React, { createElement, Component } from 'preact/compat';
describe('components', () => {
/** @type {HTMLDivElement} */
@@ -240,4 +240,89 @@ describe('components', () => {
expect(Page.prototype.UNSAFE_componentWillMount).to.have.been.called;
});
});
+
+ describe('defaultProps', () => {
+ it('should apply default props on initial render', () => {
+ class WithDefaultProps extends Component {
+ constructor(props, context) {
+ super(props, context);
+ expect(props).to.be.deep.equal({
+ fieldA: 1,
+ fieldB: 2,
+ fieldC: 1,
+ fieldD: 2
+ });
+ }
+ render() {
+ return
;
+ }
+ }
+ WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
+ React.render(
+ ,
+ scratch
+ );
+ });
+
+ it('should apply default props on rerender', () => {
+ let doRender;
+ class Outer extends Component {
+ constructor() {
+ super();
+ this.state = { i: 1 };
+ }
+ componentDidMount() {
+ doRender = () => this.setState({ i: 2 });
+ }
+ render(props, { i }) {
+ return ;
+ }
+ }
+ class WithDefaultProps extends Component {
+ constructor(props, context) {
+ super(props, context);
+ this.ctor(props, context);
+ }
+ ctor() {}
+ componentWillReceiveProps() {}
+ render() {
+ return ;
+ }
+ }
+ WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
+
+ let proto = WithDefaultProps.prototype;
+ sinon.spy(proto, 'ctor');
+ sinon.spy(proto, 'componentWillReceiveProps');
+ sinon.spy(proto, 'render');
+
+ React.render(, scratch);
+ doRender();
+
+ const PROPS1 = {
+ fieldA: 1,
+ fieldB: 1,
+ fieldC: 1,
+ fieldD: 1
+ };
+
+ const PROPS2 = {
+ fieldA: 1,
+ fieldB: 2,
+ fieldC: 1,
+ fieldD: 2
+ };
+
+ expect(proto.ctor).to.have.been.calledWithMatch(PROPS1);
+ expect(proto.render).to.have.been.calledWithMatch(PROPS1);
+
+ rerender();
+
+ // expect(proto.ctor).to.have.been.calledWith(PROPS2);
+ expect(proto.componentWillReceiveProps).to.have.been.calledWithMatch(
+ PROPS2
+ );
+ expect(proto.render).to.have.been.calledWithMatch(PROPS2);
+ });
+ });
});
diff --git a/src/clone-element.js b/src/clone-element.js
index 50e778d1ec..62669d8202 100644
--- a/src/clone-element.js
+++ b/src/clone-element.js
@@ -12,6 +12,7 @@ export function cloneElement(vnode, props, children) {
key,
ref,
i;
+
for (i in props) {
if (i == 'key') key = props[i];
else if (i == 'ref') ref = props[i];
diff --git a/src/create-element.js b/src/create-element.js
index d71de9eb01..d5955d0aa1 100644
--- a/src/create-element.js
+++ b/src/create-element.js
@@ -34,16 +34,6 @@ export function createElement(type, props, children) {
normalizedProps.children = children;
}
- // If a Component VNode, check for and apply defaultProps
- // Note: type may be undefined in development, must never error here.
- if (typeof type == 'function' && type.defaultProps != null) {
- for (i in type.defaultProps) {
- if (normalizedProps[i] === UNDEFINED) {
- normalizedProps[i] = type.defaultProps[i];
- }
- }
- }
-
return createVNode(type, normalizedProps, key, ref, 0);
}
diff --git a/src/index.d.ts b/src/index.d.ts
index 16953eb386..578fd428a7 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -85,14 +85,12 @@ export type ComponentProps<
export interface FunctionComponent {
(props: RenderableProps
, context?: any): VNode | null;
displayName?: string;
- defaultProps?: Partial;
}
export interface FunctionalComponent
extends FunctionComponent
{}
export interface ComponentClass
{
new (props: P, context?: any): Component
;
displayName?: string;
- defaultProps?: Partial
;
contextType?: Context;
getDerivedStateFromProps?(
props: Readonly,
@@ -137,7 +135,6 @@ export abstract class Component
{
constructor(props?: P, context?: any);
static displayName?: string;
- static defaultProps?: any;
static contextType?: Context;
// Static members cannot reference class type parameters. This is not
diff --git a/test/browser/spec.test.js b/test/browser/spec.test.js
index ee5f3882c1..a485244fb2 100644
--- a/test/browser/spec.test.js
+++ b/test/browser/spec.test.js
@@ -16,88 +16,6 @@ describe('Component spec', () => {
teardown(scratch);
});
- describe('defaultProps', () => {
- it('should apply default props on initial render', () => {
- class WithDefaultProps extends Component {
- constructor(props, context) {
- super(props, context);
- expect(props).to.be.deep.equal({
- fieldA: 1,
- fieldB: 2,
- fieldC: 1,
- fieldD: 2
- });
- }
- render() {
- return ;
- }
- }
- WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
- render(, scratch);
- });
-
- it('should apply default props on rerender', () => {
- let doRender;
- class Outer extends Component {
- constructor() {
- super();
- this.state = { i: 1 };
- }
- componentDidMount() {
- doRender = () => this.setState({ i: 2 });
- }
- render(props, { i }) {
- return ;
- }
- }
- class WithDefaultProps extends Component {
- constructor(props, context) {
- super(props, context);
- this.ctor(props, context);
- }
- ctor() {}
- componentWillReceiveProps() {}
- render() {
- return ;
- }
- }
- WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
-
- let proto = WithDefaultProps.prototype;
- sinon.spy(proto, 'ctor');
- sinon.spy(proto, 'componentWillReceiveProps');
- sinon.spy(proto, 'render');
-
- render(, scratch);
- doRender();
-
- const PROPS1 = {
- fieldA: 1,
- fieldB: 1,
- fieldC: 1,
- fieldD: 1
- };
-
- const PROPS2 = {
- fieldA: 1,
- fieldB: 2,
- fieldC: 1,
- fieldD: 2
- };
-
- expect(proto.ctor).to.have.been.calledWithMatch(PROPS1);
- expect(proto.render).to.have.been.calledWithMatch(PROPS1);
-
- rerender();
-
- // expect(proto.ctor).to.have.been.calledWith(PROPS2);
- expect(proto.componentWillReceiveProps).to.have.been.calledWithMatch(
- PROPS2
- );
- expect(proto.render).to.have.been.calledWithMatch(PROPS2);
- });
- });
-
describe('forceUpdate', () => {
it('should force a rerender', () => {
let forceUpdate;
diff --git a/test/shared/createElement.test.js b/test/shared/createElement.test.js
index 6c0ae8dff2..8cdd0b52a8 100644
--- a/test/shared/createElement.test.js
+++ b/test/shared/createElement.test.js
@@ -270,12 +270,6 @@ describe('createElement(jsx)', () => {
.that.deep.equals(['x', 'y']);
});
- it('should respect defaultProps', () => {
- const Component = ({ children }) => children;
- Component.defaultProps = { foo: 'bar' };
- expect(h(Component).props).to.deep.equal({ foo: 'bar' });
- });
-
it('should override defaultProps', () => {
const Component = ({ children }) => children;
Component.defaultProps = { foo: 'default' };