Skip to content

DOM Node APIs#844

Open
Freddy03h wants to merge 4 commits intomainfrom
dom-node-api
Open

DOM Node APIs#844
Freddy03h wants to merge 4 commits intomainfrom
dom-node-api

Conversation

@Freddy03h
Copy link
Member

Blog Post : Starting from React Native 0.82, native components will provide DOM-like nodes via refs.
Documentation : Nodes from refs


I tried to follow the approach used in experimental-rescript-webapi but I hope this rescript feature for tagged variants can be addressed soon, as it would provide a better DX.

In the meantime, I added a NodeTypeHelper module to help with type conversions.
I know it doesn’t follow the zero-runtime binding rule, but while experimenting with experimental-rescript-webapi, I found this kind of helper quite useful, and it would otherwise have to be reimplemented in every project.

@Freddy03h
Copy link
Member Author

Freddy03h commented Mar 10, 2026

Migration should be easy if the rescript feature for tagged variants is accepted.

Example with the current PR

let textData = switch element.childNodes
->DOMApi.NodeList.item(0)
->Null.map(node => node->DOMApi.NodeTypeHelper.findNodeType) {
| Null.Value(Text(text)) => text.data
| _ => "not a text node"
}

Example with the rescript feature for tagged variants.

let textData = switch element.childNodes->DOMApi.NodeList.item(0) {
| Null.Value(Text(text)) => text.data
| _ => "not a text node"
}

@fhammerschmidt fhammerschmidt requested a review from cknitt March 12, 2026 16:36

module Document = {
include Node.Impl({
type t = element
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't that be

Suggested change
type t = element
type t = document

?

| Document(document)
| Unknown

let findNodeType = (node: node) =>
Copy link
Member

Choose a reason for hiding this comment

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

This could be called classify like similar functions in the stdlib. And the module could be NodeType. So that you would have NodeType.classify returning a NodeType.t.

@cknitt
Copy link
Member

cknitt commented Mar 18, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c58b72837

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

type t = element
})

include DOMAPI.Element.Impl({

Choose a reason for hiding this comment

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

P1 Badge Do not expose DOM ref APIs to supported 0.81 apps

NativeElement now includes the new DOM-style methods on every ref, but this package still advertises react-native >=0.81.0 support. React Native only started returning DOM-like nodes from refs in 0.82, so an app that is still on 0.81 can compile against these bindings and then hit undefined is not a function at runtime as soon as it calls one of the newly exposed methods.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

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

It will be done before release

@send
external compareDocumentPosition: (T.t, node) => int = "compareDocumentPosition"
@send external contains: (T.t, node) => bool = "contains"
@send external getRootNode: T.t => T.t = "getRootNode"

Choose a reason for hiding this comment

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

P1 Badge Return a node from getRootNode

getRootNode() is typed as returning T.t, but for mounted element and text nodes the root is the document node, not another element/text node. The current signature is only correct for the detached-node case mentioned in the React Native docs, so common mounted calls will be typed as element/text and let callers use subtype-specific fields on a document object at runtime.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

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

Should be node

type t = document
})

@send external getElementById: (document, string) => element = "getElementById"

Choose a reason for hiding this comment

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

P2 Badge Make getElementById nullable

getElementById() can legitimately miss when no native component has the requested id/nativeID, but this binding models it as always returning element. That makes the absence case impossible to express in ReScript and turns a normal lookup miss into a null dereference when consumers immediately access fields or methods on the result.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

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

good catch, I'll also open a PR on webapi

type t = text
})

@send external substringData: (text, ~offset: int, ~count: int) => unit = "substringData"

Choose a reason for hiding this comment

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

P2 Badge Return the substring from Text.substringData

substringData() returns the extracted text, but the external is declared as unit. As written, the new Text-node binding silently discards React Native's return value, so callers cannot actually read any substring from a text node through this API.

Useful? React with 👍 / 👎.

@cknitt
Copy link
Member

cknitt commented Mar 18, 2026

@Freddy03h I think Codex has some good points here, can you check them?

@Freddy03h Freddy03h marked this pull request as ready for review March 18, 2026 19:15
@cknitt cknitt mentioned this pull request Mar 19, 2026
@Freddy03h
Copy link
Member Author

I have a concern:

// NativeElement.res
type element = DOMAPI.element

// ScrollViewElement.res
type element

If I change ScrollView’s element to DOMAPI.element, it means we could use a ScrollView method on a View.
Is there a way to prevent that? Thanks.

@cknitt
Copy link
Member

cknitt commented Mar 20, 2026

That is a good point.

I think we need to keep the element types abstract to avoid this issue, and offer "casts" using %identity, like

scrollViewElement->ScrollViewElement.asNativeElement->NativeElement.asDOMElement

and/or directly

scrollViewElement->ScrollViewElement.asDOMElement

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants