Skip to content

React 19 APIs #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/React.bs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 45 additions & 3 deletions src/React.res
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type element = Jsx.element
external float: float => element = "%identity"
external int: int => element = "%identity"
external string: string => element = "%identity"
external promise: promise<element> => element = "%identity"

external array: array<element> => element = "%identity"

Expand Down Expand Up @@ -309,10 +310,9 @@ external useImperativeHandle7: (

@module("react") external useId: unit => string = "useId"

@module("react") external useDeferredValue: 'value => 'value = "useDeferredValue"

/** `useDeferredValue` is a React Hook that lets you defer updating a part of the UI. */
@module("react")
external useTransition: unit => (bool, (unit => unit) => unit) = "useTransition"
external useDeferredValue: ('value, ~initialValue: 'value=?) => 'value = "useDeferredValue"

@module("react")
external useInsertionEffectOnEveryRender: (unit => option<unit => unit>) => unit =
Expand Down Expand Up @@ -405,3 +405,45 @@ external setDisplayName: (component<'props>, string) => unit = "displayName"

@get @return(nullable)
external displayName: component<'props> => option<string> = "displayName"

// Actions

type transitionFunction = unit => promise<unit>

type transitionStartFunction = transitionFunction => unit

/** `useTransition` is a React Hook that lets you render a part of the UI in the background. */
@module("react")
external useTransition: unit => (bool, transitionStartFunction) = "useTransition"

type action<'state, 'payload> = ('state, 'payload) => promise<'state>

type formAction<'formData> = 'formData => promise<unit>

/** `useActionState` is a Hook that allows you to update state based on the result of a form action. */
@module("react")
external useActionState: (
action<'state, 'payload>,
'state,
~permalink: string=?,
) => ('state, formAction<'payload>, bool) = "useActionState"

/** `useOptimistic` is a React Hook that lets you optimistically update the UI. */
@module("react")
external useOptimistic: ('state, ('state, 'action) => 'state) => ('state, 'action => unit) =
"useOptimistic"

module Usable = {
type t<'value>

external context: Context.t<'value> => t<'value> = "%identity"
external promise: promise<'value> => t<'value> = "%identity"
}

/** `use` is a React API that lets you read the value of a resource like a Promise or context. */
@module("react")
external use: Usable.t<'value> => 'value = "use"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good idea? Or should we just have multiple bindings for use (useContext, usePromise, ...).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe multiple bindings is more ergonomic. With good code examples what they do, the difference in name is doable.


/** `act` is a test helper to apply pending React updates before making assertions. */
@module("react")
external act: (unit => promise<unit>) => promise<unit> = "act"
6 changes: 3 additions & 3 deletions src/ReactDOM.bs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

147 changes: 146 additions & 1 deletion src/ReactDOM.res
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ module Client = {
external hydrateRoot: (Dom.element, React.element) => Root.t = "hydrateRoot"
}

// Very rudimentary form data bindings
module FormData = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is certainly not ideal, but I don't want to drag the new WebAPI in yet.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense for now. The folks that do use the new WebAPI can always convert it.

type t
type value

@new external make: unit => t = "FormData"

@send external append: (t, string, ~filename: string=?) => unit = "append"
@send external delete: (t, string) => unit = "delete"
@send external get: (t, string) => option<value> = "get"
@send external getAll: (t, string) => array<value> = "getAll"
@send external set: (string, string) => unit = "set"
@send external has: string => bool = "has"
// @send external keys: t => Iterator.t<string> = "keys";
// @send external values: t => Iterator.t<value> = "values";
}

@module("react-dom")
external createPortal: (React.element, Dom.element) => React.element = "createPortal"

Expand All @@ -37,12 +54,140 @@ type domRef = JsxDOM.domRef
module Ref = {
type t = domRef
type currentDomRef = React.ref<Js.nullable<Dom.element>>
type callbackDomRef = Js.nullable<Dom.element> => unit
type callbackDomRef = Js.nullable<Dom.element> => option<unit => unit>

external domRef: currentDomRef => domRef = "%identity"
external callbackDomRef: callbackDomRef => domRef = "%identity"
}

// Hooks

type formStatus<'state> = {
/** If true, this means the parent <form> is pending submission. Otherwise, false. */
pending: bool,
/** An object implementing the FormData interface that contains the data the parent <form> is submitting. If there is no active submission or no parent <form>, it will be null. */
data: FormData.t,
/** This represents whether the parent <form> is submitting with either a GET or POST HTTP method. By default, a <form> will use the GET method and can be specified by the method property. */
method: [#get | #post],
/** A reference to the function passed to the action prop on the parent <form>. If there is no parent <form>, the property is null. If there is a URI value provided to the action prop, or no action prop specified, status.action will be null. */
action: React.action<'state, FormData.t>,
}

/** `useFormStatus` is a Hook that gives you status information of the last form submission. */
@module("react-dom")
external useFormStatus: unit => formStatus<'state> = "useFormStatus"

// Resource Preloading APIs

/** The CORS policy to use. */
type crossOrigin = [
| #anonymous
| #"use-credentials"
]

/** The Referrer header to send when fetching. */
type referrerPolicy = [
| #"referrer-when-downgrade"
| #"no-referrer"
| #origin
| #"origin-when-cross-origin"
| #"unsafe-url"
]

/** Suggests a relative priority for fetching the resource. */
type fetchPriority = [#auto | #high | #low]

/** `prefetchDNS` lets you eagerly look up the IP of a server that you expect to load resources from. */
@module("react-dom")
external prefetchDNS: string => unit = "prefetchDNS"

/** `preconnect` lets you eagerly connect to a server that you expect to load resources from. */
@module("react-dom")
external preconnect: string => unit = "preconnect"

type preloadOptions = {
/** The type of resource. */
@as("as")
as_: [
| #audio
| #document
| #embed
| #fetch
| #font
| #image
| #object
| #script
| #style
| #track
| #video
| #worker
],
/** The CORS policy to use. It is required when as is set to "fetch". */
crossOrigin?: crossOrigin,
/** The Referrer header to send when fetching. */
referrerPolicy?: referrerPolicy,
/** A cryptographic hash of the resource, to verify its authenticity. */
integrity?: string,
/** The MIME type of the resource. */
@as("type")
type_?: string,
/** A cryptographic nonce to allow the resource when using a strict Content Security Policy. */
nonce?: string,
/** Suggests a relative priority for fetching the resource. */
fetchPriority?: fetchPriority,
/** For use only with as: "image". Specifies the source set of the image. */
imageSrcSet?: string,
/** For use only with as: "image". Specifies the sizes of the image. */
imageSizes?: string,
}

/** `preload` lets you eagerly fetch a resource such as a stylesheet, font, or external script that you expect to use. */
@module("react-dom")
external preload: (string, preloadOptions) => unit = "preload"

type preloadModuleOptions = {
/** The type of resource. */
@as("as")
as_: [#script],
/** The CORS policy to use. It is required when as is set to "fetch". */
crossOrigin?: crossOrigin,
/** A cryptographic hash of the resource, to verify its authenticity. */
integrity?: string,
/** A cryptographic nonce to allow the resource when using a strict Content Security Policy. */
nonce?: string,
}

/** `preloadModule` lets you eagerly fetch an ESM module that you expect to use. */
@module("react-dom")
external preloadModule: (string, preloadModuleOptions) => unit = "preloadModule"

type preinitOptions = {
/** The type of resource. */
@as("as")
as_: [#script | #style],
/** Required with stylesheets. Says where to insert the stylesheet relative to others. Stylesheets with higher precedence can override those with lower precedence. */
precedence?: [#reset | #low | #medium | #high],
/** The CORS policy to use. It is required when as is set to "fetch". */
crossOrigin?: crossOrigin,
/** The Referrer header to send when fetching. */
referrerPolicy?: referrerPolicy,
/** A cryptographic hash of the resource, to verify its authenticity. */
integrity?: string,
nonce?: string,
/** Suggests a relative priority for fetching the resource. */
fetchPriority?: fetchPriority,
}

/** `preinit` lets you eagerly fetch and evaluate a stylesheet or external script. */
@module("react-dom")
external preinit: (string, preinitOptions) => unit = "preinit"

/** To preinit an ESM module, call the `preinitModule` function from react-dom. */
@module("react-dom")
external preinitModule: (string, preloadModuleOptions) => unit = "preinitModule"

// Runtime

type domProps = JsxDOM.domProps

@variadic @module("react")
Expand Down