Skip to content

Commit

Permalink
[react] Enable Awaited<ReactNode> in experimental release channels (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored Mar 11, 2024
1 parent bb58be8 commit bf659ae
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 6 deletions.
12 changes: 12 additions & 0 deletions types/react-dom/test/canary-tests.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference types="../canary"/>
import React = require("react");
import ReactDOM = require("react-dom");
import ReactDOMClient = require("react-dom/client");

Expand Down Expand Up @@ -167,6 +168,17 @@ function formTest() {
Promise.resolve(0),
)[0];

useFormState(
async (state: React.ReactNode, payload: FormData): Promise<React.ReactNode> => {
return state;
},
(
<button>
New Project
</button>
),
);

return (
<button
onClick={() => {
Expand Down
18 changes: 15 additions & 3 deletions types/react/experimental.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,22 @@ declare const UNDEFINED_VOID_ONLY: unique symbol;
type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never };

declare module "." {
// Need an interface to not cause ReactNode to be a self-referential type.
interface PromiseLikeOfReactNode extends PromiseLike<ReactNode> {}
/**
* @internal Use `Awaited<ReactNode>` instead
*/
// Helper type to enable `Awaited<ReactNode>`.
// Must be a copy of the non-thenables of `ReactNode`.
type AwaitedReactNode =
| ReactElement
| string
| number
| Iterable<AwaitedReactNode>
| ReactPortal
| boolean
| null
| undefined;
interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES {
promises: PromiseLikeOfReactNode;
promises: Promise<AwaitedReactNode>;
}

export interface SuspenseProps {
Expand Down
1 change: 1 addition & 0 deletions types/react/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ declare namespace React {
* <Component customElement={<div>hello</div>} />
* ```
*/
// non-thenables need to be kept in sync with AwaitedReactNode
type ReactNode =
| ReactElement
| string
Expand Down
4 changes: 4 additions & 0 deletions types/react/test/experimental.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ function useEvent() {
// @ts-expect-error plain objects are not allowed
<div>{{ dave: true }}</div>;
<div>{Promise.resolve("React")}</div>;

const asyncTests = async function asyncTests() {
const node: Awaited<React.ReactNode> = await Promise.resolve("React");
};
}

function elementTypeTests() {
Expand Down
5 changes: 5 additions & 0 deletions types/react/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,11 @@ class RenderChildren extends React.Component<{ children?: React.ReactNode }> {
const plainObject: React.ReactNode = { dave: true };
// Will not type-check in a real project but accepted in DT tests since experimental.d.ts is part of compilation.
const promise: React.ReactNode = Promise.resolve("React");

const asyncTests = async function asyncTests() {
// Will not type-check in a real project but accepted in DT tests since experimental.d.ts is part of compilation.
const node: Awaited<React.ReactNode> = await Promise.resolve("React");
};
}

const Memoized1 = React.memo(function Foo(props: { foo: string }) {
Expand Down
18 changes: 15 additions & 3 deletions types/react/ts5.0/experimental.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,22 @@ declare const UNDEFINED_VOID_ONLY: unique symbol;
type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never };

declare module "." {
// Need an interface to not cause ReactNode to be a self-referential type.
interface PromiseLikeOfReactNode extends PromiseLike<ReactNode> {}
/**
* @internal Use `Awaited<ReactNode>` instead
*/
// Helper type to enable `Awaited<ReactNode>`.
// Must be a copy of the non-thenables of `ReactNode`.
type AwaitedReactNode =
| ReactElement
| string
| number
| Iterable<AwaitedReactNode>
| ReactPortal
| boolean
| null
| undefined;
interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES {
promises: PromiseLikeOfReactNode;
promises: Promise<AwaitedReactNode>;
}

export interface SuspenseProps {
Expand Down
1 change: 1 addition & 0 deletions types/react/ts5.0/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ declare namespace React {
* <Component customElement={<div>hello</div>} />
* ```
*/
// non-thenables need to be kept in sync with AwaitedReactNode
type ReactNode =
| ReactElement
| string
Expand Down
4 changes: 4 additions & 0 deletions types/react/ts5.0/test/experimental.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ function useEvent() {
// @ts-expect-error plain objects are not allowed
<div>{{ dave: true }}</div>;
<div>{Promise.resolve("React")}</div>;

const asyncTests = async function asyncTests() {
const node: Awaited<React.ReactNode> = await Promise.resolve("React");
};
}

function elementTypeTests() {
Expand Down
5 changes: 5 additions & 0 deletions types/react/ts5.0/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,11 @@ class RenderChildren extends React.Component<{ children?: React.ReactNode }> {
const plainObject: React.ReactNode = { dave: true };
// Will not type-check in a real project but accepted in DT tests since experimental.d.ts is part of compilation.
const promise: React.ReactNode = Promise.resolve("React");

const asyncTests = async function asyncTests() {
// Will not type-check in a real project but accepted in DT tests since experimental.d.ts is part of compilation.
const node: Awaited<React.ReactNode> = await Promise.resolve("React");
};
}

const Memoized1 = React.memo(function Foo(props: { foo: string }) {
Expand Down

0 comments on commit bf659ae

Please sign in to comment.