Comprehensive DOM operations including rendering, hydration, portals, and complete props system supporting all HTML, SVG, and accessibility attributes.
/* DOM utilities */
[@bs.val] [@bs.return nullable]
external querySelector: string => option(Dom.element) = "document.querySelector";
/* Rendering */
[@bs.module "react-dom"]
external render: (React.element, Dom.element) => unit = "render";
[@bs.module "react-dom"]
external hydrate: (React.element, Dom.element) => unit = "hydrate";
[@bs.module "react-dom"]
external unmountComponentAtNode: Dom.element => unit = "unmountComponentAtNode";
/* Portals */
[@bs.module "react-dom"]
external createPortal: (React.element, Dom.element) => React.element = "createPortal";
/* Utilities */
external domElementToObj: Dom.element => Js.t({..}) = "%identity";Usage Examples:
/* Basic rendering */
let root = ReactDOM.querySelector("#root");
switch (root) {
| Some(element) => ReactDOM.render(<App />, element)
| None => Js.log("Root element not found")
}
/* Server-side rendering hydration */
let container = ReactDOM.querySelector("#app");
switch (container) {
| Some(element) => ReactDOM.hydrate(<App />, element)
| None => ()
}
/* Creating portals */
[@react.component]
let make = (~children) => {
let modalRoot = ReactDOM.querySelector("#modal-root");
switch (modalRoot) {
| Some(element) => ReactDOM.createPortal(children, element)
| None => React.null
}
}module Experimental = {
type root;
[@bs.module "react-dom"]
external createRoot: Dom.element => root = "createRoot";
[@bs.module "react-dom"]
external createBlockingRoot: Dom.element => root = "createBlockingRoot";
[@bs.send]
external render: (root, React.element) => unit = "render";
};Usage Example:
let container = ReactDOM.querySelector("#root");
switch (container) {
| Some(element) => {
let root = ReactDOM.Experimental.createRoot(element);
ReactDOM.Experimental.render(root, <App />);
}
| None => ()
}type domRef;
type style = ReactDOMStyle.t;
module Ref = {
type t = domRef;
type currentDomRef = React.ref(Js.nullable(Dom.element));
type callbackDomRef = Js.nullable(Dom.element) => unit;
external domRef: currentDomRef => domRef = "%identity";
external callbackDomRef: callbackDomRef => domRef = "%identity";
};The domProps type includes comprehensive support for all HTML, SVG, and ARIA attributes:
module Props = {
[@bs.deriving abstract]
type domProps = {
/* Core attributes */
[@bs.optional] key: string,
[@bs.optional] ref: domRef,
[@bs.optional] className: string,
[@bs.optional] id: string,
[@bs.optional] style: style,
/* Content and text */
[@bs.optional] children: React.element,
[@bs.optional] dangerouslySetInnerHTML: {. "__html": string},
/* Form attributes */
[@bs.optional] value: string,
[@bs.optional] defaultValue: string,
[@bs.optional] checked: bool,
[@bs.optional] defaultChecked: bool,
[@bs.optional] placeholder: string,
[@bs.optional] disabled: bool,
[@bs.optional] readOnly: bool,
[@bs.optional] required: bool,
[@bs.optional] autoFocus: bool,
[@bs.optional] autoComplete: string,
[@bs.optional] name: string,
[@bs.optional] type_: string,
[@bs.optional] accept: string,
[@bs.optional] multiple: bool,
[@bs.optional] min: string,
[@bs.optional] max: string,
[@bs.optional] step: string,
[@bs.optional] pattern: string,
[@bs.optional] minLength: int,
[@bs.optional] maxLength: int,
[@bs.optional] size: int,
[@bs.optional] rows: int,
[@bs.optional] cols: int,
[@bs.optional] wrap: string,
/* Link and media attributes */
[@bs.optional] href: string,
[@bs.optional] target: string,
[@bs.optional] rel: string,
[@bs.optional] download: string,
[@bs.optional] src: string,
[@bs.optional] srcSet: string,
[@bs.optional] alt: string,
[@bs.optional] width: string,
[@bs.optional] height: string,
[@bs.optional] poster: string,
[@bs.optional] preload: string,
[@bs.optional] autoPlay: bool,
[@bs.optional] controls: bool,
[@bs.optional] loop: bool,
[@bs.optional] muted: bool,
/* Layout and styling */
[@bs.optional] hidden: bool,
[@bs.optional] title: string,
[@bs.optional] lang: string,
[@bs.optional] dir: string,
[@bs.optional] tabIndex: int,
[@bs.optional] role: string,
[@bs.optional] draggable: bool,
[@bs.optional] contentEditable: bool,
[@bs.optional] spellCheck: bool,
[@bs.optional] translate: string,
/* Data attributes */
[@bs.optional] [@bs.as "data-testid"] dataTestId: string,
/* Event handlers */
[@bs.optional] onClick: ReactEvent.Mouse.t => unit,
[@bs.optional] onDoubleClick: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseDown: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseUp: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseEnter: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseLeave: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseMove: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseOut: ReactEvent.Mouse.t => unit,
[@bs.optional] onMouseOver: ReactEvent.Mouse.t => unit,
[@bs.optional] onChange: ReactEvent.Form.t => unit,
[@bs.optional] onInput: ReactEvent.Form.t => unit,
[@bs.optional] onSubmit: ReactEvent.Form.t => unit,
[@bs.optional] onReset: ReactEvent.Form.t => unit,
[@bs.optional] onInvalid: ReactEvent.Form.t => unit,
[@bs.optional] onKeyDown: ReactEvent.Keyboard.t => unit,
[@bs.optional] onKeyPress: ReactEvent.Keyboard.t => unit,
[@bs.optional] onKeyUp: ReactEvent.Keyboard.t => unit,
[@bs.optional] onFocus: ReactEvent.Focus.t => unit,
[@bs.optional] onBlur: ReactEvent.Focus.t => unit,
[@bs.optional] onScroll: ReactEvent.UI.t => unit,
[@bs.optional] onWheel: ReactEvent.Wheel.t => unit,
[@bs.optional] onDrag: ReactEvent.Drag.t => unit,
[@bs.optional] onDragEnd: ReactEvent.Drag.t => unit,
[@bs.optional] onDragEnter: ReactEvent.Drag.t => unit,
[@bs.optional] onDragExit: ReactEvent.Drag.t => unit,
[@bs.optional] onDragLeave: ReactEvent.Drag.t => unit,
[@bs.optional] onDragOver: ReactEvent.Drag.t => unit,
[@bs.optional] onDragStart: ReactEvent.Drag.t => unit,
[@bs.optional] onDrop: ReactEvent.Drag.t => unit,
[@bs.optional] onTouchCancel: ReactEvent.Touch.t => unit,
[@bs.optional] onTouchEnd: ReactEvent.Touch.t => unit,
[@bs.optional] onTouchMove: ReactEvent.Touch.t => unit,
[@bs.optional] onTouchStart: ReactEvent.Touch.t => unit,
[@bs.optional] onPointerDown: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerMove: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerUp: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerCancel: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerEnter: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerLeave: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerOver: ReactEvent.Pointer.t => unit,
[@bs.optional] onPointerOut: ReactEvent.Pointer.t => unit,
[@bs.optional] onCopy: ReactEvent.Clipboard.t => unit,
[@bs.optional] onCut: ReactEvent.Clipboard.t => unit,
[@bs.optional] onPaste: ReactEvent.Clipboard.t => unit,
[@bs.optional] onCompositionEnd: ReactEvent.Composition.t => unit,
[@bs.optional] onCompositionStart: ReactEvent.Composition.t => unit,
[@bs.optional] onCompositionUpdate: ReactEvent.Composition.t => unit,
[@bs.optional] onSelect: ReactEvent.Selection.t => unit,
[@bs.optional] onLoad: ReactEvent.Image.t => unit,
[@bs.optional] onError: ReactEvent.Image.t => unit,
[@bs.optional] onAnimationStart: ReactEvent.Animation.t => unit,
[@bs.optional] onAnimationEnd: ReactEvent.Animation.t => unit,
[@bs.optional] onAnimationIteration: ReactEvent.Animation.t => unit,
[@bs.optional] onTransitionEnd: ReactEvent.Transition.t => unit,
};
/* Alternative props type with callback ref */
[@bs.deriving abstract]
type props = {
/* Same as domProps but with callback ref */
[@bs.optional] ref: Js.nullable(Dom.element) => unit,
/* ... all other props same as domProps */
};
};Complete ARIA accessibility attribute support:
/* ARIA attributes (partial list - see domProps for complete set) */
[@bs.optional] [@bs.as "aria-label"] ariaLabel: string,
[@bs.optional] [@bs.as "aria-labelledby"] ariaLabelledby: string,
[@bs.optional] [@bs.as "aria-describedby"] ariaDescribedby: string,
[@bs.optional] [@bs.as "aria-hidden"] ariaHidden: bool,
[@bs.optional] [@bs.as "aria-expanded"] ariaExpanded: bool,
[@bs.optional] [@bs.as "aria-checked"] ariaChecked: string,
[@bs.optional] [@bs.as "aria-selected"] ariaSelected: bool,
[@bs.optional] [@bs.as "aria-disabled"] ariaDisabled: bool,
[@bs.optional] [@bs.as "aria-pressed"] ariaPressed: string,
[@bs.optional] [@bs.as "aria-current"] ariaCurrent: string,
[@bs.optional] [@bs.as "aria-live"] ariaLive: string,
[@bs.optional] [@bs.as "aria-atomic"] ariaAtomic: bool,
[@bs.optional] [@bs.as "aria-busy"] ariaBusy: bool,
[@bs.optional] [@bs.as "aria-relevant"] ariaRelevant: string,Complete SVG attribute support:
/* SVG attributes (partial list) */
[@bs.optional] fill: string,
[@bs.optional] stroke: string,
[@bs.optional] strokeWidth: string,
[@bs.optional] strokeDasharray: string,
[@bs.optional] strokeDashoffset: string,
[@bs.optional] strokeLinecap: string,
[@bs.optional] strokeLinejoin: string,
[@bs.optional] opacity: string,
[@bs.optional] fillOpacity: string,
[@bs.optional] strokeOpacity: string,
[@bs.optional] transform: string,
[@bs.optional] viewBox: string,
[@bs.optional] preserveAspectRatio: string,
[@bs.optional] x: string,
[@bs.optional] y: string,
[@bs.optional] x1: string,
[@bs.optional] y1: string,
[@bs.optional] x2: string,
[@bs.optional] y2: string,
[@bs.optional] cx: string,
[@bs.optional] cy: string,
[@bs.optional] r: string,
[@bs.optional] rx: string,
[@bs.optional] ry: string,
[@bs.optional] d: string,
[@bs.optional] points: string,/* String-based DOM element creation */
external stringToComponent: string => React.component(domProps) = "%identity";
/* Create DOM elements with props */
external createDOMElementVariadic: (string, ~props: domProps=?, array(React.element)) => React.element = "createElement";[@react.component]
let make = () => {
let (name, setName) = React.useState(() => "");
let (email, setEmail) = React.useState(() => "");
let (isSubmitting, setIsSubmitting) = React.useState(() => false);
let handleSubmit = (event: ReactEvent.Form.t) => {
ReactEvent.Form.preventDefault(event);
setIsSubmitting(_ => true);
submitForm(name, email)
|> Promise.then_(_ => {
setIsSubmitting(_ => false);
Promise.resolve();
})
|> ignore;
};
<form onSubmit=handleSubmit>
<div>
<label htmlFor="name" ariaLabel="Full name">
{React.string("Name:")}
</label>
<input
id="name"
type_="text"
value=name
onChange={(event) => {
let value = ReactEvent.Form.target(event)##value;
setName(_ => value);
}}
required=true
autoComplete="name"
placeholder="Enter your full name"
/>
</div>
<div>
<label htmlFor="email" ariaLabel="Email address">
{React.string("Email:")}
</label>
<input
id="email"
type_="email"
value=email
onChange={(event) => {
let value = ReactEvent.Form.target(event)##value;
setEmail(_ => value);
}}
required=true
autoComplete="email"
placeholder="Enter your email"
/>
</div>
<button
type_="submit"
disabled=isSubmitting
ariaLabel="Submit form"
>
{React.string(isSubmitting ? "Submitting..." : "Submit")}
</button>
</form>
}[@react.component]
let make = (~isOpen: bool, ~onClose: unit => unit) => {
let dialogRef = React.useRef(Js.Nullable.null);
React.useEffect1(() => {
if (isOpen) {
switch (dialogRef.current->Js.Nullable.toOption) {
| Some(element) => element->focus
| None => ()
};
};
None;
}, [|isOpen|]);
if (isOpen) {
ReactDOM.createPortal(
<div
className="modal-overlay"
role="dialog"
ariaModal=true
ariaLabelledby="dialog-title"
ariaDescribedby="dialog-description"
ref={ReactDOM.Ref.domRef(dialogRef)}
tabIndex=(-1)
onClick={(event) => {
if (ReactEvent.Mouse.target(event) === ReactEvent.Mouse.currentTarget(event)) {
onClose();
}
}}
>
<div className="modal-content">
<h2 id="dialog-title">
{React.string("Confirmation")}
</h2>
<p id="dialog-description">
{React.string("Are you sure you want to continue?")}
</p>
<div>
<button onClick={_ => onClose()} autoFocus=true>
{React.string("Cancel")}
</button>
<button onClick={_ => onClose()}>
{React.string("Confirm")}
</button>
</div>
</div>
</div>,
querySelector("#modal-root")->Option.getWithDefault(Dom.document##body)
)
} else {
React.null
}
}[@react.component]
let make = () => {
let inputRef = React.useRef(Js.Nullable.null);
let (measurements, setMeasurements) = React.useState(() => None);
let measureInput = () => {
switch (inputRef.current->Js.Nullable.toOption) {
| Some(element) => {
let rect = element##getBoundingClientRect();
setMeasurements(_ => Some({
width: rect##width,
height: rect##height,
}));
}
| None => ()
}
};
<div>
<input
ref={ReactDOM.Ref.domRef(inputRef)}
placeholder="Measure me"
onFocus={_ => measureInput()}
/>
<button onClick={_ => measureInput()}>
{React.string("Get Measurements")}
</button>
{switch (measurements) {
| Some({width, height}) =>
<p> {React.string(j`Width: $width, Height: $height`)} </p>
| None => React.null
}}
</div>
}