Skip to content

Commit

Permalink
Move defaultProps into preact/compat (#4657)
Browse files Browse the repository at this point in the history
* Move `defaultProps` into `preact/compat`

This will be handled in `options.vnode` for function/class components.
This hook gets called for every invocation of `jsx`/`createElement` and
`cloneElement`.

* Try it

* refactor: This is horrific but seems to work? (#4662)

---------

Co-authored-by: Ryan Christian <[email protected]>
  • Loading branch information
JoviDeCroock and rschristian committed Feb 13, 2025
1 parent cdb0341 commit 593b951
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 239 deletions.
195 changes: 144 additions & 51 deletions compat/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as _hooks from '../../hooks';
// Intentionally not using a relative path to take advantage of
// the TS version resolution mechanism
import * as preact from 'preact';
import * as preact1 from 'preact';
import { JSXInternal } from '../../src/jsx';
import * as _Suspense from './suspense';

Expand All @@ -13,6 +13,99 @@ interface SignalLike<T> {

type Signalish<T> = T | SignalLike<T>;

declare namespace preact {
export interface FunctionComponent<P = {}> {
(
props: preact1.RenderableProps<P>,
context?: any
): preact1.ComponentChildren;
displayName?: string;
defaultProps?: Partial<P> | undefined;
}

export interface ComponentClass<P = {}, S = {}> {
new (props: P, context?: any): preact1.Component<P, S>;
displayName?: string;
defaultProps?: Partial<P>;
contextType?: preact1.Context<any>;
getDerivedStateFromProps?(
props: Readonly<P>,
state: Readonly<S>
): Partial<S> | null;
getDerivedStateFromError?(error: any): Partial<S> | null;
}

export interface Component<P = {}, S = {}> {
componentWillMount?(): void;
componentDidMount?(): void;
componentWillUnmount?(): void;
getChildContext?(): object;
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
shouldComponentUpdate?(
nextProps: Readonly<P>,
nextState: Readonly<S>,
nextContext: any
): boolean;
componentWillUpdate?(
nextProps: Readonly<P>,
nextState: Readonly<S>,
nextContext: any
): void;
getSnapshotBeforeUpdate?(oldProps: Readonly<P>, oldState: Readonly<S>): any;
componentDidUpdate?(
previousProps: Readonly<P>,
previousState: Readonly<S>,
snapshot: any
): void;
componentDidCatch?(error: any, errorInfo: preact1.ErrorInfo): void;
}

export abstract class Component<P, S> {
constructor(props?: P, context?: any);

static displayName?: string;
static defaultProps?: any;
static contextType?: preact1.Context<any>;

// Static members cannot reference class type parameters. This is not
// supported in TypeScript. Reusing the same type arguments from `Component`
// will lead to an impossible state where one cannot satisfy the type
// constraint under no circumstances, see #1356.In general type arguments
// seem to be a bit buggy and not supported well at the time of this
// writing with TS 3.3.3333.
static getDerivedStateFromProps?(
props: Readonly<object>,
state: Readonly<object>
): object | null;
static getDerivedStateFromError?(error: any): object | null;

state: Readonly<S>;
props: preact1.RenderableProps<P>;
context: any;

// From https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e836acc75a78cf0655b5dfdbe81d69fdd4d8a252/types/react/index.d.ts#L402
// // We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
setState<K extends keyof S>(
state:
| ((
prevState: Readonly<S>,
props: Readonly<P>
) => Pick<S, K> | Partial<S> | null)
| (Pick<S, K> | Partial<S> | null),
callback?: () => void
): void;

forceUpdate(callback?: () => void): void;

abstract render(
props?: preact1.RenderableProps<P>,
state?: Readonly<S>,
context?: any
): preact1.ComponentChildren;
}
}

// export default React;
export = React;
export as namespace React;
Expand Down Expand Up @@ -49,32 +142,32 @@ declare namespace React {
): T;

// Preact Defaults
export import Context = preact.Context;
export import ContextType = preact.ContextType;
export import RefObject = preact.RefObject;
export import Context = preact1.Context;
export import ContextType = preact1.ContextType;
export import RefObject = preact1.RefObject;
export import Component = preact.Component;
export import FunctionComponent = preact.FunctionComponent;
export import ComponentType = preact.ComponentType;
export import ComponentType = preact1.ComponentType;
export import ComponentClass = preact.ComponentClass;
export import FC = preact.FunctionComponent;
export import createContext = preact.createContext;
export import Ref = preact.Ref;
export import createRef = preact.createRef;
export import Fragment = preact.Fragment;
export import createElement = preact.createElement;
export import cloneElement = preact.cloneElement;
export import ComponentProps = preact.ComponentProps;
export import ReactNode = preact.ComponentChild;
export import ReactElement = preact.VNode;
export import Consumer = preact.Consumer;
export import ErrorInfo = preact.ErrorInfo;
export import FC = preact1.FunctionComponent;
export import createContext = preact1.createContext;
export import Ref = preact1.Ref;
export import createRef = preact1.createRef;
export import Fragment = preact1.Fragment;
export import createElement = preact1.createElement;
export import cloneElement = preact1.cloneElement;
export import ComponentProps = preact1.ComponentProps;
export import ReactNode = preact1.ComponentChild;
export import ReactElement = preact1.VNode;
export import Consumer = preact1.Consumer;
export import ErrorInfo = preact1.ErrorInfo;

// Suspense
export import Suspense = _Suspense.Suspense;
export import lazy = _Suspense.lazy;

// Compat
export import StrictMode = preact.Fragment;
export import StrictMode = preact1.Fragment;
export const version: string;
export function startTransition(cb: () => void): void;

Expand All @@ -83,15 +176,15 @@ declare namespace React {
extends JSXInternal.HTMLAttributes<T> {}
export interface HTMLProps<T extends EventTarget>
extends JSXInternal.AllHTMLAttributes<T>,
preact.ClassAttributes<T> {}
preact1.ClassAttributes<T> {}
export interface AllHTMLAttributes<T extends EventTarget>
extends JSXInternal.AllHTMLAttributes<T> {}
export import DetailedHTMLProps = JSXInternal.DetailedHTMLProps;
export import CSSProperties = JSXInternal.CSSProperties;

export interface SVGProps<T extends EventTarget>
extends JSXInternal.SVGAttributes<T>,
preact.ClassAttributes<T> {}
preact1.ClassAttributes<T> {}

interface SVGAttributes extends JSXInternal.SVGAttributes {}

Expand Down Expand Up @@ -186,81 +279,81 @@ declare namespace React {
export import TransitionEventHandler = JSXInternal.TransitionEventHandler;

export function createPortal(
vnode: preact.ComponentChildren,
container: preact.ContainerNode
): preact.VNode<any>;
vnode: preact1.ComponentChildren,
container: preact1.ContainerNode
): preact1.VNode<any>;

export function render(
vnode: preact.ComponentChild,
parent: preact.ContainerNode,
vnode: preact1.ComponentChild,
parent: preact1.ContainerNode,
callback?: () => void
): Component | null;

export function hydrate(
vnode: preact.ComponentChild,
parent: preact.ContainerNode,
vnode: preact1.ComponentChild,
parent: preact1.ContainerNode,
callback?: () => void
): Component | null;

export function unmountComponentAtNode(
container: preact.ContainerNode
container: preact1.ContainerNode
): boolean;

export function createFactory(
type: preact.VNode<any>['type']
type: preact1.VNode<any>['type']
): (
props?: any,
...children: preact.ComponentChildren[]
) => preact.VNode<any>;
...children: preact1.ComponentChildren[]
) => preact1.VNode<any>;
export function isValidElement(element: any): boolean;
export function isFragment(element: any): boolean;
export function isMemo(element: any): boolean;
export function findDOMNode(
component: preact.Component | Element
component: preact1.Component | Element
): Element | null;

export abstract class PureComponent<
P = {},
S = {},
SS = any
> extends preact.Component<P, S> {
> extends preact1.Component<P, S> {
isPureReactComponent: boolean;
}

export type MemoExoticComponent<C extends preact.FunctionalComponent<any>> =
preact.FunctionComponent<ComponentProps<C>> & {
export type MemoExoticComponent<C extends preact1.FunctionalComponent<any>> =
preact1.FunctionComponent<ComponentProps<C>> & {
readonly type: C;
};

export function memo<P = {}>(
component: preact.FunctionalComponent<P>,
component: preact1.FunctionalComponent<P>,
comparer?: (prev: P, next: P) => boolean
): preact.FunctionComponent<P>;
export function memo<C extends preact.FunctionalComponent<any>>(
): preact1.FunctionComponent<P>;
export function memo<C extends preact1.FunctionalComponent<any>>(
component: C,
comparer?: (
prev: preact.ComponentProps<C>,
next: preact.ComponentProps<C>
prev: preact1.ComponentProps<C>,
next: preact1.ComponentProps<C>
) => boolean
): C;

export interface RefAttributes<R> extends preact.Attributes {
ref?: preact.Ref<R> | undefined;
export interface RefAttributes<R> extends preact1.Attributes {
ref?: preact1.Ref<R> | undefined;
}

export interface ForwardFn<P = {}, T = any> {
(props: P, ref: ForwardedRef<T>): preact.ComponentChild;
(props: P, ref: ForwardedRef<T>): preact1.ComponentChild;
displayName?: string;
}

export interface ForwardRefExoticComponent<P>
extends preact.FunctionComponent<P> {
extends preact1.FunctionComponent<P> {
defaultProps?: Partial<P> | undefined;
}

export function forwardRef<R, P = {}>(
fn: ForwardFn<P, R>
): preact.FunctionalComponent<PropsWithoutRef<P> & { ref?: preact.Ref<R> }>;
): preact1.FunctionalComponent<PropsWithoutRef<P> & { ref?: preact1.Ref<R> }>;

export type PropsWithoutRef<P> = Omit<P, 'ref'>;

Expand Down Expand Up @@ -308,21 +401,21 @@ declare namespace React {
export function flushSync<A, R>(fn: (a: A) => R, a: A): R;

export type PropsWithChildren<P = unknown> = P & {
children?: preact.ComponentChildren | undefined;
children?: preact1.ComponentChildren | undefined;
};

export const Children: {
map<T extends preact.ComponentChild, R>(
map<T extends preact1.ComponentChild, R>(
children: T | T[],
fn: (child: T, i: number) => R
): R[];
forEach<T extends preact.ComponentChild>(
forEach<T extends preact1.ComponentChild>(
children: T | T[],
fn: (child: T, i: number) => void
): void;
count: (children: preact.ComponentChildren) => number;
only: (children: preact.ComponentChildren) => preact.ComponentChild;
toArray: (children: preact.ComponentChildren) => preact.VNode<{}>[];
count: (children: preact1.ComponentChildren) => number;
only: (children: preact1.ComponentChildren) => preact1.ComponentChild;
toArray: (children: preact1.ComponentChildren) => preact1.VNode<{}>[];
};

// scheduler
Expand Down
10 changes: 9 additions & 1 deletion compat/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
useSyncExternalStore,
useTransition
} from './index';
import { assign } from './util';

export const REACT_ELEMENT_TYPE = Symbol.for('react.element');

Expand Down Expand Up @@ -237,8 +238,15 @@ options.vnode = vnode => {
// only normalize props on Element nodes
if (typeof vnode.type === 'string') {
handleDomVNode(vnode);
} else if (typeof vnode.type === 'function' && vnode.type.defaultProps) {
let normalizedProps = assign({}, vnode.props);
for (let i in vnode.type.defaultProps) {
if (normalizedProps[i] === undefined) {
normalizedProps[i] = vnode.type.defaultProps[i];
}
}
vnode.props = normalizedProps;
}

vnode.$$typeof = REACT_ELEMENT_TYPE;

if (oldVNodeHook) oldVNodeHook(vnode);
Expand Down
Loading

0 comments on commit 593b951

Please sign in to comment.