diff --git a/CHANGES.md b/CHANGES.md index 5422e0508..7ad00672e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Unreleased +* FEATURE: add color to domProps (@tatchi in https://github.com/reasonml/reason-react/pull/871) +* BREAKING: Support for React 19 (@davesnx in #846) +* DOCS: Documentation updates for 0.16 (@davesnx in https://github.com/reasonml/reason-react/pull/864) +* INFRA: Update deps (@johnhaley81 in https://github.com/reasonml/reason-react/pull/876) +* INFRA: update setup-ocaml to v3 (@anmonteiro in https://github.com/reasonml/reason-react/pull/878) +* FIX: Remove raise annotations and fix locations on errors (@davesnx https://github.com/reasonml/reason-react/pull/863) +* FIX: type of pipeable stream to allow objects with keys (@anmonteiro in https://github.com/reasonml/reason-react/pull/854) +* FEATURE: Add `preconnect`, `prefetchDNS`, `preinit`, `preinitModule`, `preload` and `preloadModule` in ReactDOM.Experimental (@r17x in https://github.com/reasonml/reason-react/pull/849) +* BREAKING: Make lowerbound be Melange 5.1 (due to Js.FormData.t usage) + # 0.15.0 * Add `isValidElement` (@r17x in diff --git a/demo/dune b/demo/dune index 851bbbc10..69ca2cbec 100644 --- a/demo/dune +++ b/demo/dune @@ -1,6 +1,8 @@ (melange.emit (target demo) (alias melange-app) + (enabled_if + (= %{profile} dev)) (module_systems (es6 mjs)) (libraries reason-react melange.belt melange.dom) diff --git a/demo/index.html b/demo/index.html index e333157fd..c65734377 100644 --- a/demo/index.html +++ b/demo/index.html @@ -25,10 +25,10 @@ "melange.belt/": "./demo/node_modules/melange.belt/", "melange.js/": "./demo/node_modules/melange.js/", "reason-react/": "./demo/node_modules/reason-react/", - "react/jsx-runtime": "https://esm.sh/react@19.0.0-rc.1/jsx-runtime", - "react": "https://esm.sh/react@19.0.0-rc.1", - "react-dom": "https://esm.sh/react-dom@19.0.0-rc.1", - "react-dom/client": "https://esm.sh/react-dom@19.0.0-rc.1/client" + "react/jsx-runtime": "https://esm.sh/react@19.1.0/jsx-runtime?dev", + "react": "https://esm.sh/react@19.1.0?dev", + "react-dom": "https://esm.sh/react-dom@19.1.0?dev", + "react-dom/client": "https://esm.sh/react-dom@19.1.0/client?dev" } } diff --git a/demo/main.re b/demo/main.re index 8994a58dc..3edee04fb 100644 --- a/demo/main.re +++ b/demo/main.re @@ -161,6 +161,66 @@ module UseReducerNoProblemo = { }; }; +module FragmentsEverywhere = { + [@react.component] + let make = () => { + let items = ["Apple", "Banana", "Cherry", "Date"]; + let numbers = [1, 2, 3, 4, 5]; + +
+

{React.string("Fragments Everywhere")}

+ +

{React.string("This is inside a fragment")}

+ {React.string("Multiple elements together")} + {React.string("Without a wrapper div")} +
+
+

{React.string("List of items with fragments:")}

+ {items + |> List.mapi((index, item) => + + {React.string(item)} + + {React.string(" - Item #" ++ string_of_int(index + 1))} + +
+
+ ) + |> Array.of_list + |> React.array} +
+ <> +
+

{React.string("Another fragment section")}

+ + {React.string("Emphasized text")} + +
+

{React.string("Numbers with fragments:")}

+ {numbers + |> List.map(num => + +
{React.string("Number: " ++ string_of_int(num))}
+ + {React.string("Square: " ++ string_of_int(num * num))} + +
+
+ ) + |> Array.of_list + |> React.array} +
+
; + }; +}; + +module WithoutForward = { + [@react.component] + let make = (~ref=?) => { + ; + }; +}; + module App = { [@react.component] let make = (~initialValue) => { @@ -174,7 +234,9 @@ module App = { - + + + ; }; }; diff --git a/docs/jsx.md b/docs/jsx.md index caf80474a..e562c91fc 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -2,11 +2,19 @@ title: JSX --- -Reason comes with the [JSX](https://reasonml.github.io/docs/en/jsx.html) syntax! ReasonReact works very similar to how [the ReactJS JSX transform](https://reactjs.org/docs/introducing-jsx.html) does. +Reason comes with [JSX](https://reasonml.github.io/docs/en/jsx.html) syntax. Enables representation of HTML-like expressions within the language. + +reason-react enables [the ReactJS JSX transform](https://reactjs.org/docs/introducing-jsx.html) in Reason. + +Since `reason-react.0.12.0`, the JSX transformation currently supports the [New JSX Transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html). JSX functions are imported from `react/jsx-runtime`. Previous versions of reason-react used the legacy API `React.createElement`. + +# Install To use it, you would need to install [`reason-react-ppx`](https://opam.ocaml.org/packages/reason-react-ppx/) and add `(preprocess (pps reason-react-ppx))` in [`melange.emit or library`](https://dune.readthedocs.io/en/stable/melange.html) stanzas in your `dune` file. -Here's a list of transformations made by the [ppx](https://ocaml.org/docs/metaprogramming): +# What the ppx does + +Here's a list of transformations made by the [ppx](https://ocaml.org/docs/metaprogramming). ## Uncapitalized @@ -17,27 +25,26 @@ Here's a list of transformations made by the [ppx](https://ocaml.org/docs/metapr transforms into ```reason -ReactDOM.createDOMElementVariadic( +ReactDOM.jsxs( "div", - ~props=ReactDOM.domProps(~foo=bar, ()), - [|child1, child2|] -); + ReactDOM.domProps( + ~children=React.array([|child1, child2|]), + ~foo=bar, + (), + ) +) ``` which compiles to the JavaScript code: ```js -React.createElement('div', {foo: bar}, child1, child2) +React.jsx('div', {foo: bar, children: [ child1, child2 ] }) ``` Prop-less `
` transforms into ```reason -ReactDOM.createDOMElementVariadic( - "div", - ~props=ReactDOM.domProps(), - [||] -); +ReactDOM.jsx("div", ReactDOM.domProps()); ``` Which compiles to @@ -49,61 +56,63 @@ React.createElement('div', {}) ## Capitalized ```reason - {child1} {child2} + {child1} {child2} ``` transforms into ```reason -React.createElementVariadic( +React.jsxs( MyReasonComponent.make, MyReasonComponent.makeProps( - ~key=a, ~ref=b, ~foo=bar, ~baz=qux, - ~children=React.null, + ~children=[|child1, child2|], () ), - [|child1, child2|] ); ``` which compiles to ```js -React.createElement( +React.jsxs( MyReasonComponent.make, { - key: a, ref: b, foo: bar, baz: qux, - children: null, + children: [ child1, child2 ], }, - child1, - child2, ); ``` Prop-less `` transforms into ```reason -React.createElement(MyReasonComponent.make, MyReasonComponent.makeProps()); +React.jsx( + MyReasonComponent.make, + MyReasonComponent.makeProps(), +); ``` which compiles to ```js -React.createElement(MyReasonComponent.make, {}); +React.jsx(MyReasonComponent.make, {}); ``` The `make` above is exactly the same `make` function you've seen in the previous section. -`ref` and `key` are reserved in ReasonReact, just like in ReactJS. **Don't** use them as props in your component! +`ref` and `key` are reserved in reason-react, just like in ReactJS. **Don't** use them as props in your component! ## Fragment +Fragment lets you group elements without a wrapper node, and return a single element without any effect on the DOM. More details about this in the [react documentation: Fragments](https://react.dev/reference/react/Fragment). + +The empty JSX tag `<>` is shorthand for `` + ```reason <> child1 child2 ; ``` @@ -111,18 +120,21 @@ The `make` above is exactly the same `make` function you've seen in the previous transforms into ```reason -ReactDOMRe.createElement(ReasonReact.fragment, [|child1, child2|]); +React.jsx( + React.jsxFragment, + ReactDOM.domProps(~children=React.array([|child1, child2|]), ()), +); ``` Which compiles to ```js -React.createElement(React.Fragment, undefined, child1, child2); +React.jsx(React.Fragment, { children: [child1, child2] }); ``` ## Children -ReasonReact children are **fully typed**, and you can pass any data structure to it (as long as the receiver component permits it). When you write: +reason-react children are **fully typed**, and you can pass any data structure to it (as long as the receiver component permits it). When you write: ```reason
diff --git a/dune-project b/dune-project index 29e6af47d..0c7560ffe 100644 --- a/dune-project +++ b/dune-project @@ -31,15 +31,10 @@ (name reason-react) (synopsis "Reason bindings for React.js") (description - "ReasonReact helps you use Reason to build React components with deeply integrated, strong, static type safety.\n\nIt is designed and built by people using Reason and React in large, mission critical production React codebases.") + "reason-react helps you use Reason to build React components with deeply integrated, strong, static type safety.\n\nIt is designed and built by people using Reason and React in large, mission critical production React codebases.") (depends ocaml - (melange - (or - (>= 3.0.0) - (and - (<= 5.1.0-53) - :with-test))) + (melange (<= 5.1.0)) (reason-react-ppx (= :version)) (reason @@ -57,7 +52,7 @@ (package (name reason-react-ppx) (synopsis "React.js JSX PPX") - (description "ReasonReact JSX PPX") + (description "reason-react JSX PPX") (depends (ocaml (>= 4.14)) diff --git a/package-lock.json b/package-lock.json index dc6c72fd2..8917e8778 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "reason-react", - "version": "0.11.0", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reason-react", - "version": "0.11.0", + "version": "0.0.0", "license": "MIT", "devDependencies": { "@testing-library/dom": "^10.4.0", - "@testing-library/react": "^16.0.1", + "@testing-library/react": "^16.3.0", "http-server": "^14.1.1", "jest": "^26.0.1", "react": "^19.1.0", @@ -1018,11 +1018,10 @@ } }, "node_modules/@testing-library/react": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.1.tgz", - "integrity": "sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -1031,10 +1030,10 @@ }, "peerDependencies": { "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { diff --git a/package.json b/package.json index 1389098f1..b22df63ec 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,7 @@ { "name": "reason-react", - "version": "0.11.0", - "description": "React bindings for Reason", - "files": [ - "dune", - "dune-project", - "README.md", - "HISTORY.md", - "LICENSE", - "src", - "ppx/src", - "reason-react-ppx.opam", - "reason-react.opam" - ], - "keywords": [ - "reasonml", - "react" - ], + "version": "0.0.0", + "description": "This package.json is used to install node development dependencies", "author": "", "license": "MIT", "repository": { @@ -26,7 +11,7 @@ "homepage": "https://reasonml.github.io/reason-react/", "devDependencies": { "@testing-library/dom": "^10.4.0", - "@testing-library/react": "^16.0.1", + "@testing-library/react": "^16.3.0", "http-server": "^14.1.1", "jest": "^26.0.1", "react": "^19.1.0", diff --git a/reason-react-ppx.opam b/reason-react-ppx.opam index 8bbc1290c..b827c1ed0 100644 --- a/reason-react-ppx.opam +++ b/reason-react-ppx.opam @@ -1,7 +1,7 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "React.js JSX PPX" -description: "ReasonReact JSX PPX" +description: "reason-react JSX PPX" maintainer: [ "David Sancho " "Antonio Monteiro " diff --git a/reason-react.opam b/reason-react.opam index 46fc4897c..44b9a1a3e 100644 --- a/reason-react.opam +++ b/reason-react.opam @@ -2,7 +2,7 @@ opam-version: "2.0" synopsis: "Reason bindings for React.js" description: """ -ReasonReact helps you use Reason to build React components with deeply integrated, strong, static type safety. +reason-react helps you use Reason to build React components with deeply integrated, strong, static type safety. It is designed and built by people using Reason and React in large, mission critical production React codebases.""" maintainer: [ @@ -19,7 +19,7 @@ bug-reports: "https://github.com/reasonml/reason-react/issues" depends: [ "dune" {>= "3.9"} "ocaml" - "melange" {>= "3.0.0" | <= "5.1.0-53" & with-test} + "melange" {<= "5.1.0"} "reason-react-ppx" {= version} "reason" {>= "3.12.0"} "ocaml-lsp-server" {with-dev-setup} @@ -43,6 +43,6 @@ build: [ ] dev-repo: "git+https://github.com/reasonml/reason-react.git" depexts: [ - ["react"] {npm-version = "^18.0.0"} - ["react-dom"] {npm-version = "^18.0.0"} + ["react"] {npm-version = "^19.1.0"} + ["react-dom"] {npm-version = "^19.1.0"} ] diff --git a/reason-react.opam.template b/reason-react.opam.template index d287dfa45..71bb69881 100644 --- a/reason-react.opam.template +++ b/reason-react.opam.template @@ -1,4 +1,4 @@ depexts: [ - ["react"] {npm-version = "^18.0.0"} - ["react-dom"] {npm-version = "^18.0.0"} + ["react"] {npm-version = "^19.1.0"} + ["react-dom"] {npm-version = "^19.1.0"} ] diff --git a/src/React.re b/src/React.re index 77c7e1705..b0fe5d81b 100644 --- a/src/React.re +++ b/src/React.re @@ -723,7 +723,9 @@ external useContext: Context.t('any) => 'any = "useContext"; [@mel.module "react"] external useRef: 'value => ref('value) = "useRef"; [@mel.module "react"] external useId: unit => string = "useId"; -[@mel.module "react"] external useDeferredValue: 'a => 'a = "useDeferredValue"; +[@mel.module "react"] +external useDeferredValue: ('a, ~initialValue: 'a=?) => 'a = + "useDeferredValue"; [@mel.module "react"] external useImperativeHandle0: @@ -877,6 +879,11 @@ module Uncurried = { external useTransition: unit => (bool, callback(callback(unit, unit), unit)) = "useTransition"; +[@mel.module "react"] +external useTransitionAsync: + unit => (bool, callbackAsync(callback(unit, unit), unit)) = + "useTransition"; + [@mel.module "react"] external startTransition: ([@mel.uncurry] (unit => unit)) => unit = "startTransition"; @@ -887,12 +894,15 @@ external useDebugValue: ('value, ~format: 'value => string=?, unit) => unit = [@mel.module "react"] external act: (unit => unit) => Js.Promise.t(unit) = "act"; + [@mel.module "react"] external actAsync: (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) = "act"; module Experimental = { /* This module is used to bind to APIs for future versions of React. There is no guarantee of backwards compatibility or stability. */ + external promise: Js.Promise.t(element) => element = "%identity"; + /* https://react.dev/reference/react/use */ [@mel.module "react"] external usePromise: Js.Promise.t('a) => 'a = "use"; [@mel.module "react"] external useContext: Context.t('a) => 'a = "use"; diff --git a/src/React.rei b/src/React.rei index 367ccdf18..dee162e1a 100644 --- a/src/React.rei +++ b/src/React.rei @@ -415,7 +415,9 @@ external useContext: Context.t('any) => 'any = "useContext"; [@mel.module "react"] external useRef: 'value => ref('value) = "useRef"; [@mel.module "react"] external useId: unit => string = "useId"; -[@mel.module "react"] external useDeferredValue: 'a => 'a = "useDeferredValue"; +[@mel.module "react"] +external useDeferredValue: ('a, ~initialValue: 'a=?) => 'a = + "useDeferredValue"; [@mel.module "react"] external useImperativeHandle0: @@ -573,14 +575,22 @@ external startTransition: ([@mel.uncurry] (unit => unit)) => unit = external useTransition: unit => (bool, callback(callback(unit, unit), unit)) = "useTransition"; +[@mel.module "react"] +external useTransitionAsync: + unit => (bool, callbackAsync(callback(unit, unit), unit)) = + "useTransition"; + [@mel.module "react"] external act: (unit => unit) => Js.Promise.t(unit) = "act"; + [@mel.module "react"] external actAsync: (unit => Js.Promise.t(unit)) => Js.Promise.t(unit) = "act"; module Experimental: { /* This module is used to bind to APIs for future versions of React. There is no guarantee of backwards compatibility or stability. */ + external promise: Js.Promise.t(element) => element = "%identity"; + [@mel.module "react"] external usePromise: Js.Promise.t('a) => 'a = "use"; [@mel.module "react"] external useContext: Context.t('a) => 'a = "use"; diff --git a/src/ReactDOM.rei b/src/ReactDOM.rei index ed35c948d..75de82b44 100644 --- a/src/ReactDOM.rei +++ b/src/ReactDOM.rei @@ -472,6 +472,9 @@ external createPortal: (React.element, Dom.element) => React.element = "createPortal"; [@mel.module "react-dom"] +[@deprecated + "Use ReactDOM.Client.unmount instead. This function will be removed in the next release." +] external unmountComponentAtNode: Dom.element => unit = "unmountComponentAtNode";