├── website ├── static │ ├── .nojekyll │ ├── img │ │ ├── examples │ │ │ ├── hn.png │ │ │ └── todomvc.png │ │ ├── new-bsb-template.png │ │ ├── tailwind-example.png │ │ ├── reason-react-favicon.png │ │ ├── logos │ │ │ ├── rescript-brandmark@2x.png │ │ │ ├── literal.svg │ │ │ ├── messenger.svg │ │ │ ├── astrolabe.svg │ │ │ ├── facebook.svg │ │ │ ├── ahrefs.svg │ │ │ ├── astrocoders.svg │ │ │ ├── pupilfirst.svg │ │ │ ├── appier.svg │ │ │ ├── beop.svg │ │ │ ├── codeheroes.svg │ │ │ ├── atvero.svg │ │ │ └── imandra.svg │ │ └── reason-react-white.svg │ ├── css │ │ ├── logo.css │ │ └── custom.css │ └── js │ │ ├── redirectBlog.js │ │ ├── redirect.js │ │ └── redirectIndex.js ├── .gitignore ├── blog │ ├── 2017-07-05-021.md │ ├── 2017-06-21-015.md │ ├── 2019-03-12-small-release.md │ ├── 2017-06-09-major-release.md │ ├── 2018-08-06-050.md │ ├── 2020-06-08-090-release.md │ ├── 2017-10-02-new-docs-site.md │ ├── 2020-05-05-080-release.md │ ├── 2018-01-09-subscriptions-send-router.md │ ├── 2017-11-17-power-children.md │ ├── 2019-04-10-react-hooks.md │ ├── 2021-05-07-rescript-migration.md │ ├── 2019-10-18-new-bsb-template.md │ ├── 2023-06-11-reborn.md │ └── 2023-09-13-reason-react-ppx.md ├── package.json ├── pages │ └── en │ │ ├── examples.js │ │ ├── gettingStarted.js │ │ ├── faq.js │ │ ├── built-with-reason-react.js │ │ └── help-with-translations.js ├── core │ └── Footer.js ├── README.md ├── sidebars.json └── languages.js ├── .ocamlformat ├── dune ├── test ├── blackbox-tests │ ├── dune │ └── useCallback.t ├── jest │ ├── dune │ ├── README.md │ ├── Mock.re │ └── Expect.re ├── melange-testing-library │ ├── dom │ │ ├── DomTestingLibrary.re │ │ └── dune │ ├── react │ │ └── dune │ └── README.md ├── dune ├── ReactRouter__test.re ├── Ref__test.re └── ReactDOM__test.re ├── ppx ├── test │ ├── key-as-prop-error.t │ │ ├── component.re │ │ └── run.t │ ├── components-destructured-error.t │ │ ├── component.re │ │ └── run.t │ ├── record-props.t │ │ ├── input.re │ │ └── run.t │ ├── record-props-error.t │ │ ├── input.re │ │ └── run.t │ ├── dune │ ├── external.t │ │ ├── input.re │ │ └── run.t │ ├── keys.t │ │ └── component.re │ ├── functor.t │ │ ├── input.re │ │ └── run.t │ ├── fragment.t │ │ ├── input.re │ │ └── run.t │ ├── simple.t │ │ └── component.re │ ├── uppercase.t │ │ ├── component.re │ │ └── run.t │ ├── ppx.sh │ ├── signature.t │ │ ├── input.re │ │ └── run.t │ ├── component-without-make.t │ │ ├── input.re │ │ └── run.t │ ├── upper.t │ │ ├── input.re │ │ └── run.t │ ├── optional-arg-check.t │ ├── issue-429.t │ │ └── component.re │ ├── signature-optional.t │ │ └── input.re │ ├── hover.t │ ├── react.t │ ├── lower.t │ │ └── input.re │ ├── locations-check.t │ └── component.t │ │ └── input.re ├── standalone.ml └── dune ├── .gitattributes ├── .npmignore ├── reason-react.opam.template ├── .vscode └── settings.json ├── src ├── ReactDOMServer.re ├── ReactDOMServer.rei ├── ReasonReactErrorBoundary.rei ├── dune ├── README.md ├── ReactDOMServerNode.rei ├── ReactDOMServerNode.re ├── ReasonReactErrorBoundary.re └── ReasonReactRouter.rei ├── .gitignore ├── docs ├── ternary-shortcut.md ├── props-spread.md ├── im-having-a-type-error.md ├── community.md ├── custom-hooks.md ├── example-projects.md ├── js-using-reason.md ├── adding-data-props.md ├── reason-using-js.md ├── i-really-need-feature-x-from-reactjs.md ├── render-props.md ├── clone-element.md ├── component-as-prop.md ├── playground.md ├── i-want-to-create-a-dom-element-without-jsx.md ├── importing-reason-into-js.md ├── useeffect-hook.md ├── invalid-prop-name.md ├── style.md ├── what-and-why.md ├── event.md ├── element-type-is-invalid.md ├── usereducer-hook.md ├── tailwind-css.md ├── refs.md ├── intro-example.md ├── error-boundaries.md ├── dom.md ├── importing-js-into-reason.md ├── context.md ├── graphql-apollo.md ├── working-with-optional-data.md ├── simple.md ├── usestate-hook.md └── testing.md ├── demo ├── dune └── index.html ├── .github └── workflows │ ├── nix.yml │ ├── website.yml │ └── opam.yml ├── package.json ├── reason-react-ppx.opam ├── LICENSE ├── flake.lock ├── crowdin.yaml ├── reason-react.opam ├── dune-project ├── CONTRIBUTING.md ├── Makefile ├── README.md └── flake.nix /website/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.26.0 2 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (dirs src test ppx demo) 2 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules* 2 | /build* 3 | /translated_docs* 4 | /i18n/* 5 | !/i18n/en.json 6 | -------------------------------------------------------------------------------- /test/blackbox-tests/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (package reason-react) 3 | (deps 4 | (package reason-react))) 5 | -------------------------------------------------------------------------------- /test/jest/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name jest) 3 | (modes melange) 4 | (preprocess 5 | (pps melange.ppx))) 6 | -------------------------------------------------------------------------------- /ppx/test/key-as-prop-error.t/component.re: -------------------------------------------------------------------------------- 1 | [@react.component] 2 | let make = (~key) =>
key->React.string
; 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.re linguist-language=Reason 2 | *.rei linguist-language=Reason 3 | *.t text eol=lf -linguist-detectable 4 | -------------------------------------------------------------------------------- /ppx/standalone.ml: -------------------------------------------------------------------------------- 1 | (* To run as a standalone binary, run the registered drivers *) 2 | let () = Ppxlib.Driver.standalone () 3 | -------------------------------------------------------------------------------- /website/static/img/examples/hn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reasonml/reason-react/HEAD/website/static/img/examples/hn.png -------------------------------------------------------------------------------- /test/melange-testing-library/dom/DomTestingLibrary.re: -------------------------------------------------------------------------------- 1 | include Queries; 2 | include Utils; 3 | 4 | module FireEvent = FireEvent; 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /website/ 2 | lib/bs/ 3 | .merlin 4 | docs 5 | _build 6 | test 7 | test/dune 8 | test/jest/dune 9 | ppx/test/* 10 | -------------------------------------------------------------------------------- /reason-react.opam.template: -------------------------------------------------------------------------------- 1 | depexts: [ 2 | ["react"] {npm-version = "^19.1.0"} 3 | ["react-dom"] {npm-version = "^19.1.0"} 4 | ] 5 | -------------------------------------------------------------------------------- /website/static/img/examples/todomvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reasonml/reason-react/HEAD/website/static/img/examples/todomvc.png -------------------------------------------------------------------------------- /website/static/img/new-bsb-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reasonml/reason-react/HEAD/website/static/img/new-bsb-template.png -------------------------------------------------------------------------------- /website/static/img/tailwind-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reasonml/reason-react/HEAD/website/static/img/tailwind-example.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ocaml.sandbox": { 3 | "kind": "opam", 4 | "switch": "${workspaceFolder:reason-react}" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /website/static/img/reason-react-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reasonml/reason-react/HEAD/website/static/img/reason-react-favicon.png -------------------------------------------------------------------------------- /website/static/img/logos/rescript-brandmark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reasonml/reason-react/HEAD/website/static/img/logos/rescript-brandmark@2x.png -------------------------------------------------------------------------------- /ppx/test/components-destructured-error.t/component.re: -------------------------------------------------------------------------------- 1 | [@react.component] 2 | let (pageState, setPageState) = React.useState(_ => 0); 3 | let make = (~children, ()) =>
children
; 4 | -------------------------------------------------------------------------------- /ppx/test/record-props.t/input.re: -------------------------------------------------------------------------------- 1 | module Record_props = { 2 | [@react.component {props: string}] 3 | let make = (~lola) => { 4 |
{React.string(lola)}
; 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /ppx/test/record-props-error.t/input.re: -------------------------------------------------------------------------------- 1 | module Record_props = { 2 | [@react.component {no_props: string}] 3 | let make = (~lola) => { 4 |
{React.string(lola)}
; 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /test/melange-testing-library/dom/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name melange_testing_library_dom) 3 | (wrapped false) 4 | (modes melange) 5 | (libraries melange.dom) 6 | (preprocess 7 | (pps melange.ppx))) 8 | -------------------------------------------------------------------------------- /ppx/test/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (package reason-react-ppx) 3 | (deps 4 | (package reason-react) 5 | %{bin:reason-react-ppx} 6 | %{bin:refmt} 7 | %{bin:dune} 8 | %{bin:jq} 9 | %{bin:ocamlmerlin} 10 | ppx.sh)) 11 | -------------------------------------------------------------------------------- /ppx/test/external.t/input.re: -------------------------------------------------------------------------------- 1 | module External = { 2 | [@react.component] [@otherAttribute "bla"] 3 | external component: (~a: int, ~b: string) => React.element = 4 | {|require("my-react-library").MyReactComponent|}; 5 | }; 6 | -------------------------------------------------------------------------------- /test/melange-testing-library/react/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name melange_testing_library_react) 3 | (libraries reason-react melange_testing_library_dom) 4 | (wrapped false) 5 | (modes melange) 6 | (preprocess 7 | (pps melange.ppx))) 8 | -------------------------------------------------------------------------------- /test/melange-testing-library/README.md: -------------------------------------------------------------------------------- 1 | ### vendored melange-testing-library 2 | 3 | This directory contains a vendored version of the melange-testing-library. 4 | 5 | The original repository is https://github.com/melange-community/melange-testing-library 6 | -------------------------------------------------------------------------------- /src/ReactDOMServer.re: -------------------------------------------------------------------------------- 1 | [@mel.module "react-dom/server"] 2 | external renderToString: React.element => string = "renderToString"; 3 | 4 | [@mel.module "react-dom/server"] 5 | external renderToStaticMarkup: React.element => string = 6 | "renderToStaticMarkup"; 7 | -------------------------------------------------------------------------------- /src/ReactDOMServer.rei: -------------------------------------------------------------------------------- 1 | [@mel.module "react-dom/server"] 2 | external renderToString: React.element => string = "renderToString"; 3 | 4 | [@mel.module "react-dom/server"] 5 | external renderToStaticMarkup: React.element => string = 6 | "renderToStaticMarkup"; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules* 3 | finalOutput/*.js 4 | .merlin 5 | .install 6 | /lib/bs/ 7 | /lib/js/ 8 | /lib/ocaml/ 9 | *.log 10 | .bsb.lock 11 | /src/*.js 12 | _esy 13 | _build 14 | *.install 15 | *.bs.js 16 | 17 | # Editor 18 | /.idea/ 19 | _opam 20 | -------------------------------------------------------------------------------- /website/blog/2017-07-05-021.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0.2.1 Released 3 | --- 4 | 5 | Small new breaking release! We've provided [a migration script](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#021). No big change except a better (and much demanded) `self` API. Enjoy! 6 | -------------------------------------------------------------------------------- /ppx/test/keys.t/component.re: -------------------------------------------------------------------------------- 1 | module Author = { 2 | type t = { 3 | name: string, 4 | imageUrl: string, 5 | }; 6 | }; 7 | 8 | [@react.component] 9 | let make = (~author) => 10 | ; 11 | -------------------------------------------------------------------------------- /docs/ternary-shortcut.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ternary Shortcut 3 | --- 4 | 5 | ReactJS allows the pattern `showButton && ; 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /ppx/test/uppercase.t/component.re: -------------------------------------------------------------------------------- 1 | module Box = { 2 | [@react.component] 3 | let make = (~children) => { 4 |
5 | {children} 6 |
; 7 | }; 8 | }; 9 | 10 | module Uppercase = { 11 | [@react.component] 12 | let make = (~children) => { 13 | 14 | {children} 15 | ; 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /website/static/img/logos/literal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/im-having-a-type-error.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I'm Having a Type Error 3 | --- 4 | 5 | Check your terminal to see if we've provided a dedicated error message for your use-case! If not, please file an issue [here](https://github.com/reasonml/reason-react/issues) or [here](https://github.com/reasonml-community/error-message-improvement/issues). Thanks! 6 | -------------------------------------------------------------------------------- /ppx/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name reason_react_ppx) 3 | (modules :standard \ standalone) 4 | (public_name reason-react-ppx) 5 | (kind ppx_rewriter) 6 | (libraries ppxlib)) 7 | 8 | (executable 9 | (name standalone) 10 | (modules standalone) 11 | (package reason-react-ppx) 12 | (public_name reason-react-ppx) 13 | (libraries reason-react-ppx ppxlib)) 14 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (melange.emit 2 | (alias runtest) 3 | (target test) 4 | (module_systems 5 | (commonjs bs.js)) 6 | (libraries 7 | reason-react 8 | reason-react.node 9 | jest 10 | melange.belt 11 | melange.dom 12 | melange_testing_library_dom 13 | melange_testing_library_react) 14 | (preprocess 15 | (pps melange.ppx reason-react-ppx))) 16 | -------------------------------------------------------------------------------- /docs/community.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Community 3 | --- 4 | 5 | This is where we keep track of all the awesome things happening in the community. 6 | 7 | Come say hi! 8 | 9 | - [Forum](https://reasonml.chat) 10 | - [Discord](https://discord.gg/reasonml) 11 | - [Twitter](https://twitter.com/reasonml) 12 | - [Reddit](https://www.reddit.com/r/reasonml/) 13 | - [Stack Overflow](http://stackoverflow.com/questions/tagged/reason-react) 14 | -------------------------------------------------------------------------------- /docs/custom-hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Hooks 3 | --- 4 | 5 | ```reason 6 | let useDebounce = (value, delay) => { 7 | let (debouncedValue, setDebouncedValue) = React.useState(_ => value); 8 | 9 | React.useEffect1( 10 | () => { 11 | let handler = 12 | Js.Global.setTimeout(() => setDebouncedValue(_ => value), delay); 13 | 14 | Some(() => Js.Global.clearTimeout(handler)); 15 | }, 16 | [|value|], 17 | ); 18 | 19 | debouncedValue; 20 | }; 21 | ``` 22 | -------------------------------------------------------------------------------- /src/ReasonReactErrorBoundary.rei: -------------------------------------------------------------------------------- 1 | /** 2 | * Important note on this module: 3 | * As soon as React provides a mechanism for error-catching using functional component, 4 | * this is likely to be deprecated and/or move to user space. 5 | */ 6 | type info = {componentStack: string}; 7 | 8 | type params('error) = { 9 | error: 'error, 10 | info, 11 | }; 12 | 13 | [@react.component] 14 | let make: 15 | (~children: React.element, ~fallback: params('error) => React.element) => 16 | React.element; 17 | -------------------------------------------------------------------------------- /ppx/test/record-props-error.t/run.t: -------------------------------------------------------------------------------- 1 | Since we generate invalid syntax for the argument of the make fn `(Props : <>)` 2 | We need to output ML syntax here, otherwise refmt could not parse it. 3 | $ ../ppx.sh --output ml input.re 4 | File "output.ml", line 5, characters 68-76: 5 | 5 | no_props 6 | ^^^^^^^^ 7 | Error: [@react.component] only accepts 'props' as a field, given: no_props 8 | [1] 9 | -------------------------------------------------------------------------------- /website/blog/2017-06-21-015.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0.1.5 Released 3 | --- 4 | 5 | Thanks for the wait! Head over to [HISTORY.md](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#015) to see the changes. This is a small non-breaking release, though we did fix a ref-related issue that can cause type errors. The fix is trivial; see the notes. 6 | 7 | We've batched together non-breaking features for your upgrading convenience. Next version will be a breaking one, with API tweaks that fix some of the existing pain points. Stay tuned! 8 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reason-react-site", 3 | "scripts": { 4 | "start": "docusaurus-start", 5 | "build": "docusaurus-build" 6 | }, 7 | "keywords": [], 8 | "author": "", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/reasonml/reason-react.git" 13 | }, 14 | "dependencies": { 15 | "docusaurus": "^1.14.0" 16 | }, 17 | "devDependencies": { 18 | "pjax-api": "^3.23.0", 19 | "reason-highlightjs": "0.2.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/example-projects.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Example Projects 3 | --- 4 | 5 | Feel free to submit yours! 6 | 7 | - [ReasonReact Hacker News](https://github.com/reasonml-community/reason-react-hacker-news) 8 | - [Personas Avatar Generator](https://github.com/draftbit/avatar-generator) 9 | 10 | **Legacy API:** 11 | - [ReasonReact Example](https://github.com/reasonml-community/reason-react-example) 12 | - [Reason Maze](https://github.com/reasonml-community/reason-maze) 13 | - [ReasonReact 2048](https://github.com/LIU9293/reason-react-2048) 14 | -------------------------------------------------------------------------------- /ppx/test/ppx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | function usage() { 6 | echo "Usage: $(basename "$0") --output re [file.re]" 7 | echo " $(basename "$0") --output ml [file.re]" 8 | } 9 | 10 | if [ -z "$3" ]; then 11 | usage 12 | exit 13 | fi 14 | 15 | refmt --parse re --print ml "$3" > output.ml 16 | reason-react-ppx --impl output.ml -o temp.ml 17 | 18 | if [ "$2" == "ml" ]; then 19 | cat temp.ml 20 | exit 21 | elif [ "$2" == "re" ]; then 22 | refmt --parse ml --print re temp.ml 23 | exit 24 | else 25 | usage 26 | exit 27 | fi 28 | -------------------------------------------------------------------------------- /ppx/test/external.t/run.t: -------------------------------------------------------------------------------- 1 | $ ../ppx.sh --output re input.re 2 | module External = { 3 | [@mel.obj] 4 | external componentProps: 5 | (~a: int, ~b: string, ~key: string=?, unit) => 6 | { 7 | . 8 | "a": int, 9 | "b": string, 10 | }; 11 | [@otherAttribute "bla"] 12 | external component: 13 | React.componentLike( 14 | { 15 | . 16 | "a": int, 17 | "b": string, 18 | }, 19 | React.element, 20 | ) = 21 | {|require("my-react-library").MyReactComponent|}; 22 | }; 23 | -------------------------------------------------------------------------------- /docs/js-using-reason.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReactJS using ReasonReact 3 | --- 4 | 5 | `PageReason.re`: 6 | 7 | ```reason 8 | /* ReasonReact used by ReactJS */ 9 | [@react.component] 10 | let make = (~message, ~extraGreeting=?) => { 11 | let greeting = 12 | switch (extraGreeting) { 13 | | None => "How are you?" 14 | | Some(g) => g 15 | }; 16 |
; 17 | }; 18 | ``` 19 | 20 | Then use it on the JS side through 21 | 22 | ```javascript 23 | var PageReason = require('path/to/PageReason.js').make; 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/adding-data-props.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Adding data-* attributes 3 | --- 4 | 5 | Reason doesn't support using props with dashes right now, ie: `data-id` or `data-whatever`. You can overcome this by creating a `Spread` component: 6 | 7 | ```reason 8 | /* Spread.re */ 9 | [@react.component] 10 | let make = (~props, ~children) => React.cloneElement(children, props); 11 | ``` 12 | 13 | Using Spread: 14 | 15 | ```reason 16 | [@react.component] 17 | let make = () => 18 | 19 | /* This div will now have the `data-cy` attribute in the DOM! */ 20 |
21 | ; 22 | ``` 23 | -------------------------------------------------------------------------------- /ppx/test/signature.t/input.re: -------------------------------------------------------------------------------- 1 | module Example = { 2 | [@react.component] 3 | external make: 4 | ( 5 | ~cond: bool, 6 | ~noWrap: bool=?, 7 | ~href: string, 8 | ~id: string=?, 9 | ~color: color, 10 | ~mode: linkMode=?, 11 | ~children: React.element 12 | ) => 13 | React.element; 14 | }; 15 | 16 | module MyPropIsOptionBool = { 17 | [@react.component] external make: (~myProp: bool=?) => React.element = "A"; 18 | }; 19 | 20 | module MyPropIsOptionOptionBool = { 21 | [@react.component] 22 | external make: (~myProp: option(bool)=?) => React.element = "B"; 23 | }; 24 | -------------------------------------------------------------------------------- /website/static/img/reason-react-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /website/pages/en/examples.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a redirect page from our old docs. 3 | */ 4 | 5 | const React = require("react"); 6 | 7 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 8 | 9 | class Examples extends React.Component { 10 | render() { 11 | return ( 12 |
13 | 17 | 18 |
19 | ); 20 | } 21 | } 22 | 23 | module.exports = Examples; 24 | -------------------------------------------------------------------------------- /website/pages/en/gettingStarted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a redirect page from our old docs. 3 | */ 4 | 5 | const React = require("react"); 6 | 7 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 8 | 9 | class FAQ extends React.Component { 10 | render() { 11 | return ( 12 |
13 | 17 | 18 |
19 | ); 20 | } 21 | } 22 | 23 | module.exports = FAQ; 24 | -------------------------------------------------------------------------------- /website/pages/en/faq.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a redirect page from our old docs. 3 | */ 4 | 5 | const React = require("react"); 6 | 7 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 8 | 9 | class FAQ extends React.Component { 10 | render() { 11 | return ( 12 |
13 | 17 | 18 |
19 | ); 20 | } 21 | } 22 | 23 | module.exports = FAQ; 24 | -------------------------------------------------------------------------------- /website/blog/2019-03-12-small-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReasonReact 0.6.0 (Small Release) 3 | --- 4 | 5 | Hello! It's been a while. We've been heads down working on a few secret features these days; in the meantime, hopefully you enjoyed the stability of the API so far. 6 | 7 | We're making a tiny release today to improve some nits. Consider this blog post as a heart beat. Don't forget to come to [Reason Conf](https://www.reason-conf.com)! We have some ReasonReact stuff to share with you there. 8 | 9 | As usual, release notes are [here](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#060). No migration script this time; it's just a single letter change. 10 | -------------------------------------------------------------------------------- /docs/reason-using-js.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReasonReact using ReactJS 3 | --- 4 | 5 | `MyBanner.js:` 6 | 7 | ```javascript 8 | var ReactDOM = require('react-dom'); 9 | var React = require('react'); 10 | 11 | var MyBanner = function(props) { 12 | if (props.show) { 13 | return React.createElement('div', null, 14 | 'Here\'s the message from the owner: ' + props.message 15 | ); 16 | } else { 17 | return null; 18 | } 19 | }; 20 | 21 | module.exports = MyBanner; 22 | ``` 23 | 24 | `MyBannerRe.re` 25 | 26 | ```reason 27 | /* ReactJS used by ReasonReact */ 28 | [@react.component] [@bs.module] 29 | external make : (~show: bool, ~message: string) => React.element = "./MyBanner"; 30 | ``` 31 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name react) 3 | (public_name reason-react) 4 | (wrapped false) 5 | ; Explicitly adding modules isn't necessary, but it's a good idea 6 | ; when wrapped is false, so we don't export something by accident 7 | (modules 8 | React 9 | ReactDOM 10 | ReactDOMServer 11 | ReactDOMTestUtils 12 | ReasonReactRouter 13 | ReasonReactErrorBoundary) 14 | (preprocess 15 | (pps melange.ppx reason-react-ppx)) 16 | (libraries melange.dom melange.js) 17 | (modes melange)) 18 | 19 | (library 20 | (name ReactDOMServerNode) 21 | (public_name reason-react.node) 22 | (modules ReactDOMServerNode) 23 | (libraries react) 24 | (preprocess 25 | (pps melange.ppx)) 26 | (modes melange)) 27 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: "Nix Build" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | tests: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - uses: cachix/install-nix-action@v31 18 | with: 19 | extra_nix_config: | 20 | extra-substituters = https://anmonteiro.nix-cache.workers.dev 21 | extra-trusted-public-keys = ocaml.nix-cache.com-1:/xI2h2+56rwFfKyyFVbkJSeGqSIYMC/Je+7XXqGKDIY= 22 | - name: "Run Nix tests" 23 | run: nix build .#reason-react-ppx .#reason-react 24 | 25 | -------------------------------------------------------------------------------- /docs/i-really-need-feature-x-from-reactjs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I Really Need Feature X From ReactJS 3 | --- 4 | 5 | First make sure that you indeed can't achieve this idiom using the latest version of ReasonReact. If you need help figuring out how to translate a certain idiom, feel free to ask us on [Discord](https://discord.gg/reasonml). If it is not achievable with ReasonReact, you can always write the component in ReactJS first, then bind to it using [our interop](components.md#interop). Try to isolate the required functionality into a tiny ReactJS component and write the rest of your code in ReasonReact. Once this is done, feel free to [file an issue](https://github.com/reasonml/reason-react/issues) explaining your use case! 6 | -------------------------------------------------------------------------------- /ppx/test/components-destructured-error.t/run.t: -------------------------------------------------------------------------------- 1 | Test some locations in reason-react components 2 | 3 | $ cat >dune-project < (lang dune 3.8) 5 | > (using melange 0.1) 6 | > EOF 7 | 8 | $ cat >dune < (melange.emit 10 | > (alias foo) 11 | > (target foo) 12 | > (libraries reason-react) 13 | > (preprocess 14 | > (pps melange.ppx reason-react-ppx))) 15 | > EOF 16 | 17 | $ dune build 18 | File "component.re", lines 1-2, characters 0-54: 19 | 1 | [@react.component] 20 | 2 | let (pageState, setPageState) = React.useState(_ => 0). 21 | Error: [@react.component] cannot be used with a destructured binding. Please use it on a `let make = ...` binding instead. 22 | [1] 23 | -------------------------------------------------------------------------------- /ppx/test/key-as-prop-error.t/run.t: -------------------------------------------------------------------------------- 1 | Test some locations in reason-react components 2 | 3 | $ cat >dune-project < (lang dune 3.8) 5 | > (using melange 0.1) 6 | > EOF 7 | 8 | $ cat >dune < (melange.emit 10 | > (alias foo) 11 | > (target foo) 12 | > (libraries reason-react) 13 | > (preprocess 14 | > (pps melange.ppx reason-react-ppx))) 15 | > EOF 16 | 17 | $ dune build 18 | File "component.re", line 2, characters 11-51: 19 | 2 | let make = (~key) =>
key->React.string
; 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | Error: ~key cannot be accessed from the component props. Please set the key where the component is being used. 22 | [1] 23 | -------------------------------------------------------------------------------- /website/static/img/logos/messenger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/render-props.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Render Props 3 | --- 4 | 5 | ### Basic Render Props 6 | 7 | ```reason 8 | [@react.component] 9 | let make = () => { 10 |
} /> 11 | }; 12 | ``` 13 | 14 | ```reason 15 | /* Loader.re */ 16 | 17 | [@react.component] 18 | let make = (~render) => { 19 |
{render()}
20 | }; 21 | ``` 22 | 23 | ### Children as Render Props 24 | 25 | ```reason 26 | [@react.component] 27 | let make = (~children) => { 28 | 29 | {person =>
{React.string(person.name)}
} 30 |
; 31 | }; 32 | ``` 33 | 34 | ```reason 35 | /* Loader.re */ 36 | 37 | [@react.component] 38 | let make = (~children) => { 39 | let person = {name: "Peter"}; 40 |
{children(person)}
; 41 | }; 42 | ``` 43 | -------------------------------------------------------------------------------- /ppx/test/record-props.t/run.t: -------------------------------------------------------------------------------- 1 | Since we generate invalid syntax for the argument of the make fn `(Props : <>)` 2 | We need to output ML syntax here, otherwise refmt could not parse it. 3 | $ ../ppx.sh --output ml input.re 4 | module Record_props = 5 | struct 6 | external makeProps : 7 | lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" 8 | [@@mel.obj ] 9 | let make = 10 | ((fun ~lola -> 11 | ReactDOM.jsx "div" 12 | (((ReactDOM.domProps)[@merlin.hide ]) 13 | ~children:(React.string lola) ())) 14 | [@warning "-16"]) 15 | let make = 16 | let Output$Record_props (string : < lola: 'lola > Js.t) = 17 | make ~lola:(string ## lola) in 18 | Output$Record_props 19 | end 20 | -------------------------------------------------------------------------------- /test/ReactRouter__test.re: -------------------------------------------------------------------------------- 1 | open Jest; 2 | open Jest.Expect; 3 | open ReasonReactRouter; 4 | 5 | describe("it allows to create url from string", () => { 6 | test("it supports basic paths", () => { 7 | let expected = { 8 | path: ["foo", "bar"], 9 | hash: "", 10 | search: "", 11 | }; 12 | let generated = dangerouslyGetInitialUrl(~serverUrlString="/foo/bar", ()); 13 | 14 | expect(generated == expected)->toBe(true); 15 | }); 16 | 17 | test("it creates with search", () => { 18 | let expected = { 19 | path: ["foo", "bar"], 20 | hash: "", 21 | search: "q=term", 22 | }; 23 | let generated = 24 | dangerouslyGetInitialUrl(~serverUrlString="/foo/bar?q=term", ()); 25 | 26 | expect(generated == expected)->toBe(true); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /docs/clone-element.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: cloneElement 3 | --- 4 | 5 | Signature: `let cloneElement: (React.element, ~props: Js.t({..})=?, 'anyChildrenType) => React.element` 6 | 7 | Same as ReactJS' [cloneElement](https://reactjs.org/docs/react-api.html#cloneelement). However, adding extra props to a ReasonReact component doesn't make sense; you'd use a [**render prop**](https://reactjs.org/docs/render-props.html). Therefore, `ReasonReact.cloneElement` is only used in edge-cases. 8 | 9 | ```reason 10 | let clonedElement = 11 | React.cloneElement( 12 |
, 13 | ~props={"payload": 1}, 14 | [||] 15 | ); 16 | ``` 17 | 18 | The `props` value is unsafe, be careful! 19 | 20 | You can also use `cloneElement` to simulate [prop spreading](props-spread.md), but this is discouraged in ReasonReact. 21 | -------------------------------------------------------------------------------- /website/blog/2017-06-09-major-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Major New Release! 3 | --- 4 | 5 | 0.1.4 is released! We've got a new documentation website here, a brand new ReasonReact API, a smooth migration guide, and best of all: **no breaking change**! Head over to [HISTORY.md](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#014) for the incremental migration. 6 | 7 | 8 | 9 | This is a very exciting release for us. We've been working on it while taking into account the external and internal folks' feedback. The new API should feel very, very familiar to users of ReactJS. The previous API's corner-cases are gone too. In short: improvements across the board (API, codegen, type safety, speed, familiarity, etc.). 10 | 11 | As always, we're on [Discord](https://discord.gg/reasonml) if you need any help! 12 | -------------------------------------------------------------------------------- /ppx/test/component-without-make.t/input.re: -------------------------------------------------------------------------------- 1 | module X_as_main_function = { 2 | [@react.component] 3 | let x = () =>
; 4 | }; 5 | 6 | module Create_element_as_main_function = { 7 | [@react.component] 8 | let createElement = (~lola) =>
{React.string(lola)}
; 9 | }; 10 | 11 | /* This isn't valid running code, since Foo gets transformed into Foo.make, not createElement. */ 12 | module Invalid_case = { 13 | [@react.component] 14 | let make = (~lola) => { 15 | ; 16 | }; 17 | }; 18 | 19 | /* If main function is not make, neither createElement, then it can be explicitly annotated */ 20 | /* NOTE: If you use `createElement` refmt removes it */ 21 | module Valid_case = { 22 | [@react.component] 23 | let make = () => { 24 | ; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /docs/component-as-prop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Component as Prop 3 | --- 4 | 5 | In ReactJS, `
` is easy; in ReasonReact, we can't trivially pass the whole component module (it wouldn't even be syntactically valid because a module resides in [another layer of the language](https://reasonml.github.io/docs/en/module.html)). Solution: 6 | 7 | ```reason 8 | let bannerCallback = (prop1, prop2) => ; 9 | 10 | ; 11 | ``` 12 | 13 | This also has some added advantages: 14 | 15 | - The _owner_ is the one controlling how the component renders, not some opaque logic inside `MyBanner`. 16 | - Circumvents the tendency of needing a [props spread](props-spread.md) in `MyBanner`'s render, since passing a component to a child and the child using prop spread on said component usually go together. 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reason-react", 3 | "version": "0.0.0", 4 | "description": "This package.json is used to install node development dependencies", 5 | "author": "", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/reasonml/reason-react.git" 10 | }, 11 | "homepage": "https://reasonml.github.io/reason-react/", 12 | "devDependencies": { 13 | "@testing-library/dom": "^10.4.0", 14 | "@testing-library/react": "^16.3.0", 15 | "http-server": "^14.1.1", 16 | "jest": "^26.0.1", 17 | "react": "^19.1.0", 18 | "react-dom": "^19.1.0" 19 | }, 20 | "jest": { 21 | "moduleDirectories": [ 22 | "node_modules" 23 | ], 24 | "roots": [ 25 | "_build/default/test/test/test" 26 | ], 27 | "testMatch": [ 28 | "**/*__test.bs.js" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ppx/test/uppercase.t/run.t: -------------------------------------------------------------------------------- 1 | Test some locations in reason-react components 2 | 3 | $ cat >dune-project < (lang dune 3.8) 5 | > (using melange 0.1) 6 | > EOF 7 | 8 | $ cat >dune < (melange.emit 10 | > (alias foo) 11 | > (target foo) 12 | > (libraries reason-react) 13 | > (preprocess 14 | > (pps melange.ppx reason-react-ppx))) 15 | > EOF 16 | 17 | $ dune build 18 | 19 | Let's test hovering over parts of the component 20 | 21 | `children` inside `` element 22 | 23 | $ ocamlmerlin single type-enclosing -position 14:12 -verbosity 0 \ 24 | > -filename component.re < component.re | jq '.value[0]' 25 | { 26 | "start": { 27 | "line": 14, 28 | "col": 7 29 | }, 30 | "end": { 31 | "line": 14, 32 | "col": 15 33 | }, 34 | "type": "React.element", 35 | "tail": "no" 36 | } 37 | -------------------------------------------------------------------------------- /ppx/test/upper.t/input.re: -------------------------------------------------------------------------------- 1 | let upper = ; 2 | 3 | let upper_prop = ; 4 | 5 | let upper_children_single = foo => foo ; 6 | 7 | let upper_children_multiple = (foo, bar) => foo bar ; 8 | 9 | let upper_children = 10 |

{React.string("Yep")}

; 11 | 12 | let upper_nested_module = ; 13 | 14 | let upper_child_expr =
{React.int(1)}
; 15 | let upper_child_ident =
lola
; 16 | 17 | let upper_all_kinds_of_props = 18 | 24 |
"hello"
25 |
; 26 | 27 | let upper_ref_with_children = 28 |
; 29 | -------------------------------------------------------------------------------- /docs/playground.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Playground 3 | --- 4 | 5 | You can try ReasonReact, with live JS output, in the [Reason Try page](https://reasonml.github.io/en/try.html?rrjsx=true&reason=NoAQRgzgdAxg9gOwGYEsDmACA3gKwgDwC4MBmAXwF0BuAKBoFs4ATAVwBsBTDAYThYQAuHAE4YAvNhoYMoYRwCGMAbDj0ADog6CKUjJwEZ68gNZcJACgB+CefQ4BKcQD5J06fozn4-AQBoMEBwCvD6OEgBKCkpQLIEAygLyQubmYS4ADPa0utIAPEwoAG5OOW65ai5YkYrKEALCKAho5jZ2GADU7RgARBgwbCgwpkw9HV11DU0A+nBIU40CXnyCjp2jAih2EN32ZBi5APQVpXlgLAICiBiI3ANDYlhTzgFBIYLmT2Iu3oIdGACMuxKbhB2Gq0QmjWa3Vug2Mhg4OzIJ32BzOF0QwLKBwKxV0ZFoBLo4IEABEAPIAWUiUDkCCYIgAKnAAKKcOyCADqKAEAAsAJJMcy5N5CUStDhiGHLMW9A5OfzdNRyQooDgAdx2VCAA). The template's in the Examples menu. 6 | 7 | You can also try it on [Repl.it](https://repl.it/languages/reactre). Just hit run button to start the development server and from there you can edit and see it live. Any web app you build is live-deployed and the URL will be available forever. 8 | -------------------------------------------------------------------------------- /ppx/test/optional-arg-check.t: -------------------------------------------------------------------------------- 1 | Show the error message when an optionally labelled argument has the wrong type 2 | 3 | $ cat > dune-project < (lang dune 3.8) 5 | > (using melange 0.1) 6 | > EOF 7 | 8 | $ cat > dune < (melange.emit 10 | > (target output) 11 | > (alias mel) 12 | > (emit_stdlib false) 13 | > (libraries reason-react) 14 | > (preprocess (pps melange.ppx reason-react-ppx))) 15 | > EOF 16 | 17 | $ cat > x.re < module App = { 19 | > [@react.component] 20 | > let make = (~myProp: bool=?) => React.null; 21 | > }; 22 | > EOF 23 | 24 | $ dune build @mel 25 | File "x.re", line 3, characters 15-27: 26 | 3 | let make = (~myProp: bool=?) => React.null; 27 | ^^^^^^^^^^^^ 28 | Error: This pattern matches values of type bool 29 | but a pattern was expected which matches values of type 'a option 30 | [1] 31 | -------------------------------------------------------------------------------- /docs/i-want-to-create-a-dom-element-without-jsx.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I want to create a DOM element without JSX 3 | --- 4 | 5 | In certain cases, you might want to choose the DOM element given props, and JSX prevents that. 6 | 7 | In JS, you'd do: 8 | 9 | ```js 10 | React.createElement(multiline ? "textarea" : "input" /* ... */); 11 | ``` 12 | 13 | ReactJS has a special behavior that considers strings as special components. In a typed language, that's a bit trickier. To that end we provide `ReactDOM.stringToComponent` that makes the compiler understand that a given string is to be considered a component. 14 | 15 | In Reason: 16 | 17 | ```reason 18 | React.createElement(ReactDOM.stringToComponent(multiline ? "textarea" : "input"), /* ... */) 19 | ``` 20 | 21 | You can also pass a variable: 22 | 23 | ```reason 24 | let tag = ReactDOM.stringToComponent(multiline ? "textarea" : "input"); 25 | React.createElement(tag, /* ... */) 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/importing-reason-into-js.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Importing Reason into JS 3 | --- 4 | 5 | ### Importing a Basic Reason file into Javascript 6 | 7 | ```reason 8 | /* Greeting.re */ 9 | 10 | [@react.component] 11 | let make = (~name) => {React.string("Hey " ++ name)} ; 12 | ``` 13 | 14 | ```js 15 | /* App.js */ 16 | 17 | import { make as Greeting } from './Greeting.bs' 18 | 19 | export default function App() { 20 | return 21 | } 22 | ``` 23 | 24 | ### Importing a Component as Default 25 | 26 | ```reason 27 | /* Greeting.re */ 28 | 29 | [@react.component] 30 | let make = (~name) => {React.string("Hey " ++ name)} ; 31 | 32 | /* this sets the named export to default */ 33 | let default = make; 34 | ``` 35 | 36 | ```js 37 | /* App.js */ 38 | 39 | import Greeting from './Greeting.bs' 40 | 41 | export default function App() { 42 | return 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /ppx/test/functor.t/run.t: -------------------------------------------------------------------------------- 1 | Since we generate invalid syntax for the argument of the make fn `(Props : <>)` 2 | We need to output ML syntax here, otherwise refmt could not parse it. 3 | $ ../ppx.sh --output ml input.re 4 | module type X_int = sig val x : int end 5 | module Func(M:X_int) = 6 | struct 7 | let x = M.x + 1 8 | external makeProps : 9 | a:'a -> b:'b -> ?key:string -> unit -> < a: 'a ;b: 'b > Js.t = "" 10 | [@@mel.obj ] 11 | let make = 12 | ((fun ~a -> 13 | ((fun ~b -> 14 | print_endline "This function should be named `Test$Func`" M.x; 15 | ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ())) 16 | [@warning "-16"])) 17 | [@warning "-16"]) 18 | let make = 19 | let Output$Func (Props : < a: 'a ;b: 'b > Js.t) = 20 | make ~b:(Props ## b) ~a:(Props ## a) in 21 | Output$Func 22 | end 23 | -------------------------------------------------------------------------------- /ppx/test/issue-429.t/component.re: -------------------------------------------------------------------------------- 1 | open React; 2 | 3 | type state = { 4 | count: int, 5 | show: bool, 6 | }; 7 | 8 | type action = 9 | | Click(int) 10 | | Toggle; 11 | 12 | let initialState = {count: 0, show: true}; 13 | 14 | [@react.component] 15 | let make = (~greeting) => { 16 | let (state, dispatch) = 17 | useReducer( 18 | (state, action) => 19 | switch (action) { 20 | | Click(points) => {...state, count: state.count + points} 21 | | Toggle => {...state, show: !state.show} 22 | }, 23 | initialState, 24 | ); 25 | 26 | let message = 27 | "You've clicked this " ++ string_of_int(state.count) ++ " times(s)"; 28 | 29 |
30 | 31 | 34 | {state.show ? string(greeting) : null} 35 |
; 36 | }; 37 | -------------------------------------------------------------------------------- /docs/useeffect-hook.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useEffect 3 | --- 4 | 5 | [React.js docs for useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect) 6 | 7 | Here's a simple example of how to use React's `useState` with `useEffects`. 8 | 9 | ### Cleaning up an Effect 10 | 11 | ```reason 12 | [@react.component] 13 | let make = () => { 14 | React.useEffect0(() => { 15 | let id = subscription.subscribe(); 16 | /* clean up the subscription */ 17 | Some(() => subscription.unsubscribe(id)); 18 | }); 19 | } 20 | ``` 21 | 22 | ### Conditionally Firing an Effect 23 | 24 | With this, the subscription will only be recreated when `~source` changes 25 | 26 | ```reason 27 | [@react.component] 28 | let make = (~source) => { 29 | React.useEffect1(() => { 30 | let id = subscription.subscribe(); 31 | /* clean up the subscription */ 32 | Some(() => subscription.unsubscribe(id)); 33 | }, [|source|]); 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Deploy website 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | defaults: 12 | run: 13 | working-directory: ./website 14 | 15 | steps: 16 | - uses: actions/checkout@v5 17 | 18 | - uses: actions/setup-node@v5 19 | with: 20 | node-version: 24 21 | cache: 'npm' 22 | 23 | - name: Install dependencies 24 | run: npm ci 25 | 26 | - name: Build docusaurus 27 | run: npx docusaurus-write-translations 28 | 29 | - name: Setting whoaim 30 | run: | 31 | git config --global user.email "davesnx@users.noreply.github.com" 32 | git config --global user.name "Website Deployment CI" 33 | echo "machine github.com login davesnx password ${{ secrets.GH_TOKEN }}" > ~/.netrc 34 | 35 | - name: Deploy docusaurus 36 | run: GIT_USER="davesnx" USE_SSH=false npx docusaurus-publish 37 | -------------------------------------------------------------------------------- /website/static/img/logos/astrolabe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group-2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/invalid-prop-name.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Invalid Prop Name 3 | --- 4 | 5 | Prop names like `type` (as in ``) aren't syntactically valid; `type` is a reserved keyword in Reason/OCaml. Use `` instead. 6 | 7 | ### Invalid prop names on DOM elements 8 | - `type` please use `type_` instead 9 | - `as` please use `as_` instead 10 | - `open` please use `open_` instead 11 | - `begin` please use `begin_` instead 12 | - `end` please use `end_` instead 13 | - `in` please use `in_` instead 14 | - `to` please use `to_` instead 15 | 16 | For `aria-*` use camelCasing, e.g., `ariaLabel`. For DOM components, we'll translate it to `aria-label` under the hood. 17 | 18 | For `data-*` this is a bit trickier; words with `-` in them aren't valid in Reason/OCaml. When you do want to write them, e.g., `
`, use the following: 19 | 20 | ```reason 21 | React.cloneElement( 22 |
, 23 | {"data-name": "click me"} 24 | ); 25 | ``` 26 | 27 | For non-DOM components, you need to pick valid prop names. 28 | -------------------------------------------------------------------------------- /test/jest/README.md: -------------------------------------------------------------------------------- 1 | # Jest bindings 2 | 3 | > Simpler bindings for Jest, taken from [draftbit/re-jest](https://github.com/draftbit/re-jest) and 4 | > vendored here to avoid a dependency and have control over them 5 | 6 | - Write tests with the same approach as JavaScript 7 | - Uses pipe first `->` for chaining assertions (expect) 8 | - `expect` embrace a polymorphism API 9 | - Most core functions returning unit 10 | 11 | ```reason 12 | open Jest; 13 | open Expect; 14 | 15 | test("number", () => { 16 | let myNumber = 123; 17 | expect(myNumber)->toBeGreaterThan(100); 18 | expect(myNumber)->not->toBeLessThanOrEqual(1); 19 | expect(myNumber->float_of_int)->toBeLessThan(124.0); 20 | }); 21 | 22 | testAsync("promise", (finish) => { 23 | expect(Js.Promise.resolve(123))->resolves->toEqual(123); 24 | finish(); 25 | }); 26 | 27 | test("array", () => { 28 | expect([|1, 2, 3, 4, 5|])->toContain(4); 29 | }); 30 | 31 | // Still working on this one... 32 | Skip.test("This test is skipped", () => 33 | expect(1)->toBeGreaterThan(0) 34 | ); 35 | ``` 36 | -------------------------------------------------------------------------------- /website/blog/2018-08-06-050.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReasonReact 0.5: Fragment, Event, Cleanups 3 | --- 4 | 5 | It's been a while since the last blog post! Note that we've been updating ReasonReact, just that we didn't want to drown you with too many blog posts when unnecessary. As we've said in our [ReasonConf video](https://twitter.com/reasonconf/status/1002152653718245376), we wanted the ReasonReact community to catch up and settle down a bit before showering it with another batch of changes. 6 | 7 | As usual, the proper list of changes are in [HISTORY.md](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#050), likewise for the [migration script](https://github.com/chenglou/upgrade-reason-react#installation). Major highlights: 8 | 9 | - Fragment support. 10 | - `ReactEventRe` -> `ReactEvent` revamp. 11 | - General cleanups that removes a bunch of already deprecated features. 12 | 13 | There are also many other quality-of-life improvements that, combined together, should reduce the arbitrary newcomer frictions in some places. 14 | 15 | See you next time! 16 | -------------------------------------------------------------------------------- /website/core/Footer.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | 3 | class Footer extends React.Component { 4 | render() { 5 | return ( 6 | 7 | 8 | 26 | 27 | ); 28 | } 29 | } 30 | 31 | module.exports = Footer; 32 | -------------------------------------------------------------------------------- /website/static/img/logos/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /reason-react-ppx.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "React.js JSX PPX" 4 | description: "reason-react JSX PPX" 5 | maintainer: [ 6 | "David Sancho " 7 | "Antonio Monteiro " 8 | ] 9 | authors: [ 10 | "Cheng Lou " "Ricky Vetter " 11 | ] 12 | license: "MIT" 13 | homepage: "https://reasonml.github.io/reason-react" 14 | doc: "https://reasonml.github.io/reason-react" 15 | bug-reports: "https://github.com/reasonml/reason-react/issues" 16 | depends: [ 17 | "dune" {>= "3.9"} 18 | "ocaml" {>= "4.14"} 19 | "reason" {>= "3.12.0"} 20 | "ppxlib" {>= "0.33.0" & < "0.36.0"} 21 | "merlin" {with-test} 22 | "ocamlformat" {= "0.27.0" & with-dev-setup} 23 | "odoc" {with-doc} 24 | ] 25 | build: [ 26 | ["dune" "subst"] {dev} 27 | [ 28 | "dune" 29 | "build" 30 | "-p" 31 | name 32 | "-j" 33 | jobs 34 | "@install" 35 | "@runtest" {with-test} 36 | "@doc" {with-doc} 37 | ] 38 | ] 39 | dev-repo: "git+https://github.com/reasonml/reason-react.git" 40 | -------------------------------------------------------------------------------- /test/Ref__test.re: -------------------------------------------------------------------------------- 1 | open Jest; 2 | open Expect; 3 | 4 | module Button_with_ref = { 5 | [@react.component] 6 | let make = (~children, ~ref) => { 7 | ; 8 | }; 9 | }; 10 | 11 | let getByRole = (role, container) => { 12 | ReactTestingLibrary.getByRole(~matcher=`Str(role), container); 13 | }; 14 | 15 | [@mel.get] external innerHTML: Dom.element => string = "innerHTML"; 16 | 17 | describe("ref", () => { 18 | test("can be passed as prop to a component", () => { 19 | let domRef = React.createRef(); 20 | let container = 21 | ReactTestingLibrary.render( 22 | 23 | {React.string("Click me")} 24 | , 25 | ); 26 | let button = getByRole("FancyButton", container); 27 | expect(button->innerHTML)->toBe("Click me"); 28 | let content = 29 | switch (Js.Nullable.toOption(domRef.current)) { 30 | | Some(element) => element->innerHTML 31 | | None => failwith("No element found") 32 | }; 33 | expect(content)->toBe("Click me"); 34 | }) 35 | }); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/jest/Mock.re: -------------------------------------------------------------------------------- 1 | type undefined = Js.undefined(unit); 2 | 3 | let undefined: undefined = Js.Undefined.empty; 4 | 5 | type mock('a); 6 | 7 | type t('a) = mock('a); 8 | [@mel.scope "jest"] external fn: unit => 'a = "fn"; 9 | [@mel.scope "jest"] external fnWithImplementation: 'a => 'a = "fn"; 10 | [@mel.scope "jest"] external mockModule: string => unit = "mock"; 11 | external getMock: 'a => t('a) = "%identity"; 12 | [@mel.send] 13 | external mockReturnValue: (t('a), 'b) => undefined = "mockReturnValue"; 14 | let mockReturnValue = (mock, value) => { 15 | let _ = mockReturnValue(mock, value); 16 | (); 17 | }; 18 | [@mel.send] 19 | external mockImplementation: (t('a), 'a) => undefined = "mockImplementation"; 20 | let mockImplementation = (mock, value) => { 21 | let _ = mockImplementation(mock, value); 22 | (); 23 | }; 24 | [@mel.get] [@mel.scope "mock"] 25 | external calls: t('a) => array(array('b)) = "calls"; 26 | [@mel.get] [@mel.scope "mock"] [@mel.return nullable] 27 | external lastCall: t('a) => option(array('b)) = "lastCall"; 28 | [@mel.set] [@mel.scope "mock"] 29 | external clearCalls: (t('a), [@mel.as {json|[]|json}] _) => unit = "calls"; 30 | -------------------------------------------------------------------------------- /docs/style.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Style 3 | --- 4 | 5 | Since CSS-in-JS is all the rage right now, we'll recommend our official pick soon. In the meantime, for inline styles, there's the `ReactDOM.Style.make` API: 6 | 7 | ```reason 8 |
11 | ``` 12 | 13 | It's a labeled (typed!) function call that maps to the familiar style object `{color: '#444444', fontSize: '68px'}`. **Note** that `make` returns an opaque `ReactDOM.style` type that you can't read into. We also expose a `ReactDOM.Style.combine` that takes in two `style`s and combine them. 14 | 15 | ## Escape Hatch: `unsafeAddProp` 16 | 17 | The above `Style.make` API will safely type check every style field! However, we might have missed some more esoteric fields. If that's the case, the type system will tell you that the field you're trying to add doesn't exist. To remediate this, we're exposing a `ReactDOM.Style.unsafeAddProp` to dangerously add a field to a style: 18 | 19 | ```reason 20 | let myStyle = ReactDOM.Style.make(~color="#444444", ~fontSize="68px", ()); 21 | let newStyle = ReactDOM.Style.unsafeAddProp(myStyle, "width", "10px"); 22 | ``` 23 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | ### Welcome to the source of ReasonReact 2 | 3 | We want to expose the minimum amount of top-level modules possible, and at the same time want to map closely to the npm packages. So each module maps to a npm package, and each sub-module maps to a sub-module. For example: `react-dom` -> `ReactDOM` and `react-dom/server` -> `ReactDOMServer`. 4 | 5 | Files overview: 6 | 7 | ## Bindings 8 | 9 | - `React`: bindings to React 10 | - `React.Event`: bindings to React's custom events system 11 | - `React.Context`: bindings to React's Context 12 | - `ReactDOM`: bindings to ReactDOM 13 | - `ReactDOM.Style`: bindings to create `style` objects 14 | - `ReactDOMServer`: bindings to ReactDOMServer 15 | - `ReactDOMTestUtils`: helpers for testing your components 16 | 17 | ## Extra (not part of react) 18 | - `ReasonReactErrorBoundary`: component to catch errors within your component tree 19 | - `ReasonReactRouter`: a simple, yet fully featured router with minimal memory allocations 20 | 21 | Eventually `ReasonReactErrorBoundary` and `ReasonReactRouter` could live into their own packages, but for now they are part of ReasonReact (and we will keep them here for backwards compatibility). 22 | -------------------------------------------------------------------------------- /website/static/img/logos/ahrefs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/what-and-why.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What & Why 3 | --- 4 | 5 | ReasonReact helps you use [Reason](http://reasonml.github.io/) to build [React](https://reactjs.org/) components with deeply integrated, strong, static type safety. 6 | 7 | It is designed and built by people using Reason and React in large, mission critical production React codebases. 8 | 9 | ReasonReact uses Reason's expressive language features, along with smooth JavaScript interop to provide a React API that is: 10 | 11 | - Safe and statically typed (with full type inference). 12 | - Simple and lean. 13 | - Familiar and easy to insert into an existing ReactJS codebase. 14 | 15 | One of ReactJS's strengths is that it uses the programming language's features to implement framework features. It is often said that ReactJS feels like an extension of the programming language. ReasonReact pushes that philosophy further because the Reason syntax and language features are a better match for React programming patterns. ReasonReact also uses built-in language features to seamlessly integrate into other UI framework patterns left unaddressed by ReactJS. Routing, data management, component composition and components themselves all feel like "just using Reason". 16 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs_2" 6 | }, 7 | "locked": { 8 | "lastModified": 1743885887, 9 | "narHash": "sha256-m7/Dlkq+sS9d+Ypg0tg7MIK+UupfRvtLMdtY4JVWc1Q=", 10 | "owner": "nix-ocaml", 11 | "repo": "nix-overlays", 12 | "rev": "475a35ae7f8d96254cdb57ee5ccd042c74390562", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "nix-ocaml", 17 | "repo": "nix-overlays", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs_2": { 22 | "locked": { 23 | "lastModified": 1743862455, 24 | "narHash": "sha256-I/QXtrqznq1321mYR9TyMPX/zCWb9iAH64hO+pEBY00=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "06f3516b0397bd241bde2daefc8538fc886c5467", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "repo": "nixpkgs", 33 | "rev": "06f3516b0397bd241bde2daefc8538fc886c5467", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "nixpkgs": "nixpkgs" 40 | } 41 | } 42 | }, 43 | "root": "root", 44 | "version": 7 45 | } 46 | -------------------------------------------------------------------------------- /docs/event.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Event 3 | --- 4 | 5 | ReasonReact events map cleanly to ReactJS [synthetic events](https://reactjs.org/docs/events.html). ReasonReact exposes a module called [`React.Event`](https://github.com/reasonml/reason-react/blob/main/src/React.rei#L1) to help you work with events. 6 | 7 | React.Event module contains all event types as submodules, e.g. `React.Event.Form`, `React.Event.Mouse`, etc. 8 | 9 | You can access their properties using the `React.Event.{{EventName}}.{{property}}` method. For example, to access the `target` property of a `React.Event.Form.t` event, you would use `React.Event.Form.target`. 10 | 11 | Since `target` is a JavaScript Object, those are typed as [`Js.t`][using-jst-objects]. If you're accessing fields on an object, like `event.target.value`, you'd use [Melange's `##` object access FFI][using-jst-objects]: 12 | 13 | ```reason 14 | React.Event.Form.target(event)##value; 15 | ``` 16 | 17 | Or, equivalently, using the pipe first operator: 18 | 19 | ```reason 20 | event->React.Event.Form.target##value; 21 | ``` 22 | 23 | More info in the [inline docs](https://github.com/reasonml/reason-react/blob/main/src/React.rei#L1) on the interface file. 24 | 25 | [using-jst-objects]: https://melange.re/v4.0.0/communicate-with-javascript/#using-jst-objects 26 | -------------------------------------------------------------------------------- /docs/element-type-is-invalid.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Element Type is Invalid Runtime Error 3 | --- 4 | 5 | If you run your app and see in the console the error: 6 | 7 | > "element type is invalid... You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports" 8 | 9 | This likely means that: 10 | 11 | - You're wrapping a JS component for ReasonReact using with [`ReasonReact.wrapJsForReason`](interop.md#reasonreact-using-reactjs). 12 | - The JS component uses ES6 default export (`export default MyComponent`) (or, you forgot to export the component altogether!). 13 | - You're using Babel/Webpack to compile those ES6 modules. 14 | 15 | This is a common mistake. Please see Melange's [Import an ES6 Default Value][default-es6-values]. Aka, instead of: 16 | 17 | ```reason 18 | [@mel.module] external myJSReactClass: ReasonReact.reactClass = "./myJSReactClass"; 19 | ``` 20 | 21 | Use: 22 | 23 | ```reason 24 | [@mel.module "./myJSReactClass"] external myJSReactClass: ReasonReact.reactClass = "default"; 25 | ``` 26 | 27 | Remember that Reason doesn't have runtime type errors! So it _must_ have meant that your binding was written wrongly. 28 | 29 | [default-es6-values]: https://melange.re/v4.0.0/communicate-with-javascript/#default-es6-values 30 | -------------------------------------------------------------------------------- /docs/usereducer-hook.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useReducer 3 | --- 4 | 5 | [React.js docs for useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) 6 | 7 | > useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks. 8 | 9 | ```reason 10 | /* we can create anything as the type for action, here we use a variant with 2 cases. */ 11 | type action = 12 | | Increment 13 | | Decrement; 14 | 15 | /* `state` could also be anything. In this case, we want an int */ 16 | let reducer = (state, action) => 17 | switch (action) { 18 | | Increment => state + 1 19 | | Decrement => state - 1 20 | }; 21 | 22 | [@react.component] 23 | let make = (~initialValue=0) => { 24 | let (state, dispatch) = React.useReducer(reducer, initialValue); 25 | 26 |
27 |
state->React.int
28 | 31 | 34 |
; 35 | }; 36 | ``` 37 | -------------------------------------------------------------------------------- /ppx/test/signature-optional.t/input.re: -------------------------------------------------------------------------------- 1 | module Greeting: { 2 | [@react.component] 3 | let make: (~mockup: string=?) => React.element; 4 | } = { 5 | [@react.component] 6 | let make = (~mockup: option(string)=?) => { 7 | ; 8 | }; 9 | }; 10 | 11 | module MyPropIsOptionBool = { 12 | [@react.component] external make: (~myProp: bool=?) => React.element = "A"; 13 | }; 14 | 15 | module MyPropIsOptionOptionBool = { 16 | [@react.component] 17 | external make: (~myProp: option(bool)=?) => React.element = "B"; 18 | }; 19 | 20 | module MyPropIsOptionOptionBoolWithSig: { 21 | [@react.component] 22 | external make: (~myProp: option(bool)=?) => React.element = "B"; 23 | } = { 24 | [@react.component] 25 | external make: (~myProp: option(bool)=?) => React.element = "B"; 26 | }; 27 | 28 | module MyPropIsOptionOptionBoolWithValSig: { 29 | [@react.component] 30 | let make: (~myProp: option(bool)=?) => React.element; 31 | } = { 32 | [@react.component] 33 | external make: (~myProp: option(bool)=?) => React.element = "B"; 34 | }; 35 | 36 | module MyPropIsOptionOptionBoolLetWithValSig: { 37 | [@react.component] 38 | let make: (~myProp: option(bool)=?) => React.element; 39 | } = { 40 | [@react.component] 41 | let make = (~myProp: option(option(bool))=?) => React.null; 42 | }; 43 | -------------------------------------------------------------------------------- /website/blog/2020-06-08-090-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReasonReact 0.9.0 - Uncurried and Code Cleanup 3 | --- 4 | 5 | Release 0.9.0 is primarily to setup for larger changes in coming BuckleScript and ReasonReact release, but that's not all that's there. Exciting new features include `React.Uncurried` hooks which provide even cleaner JS output and better performance for hooks that get called in hot loops. Additionally we now have better support for `Strict` mode, `ErrorBoundaries`, and pointer events. 6 | 7 | There are two breaking changes which most people are unlikely to hit: 8 | 9 | 1. We have stopped versioning lib/js and will not ship on NPM. If you are using this library from Reason you will be unaffected. If you're using directly from handwritten JS, you could run into issues. If you want to continue doing this, you can `include` the code you're using into a Reason file in your codebase where you have more control over how it is exposed. 10 | 2. We have changed the APIs of some of the `ReactDOM.Experimental` namespace. As the namespace implies, this code is not intended for production app use and you likely will not notice any difference here. 11 | 12 | Take a look at https://github.com/reasonml/reason-react/blob/main/HISTORY.md#090-052020 for a full listing of all the changes and credit to the incredible folks who have contributed. 13 | -------------------------------------------------------------------------------- /test/blackbox-tests/useCallback.t: -------------------------------------------------------------------------------- 1 | 2 | $ cat >dune-project < (lang dune 3.9) 4 | > (using melange 0.1) 5 | > EOF 6 | 7 | $ cat >dune < (melange.emit 9 | > (target js-out) 10 | > (emit_stdlib false) 11 | > (preprocess (pps melange.ppx)) 12 | > (libraries reason-react)) 13 | > EOF 14 | 15 | $ cat >x.ml < let cb = React.useCallback0(fun a b -> a + b) 17 | > EOF 18 | 19 | $ unset DUNE_CACHE_ROOT 20 | $ dune build @melange 21 | $ cat _build/default/js-out/x.js 22 | // Generated by Melange 23 | 'use strict'; 24 | 25 | const React = require("react"); 26 | 27 | const cb = React.useCallback((function (a, b) { 28 | return a + b | 0; 29 | }), []); 30 | 31 | module.exports = { 32 | cb, 33 | } 34 | /* cb Not a pure module */ 35 | 36 | Using an Uncurried function: 37 | 38 | $ cat >x.ml < let cb = React.useCallback0(fun[@u] a b -> a + b) 40 | > EOF 41 | 42 | $ dune clean 43 | $ dune build @melange 44 | $ cat _build/default/js-out/x.js 45 | // Generated by Melange 46 | 'use strict'; 47 | 48 | const React = require("react"); 49 | 50 | const cb = React.useCallback((function (a, b) { 51 | return a + b | 0; 52 | }), []); 53 | 54 | module.exports = { 55 | cb, 56 | } 57 | /* cb Not a pure module */ 58 | 59 | -------------------------------------------------------------------------------- /crowdin.yaml: -------------------------------------------------------------------------------- 1 | project_identifier_env: CROWDIN_DOCUSAURUS_PROJECT_ID 2 | api_key_env: CROWDIN_DOCUSAURUS_API_KEY 3 | base_path: "./" 4 | preserve_hierarchy: true 5 | 6 | files: 7 | - 8 | source: '/docs/*.md' 9 | translation: '/website/translated_docs/%locale%/%original_file_name%' 10 | languages_mapping: &anchor 11 | locale: 12 | 'af': 'af' 13 | 'ar': 'ar' 14 | 'bs-BA': 'bs-BA' 15 | 'ca': 'ca' 16 | 'cs': 'cs' 17 | 'da': 'da' 18 | 'de': 'de' 19 | 'el': 'el' 20 | 'es-ES': 'es-ES' 21 | 'fa': 'fa-IR' 22 | 'fi': 'fi' 23 | 'fr': 'fr' 24 | 'he': 'he' 25 | 'hu': 'hu' 26 | 'id': 'id-ID' 27 | 'it': 'it' 28 | 'ja': 'ja' 29 | 'ko': 'ko' 30 | 'mr': 'mr-IN' 31 | 'nl': 'nl' 32 | 'no': 'no-NO' 33 | 'pl': 'pl' 34 | 'pt-BR': 'pt-BR' 35 | 'pt-PT': 'pt-PT' 36 | 'ro': 'ro' 37 | 'ru': 'ru' 38 | 'sk': 'sk-SK' 39 | 'sr': 'sr' 40 | 'sv-SE': 'sv-SE' 41 | 'tr': 'tr' 42 | 'uk': 'uk' 43 | 'vi': 'vi' 44 | 'zh-CN': 'zh-CN' 45 | 'zh-TW': 'zh-TW' 46 | 'en-UD': 'en-UD' 47 | - 48 | source: '/website/i18n/en.json' 49 | translation: '/website/i18n/%locale%.json' 50 | languages_mapping: *anchor 51 | -------------------------------------------------------------------------------- /website/pages/en/built-with-reason-react.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | 3 | const CompLibrary = require("../../core/CompLibrary.js"); 4 | const Container = CompLibrary.Container; 5 | 6 | const translate = require("../../server/translate.js").translate; 7 | 8 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 9 | 10 | class Users extends React.Component { 11 | render() { 12 | const showcase = siteConfig.users.map(user => { 13 | return ( 14 | 15 | 19 | 20 | ); 21 | }); 22 | 23 | return ( 24 |
42 | ); 43 | } 44 | } 45 | 46 | module.exports = Users; 47 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Demo reason-react 9 | 10 | 21 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ppx/test/hover.t: -------------------------------------------------------------------------------- 1 | Test some locations in reason-react components, reproduces #840 2 | 3 | $ cat >dune-project < (lang dune 3.13) 5 | > (using melange 0.1) 6 | > EOF 7 | 8 | $ cat >dune < (melange.emit 10 | > (alias foo) 11 | > (target foo) 12 | > (libraries reason-react) 13 | > (emit_stdlib false) 14 | > (preprocess 15 | > (pps melange.ppx reason-react-ppx))) 16 | > EOF 17 | 18 | $ cat >component.ml < let[@react.component] make ~foo ~bar = 20 | > (div 21 | > ~children:[ React.string foo; bar |> string_of_int |> React.string ] 22 | > () [@JSX]) 23 | > EOF 24 | $ dune build @foo 25 | 26 | Let's test hovering over parts of the component 27 | 28 | `React.string` 29 | 30 | $ ocamlmerlin single type-enclosing -position 3:25 -verbosity 0 \ 31 | > -filename component.ml < component.ml | jq '.value[0]' 32 | { 33 | "start": { 34 | "line": 3, 35 | "col": 17 36 | }, 37 | "end": { 38 | "line": 3, 39 | "col": 29 40 | }, 41 | "type": "string -> React.element", 42 | "tail": "no" 43 | } 44 | 45 | The `foo` variable inside the component body 46 | 47 | $ ocamlmerlin single type-enclosing -position 3:31 -verbosity 0 \ 48 | > -filename component.ml < component.ml | jq '.value[0]' 49 | { 50 | "start": { 51 | "line": 3, 52 | "col": 30 53 | }, 54 | "end": { 55 | "line": 3, 56 | "col": 33 57 | }, 58 | "type": "string", 59 | "tail": "no" 60 | } 61 | -------------------------------------------------------------------------------- /reason-react.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Reason bindings for React.js" 4 | description: """ 5 | reason-react helps you use Reason to build React components with deeply integrated, strong, static type safety. 6 | 7 | It is designed and built by people using Reason and React in large, mission critical production React codebases.""" 8 | maintainer: [ 9 | "David Sancho " 10 | "Antonio Monteiro " 11 | ] 12 | authors: [ 13 | "Cheng Lou " "Ricky Vetter " 14 | ] 15 | license: "MIT" 16 | homepage: "https://reasonml.github.io/reason-react" 17 | doc: "https://reasonml.github.io/reason-react" 18 | bug-reports: "https://github.com/reasonml/reason-react/issues" 19 | depends: [ 20 | "dune" {>= "3.9"} 21 | "ocaml" 22 | "melange" {<= "5.1.0"} 23 | "reason-react-ppx" {= version} 24 | "reason" {>= "3.12.0"} 25 | "ocaml-lsp-server" {with-dev-setup} 26 | "opam-check-npm-deps" {= "3.0.1" & with-dev-setup} 27 | "ocamlformat" {= "0.27.0" & with-dev-setup} 28 | "odoc" {with-doc} 29 | ] 30 | build: [ 31 | ["dune" "subst"] {dev} 32 | [ 33 | "dune" 34 | "build" 35 | "-p" 36 | name 37 | "-j" 38 | jobs 39 | "@install" 40 | "@runtest" {with-test} 41 | "@doc" {with-doc} 42 | ] 43 | ] 44 | dev-repo: "git+https://github.com/reasonml/reason-react.git" 45 | depexts: [ 46 | ["react"] {npm-version = "^19.1.0"} 47 | ["react-dom"] {npm-version = "^19.1.0"} 48 | ] 49 | -------------------------------------------------------------------------------- /ppx/test/fragment.t/run.t: -------------------------------------------------------------------------------- 1 | $ ../ppx.sh --output re input.re 2 | let fragment = foo => 3 | [@bla] 4 | React.jsx( 5 | React.jsxFragment, 6 | ([@merlin.hide] ReactDOM.domProps)(~children=React.array([|foo|]), ()), 7 | ); 8 | let just_one_child = foo => 9 | React.jsx( 10 | React.jsxFragment, 11 | ([@merlin.hide] ReactDOM.domProps)(~children=React.array([|bar|]), ()), 12 | ); 13 | let poly_children_fragment = (foo, bar) => 14 | React.jsxs( 15 | React.jsxFragment, 16 | ([@merlin.hide] ReactDOM.domProps)( 17 | ~children=React.array([|foo, bar|]), 18 | (), 19 | ), 20 | ); 21 | let nested_fragment = (foo, bar, baz) => 22 | React.jsxs( 23 | React.jsxFragment, 24 | ([@merlin.hide] ReactDOM.domProps)( 25 | ~children= 26 | React.array([| 27 | foo, 28 | React.jsxs( 29 | React.jsxFragment, 30 | ([@merlin.hide] ReactDOM.domProps)( 31 | ~children=React.array([|bar, baz|]), 32 | (), 33 | ), 34 | ), 35 | |]), 36 | (), 37 | ), 38 | ); 39 | let nested_fragment_with_lower = foo => 40 | React.jsx( 41 | React.jsxFragment, 42 | ([@merlin.hide] ReactDOM.domProps)( 43 | ~children= 44 | React.array([| 45 | ReactDOM.jsx( 46 | "div", 47 | ([@merlin.hide] ReactDOM.domProps)(~children=foo, ()), 48 | ), 49 | |]), 50 | (), 51 | ), 52 | ); 53 | -------------------------------------------------------------------------------- /src/ReactDOMServerNode.rei: -------------------------------------------------------------------------------- 1 | [@deriving (jsProperties, getSet)] 2 | type options = { 3 | [@mel.optional] 4 | bootstrapScriptContent: option(string), 5 | [@mel.optional] 6 | bootstrapScripts: option(array(string)), 7 | [@mel.optional] 8 | bootstrapModules: option(array(string)), 9 | [@mel.optional] 10 | identifierPrefix: option(string), 11 | [@mel.optional] 12 | namespaceURI: option(string), 13 | [@mel.optional] 14 | nonce: option(string), 15 | [@mel.optional] 16 | onAllReady: option(unit => unit), 17 | [@mel.optional] 18 | onError: option(Js.Exn.t => unit), 19 | [@mel.optional] 20 | onShellReady: option(unit => unit), 21 | [@mel.optional] 22 | onShellError: option(Js.Exn.t => unit), 23 | [@mel.optional] 24 | progressiveChunkSize: option(int), 25 | }; 26 | 27 | type pipeableStream('a) = { 28 | /* Using empty object instead of Node.stream since Melange don't provide a binding to node's Stream (https://nodejs.org/api/stream.html) */ 29 | pipe: Js.t({..} as 'a) => unit, 30 | abort: unit => unit, 31 | }; 32 | 33 | let renderToPipeableStream: 34 | ( 35 | ~bootstrapScriptContent: string=?, 36 | ~bootstrapScripts: array(string)=?, 37 | ~bootstrapModules: array(string)=?, 38 | ~identifierPrefix: string=?, 39 | ~namespaceURI: string=?, 40 | ~nonce: string=?, 41 | ~onAllReady: unit => unit=?, 42 | ~onError: Js.Exn.t => unit=?, 43 | ~onShellReady: unit => unit=?, 44 | ~onShellError: Js.Exn.t => unit=?, 45 | ~progressiveChunkSize: int=?, 46 | React.element 47 | ) => 48 | pipeableStream('a); 49 | -------------------------------------------------------------------------------- /test/jest/Expect.re: -------------------------------------------------------------------------------- 1 | type t('a); 2 | 3 | external expect: 'a => t('a) = "expect"; 4 | 5 | [@mel.send] external toEqual: (t('a), 'a) => unit = "toEqual"; 6 | [@mel.send] external toBe: (t('a), 'a) => unit = "toBe"; 7 | [@mel.get] external not: t('a) => t('a) = "not"; 8 | [@mel.send] 9 | external toMatchSnapshot: (t('a), unit) => unit = "toMatchSnapshot"; 10 | 11 | [@mel.send] external toThrowSomething: t('a => 'b) => unit = "toThrow"; 12 | 13 | [@mel.send] external toBeGreaterThan: (t('a), 'a) => unit = "toBeGreaterThan"; 14 | [@mel.send] external toBeLessThan: (t('a), 'a) => unit = "toBeLessThan"; 15 | [@mel.send] 16 | external toBeGreaterThanOrEqual: (t('a), 'a) => unit = 17 | "toBeGreaterThanOrEqual"; 18 | [@mel.send] 19 | external toBeLessThanOrEqual: (t('a), 'a) => unit = "toBeLessThanOrEqual"; 20 | [@mel.send] 21 | external toHaveLength: (t(array('a)), 'a) => unit = "toHaveLength"; 22 | 23 | [@mel.get] 24 | external rejects: t(Js.Promise.t('a)) => t(unit => 'a) = "rejects"; 25 | 26 | [@mel.send] external toContain: (t(array('a)), 'a) => unit = "toContain"; 27 | 28 | // This isn't a real string, but it can be used to construct a predicate on a string 29 | // expect("hello world")->toEqual(stringContaining("hello")); 30 | [@mel.scope "expect"] 31 | external stringContaining: string => string = "stringContaining"; 32 | 33 | // This isn't a real array, but it can be used to construct a predicate on an array 34 | // expect([|"x", "y", "z"|])->toEqual(arrayContaining([|"x", "z"|])) 35 | [@mel.scope "expect"] 36 | external arrayContaining: array('a) => array('a) = "arrayContaining"; 37 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # ReasonReact Documentation Website 2 | 3 | This code is used to generate [https://reasonml.github.io/reason-react](https://reasonml.github.io/reason-react). It pulls in files from `../docs` and the current directory to generate the final static html files that's served on the site. 4 | 5 | `website/` contains the actual js, css, images and other files (and blog, which contains some markdown files too, these are separated from `docs/`, not too important). 6 | 7 | `yarn start` to start the server & watcher. The other scripts in package.json should also be self-descriptive. 8 | 9 | Don't use `yarn build` for now. 10 | 11 | In the end, we spit out normal HTML, with all the JS dependencies (barring a few critical ones) removed, including ReactJS itself. It's a full, static website, super lightweight, portable, unfancy but good looking. 12 | 13 | Two special files: 14 | 15 | - `sidebars.json`: lists the sections. 16 | 17 | - `siteConfig.json`: some header and i18n configs. 18 | 19 | During your development, most changes will be picked up at each browser refresh. If you touch these two files or `blog/`, however, you'll have to restart the server to see the changes. 20 | 21 | ## Translations 22 | 23 | The entire site can be translated via the [Crowdin project](https://crowdin.com/project/reason-react). This repo only has the canonical english documentation. Don't manually edit things in `i18n/`. 24 | 25 | ## Debugging 26 | 27 | `console.log`s appear in your terminal! Since the site itself is React-free. 28 | 29 | ## Building and Deploying 30 | 31 | *TODO* 32 | Ping @rickyvetter if you'd like to try it eagerly. 33 | -------------------------------------------------------------------------------- /website/blog/2017-10-02-new-docs-site.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: New Docs Site 3 | --- 4 | 5 | _The ReactJS doc site has also been updated last friday **by pure coincidence**. No conspiracy theory!_ 6 | 7 | Do you like the new look? There's been quite a bit of iterations on the new docs, with an eye toward settings up site templates for our future libraries API docs effort (more on that another time). Meawhile, true to our community's spirit: 8 | 9 | - The new docs site is much more readable, and just as simple, lean, fast, and easy to read and contribute to. 10 | - It's done by a prominent community member, [@rickyvetter](https://twitter.com/@rickyvetter), who took every nit seriously and cleaned up every corner of the documentation layout, formatting, responsiveness, reorganization, etc. Quality open source contribution right here =) 11 | - The existing links are preserved and show you the correct page to redirect to. Take your time migrating your links to the new ones; we'll keep them around for a while. 12 | - We care about our community across the globe! There is now dedicated translation support. Click on that symbol on the upper right corner! Translating the docs is a good way to learning. Just ask [@charles_mangwa](https://twitter.com/charles_mangwa) who learned Reason by translating the Reason docs to [French](https://reasonml-fr.surge.sh). 13 | - Works with JavaScript off (falls out naturally from the doc site architecture). 14 | 15 | This community is the best part of Reason. In the end, it's all about people. Tweet to [@rickyvetter](https://twitter.com/@rickyvetter) to show some thanks for his excellent work! It makes contributing in his free time much more worthwhile. 16 | -------------------------------------------------------------------------------- /website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Getting Started": [ 4 | "what-and-why", 5 | "installation", 6 | "intro-example" 7 | ], 8 | "Core": [ 9 | "components", 10 | "jsx", 11 | "event", 12 | "style", 13 | "router", 14 | "dom", 15 | "refs", 16 | "testing" 17 | ], 18 | "Hooks": [ 19 | "usestate-hook", 20 | "usereducer-hook", 21 | "useeffect-hook", 22 | "custom-hooks" 23 | ], 24 | "ReactJS Idioms Equivalents": [ 25 | "invalid-prop-name", 26 | "props-spread", 27 | "component-as-prop", 28 | "ternary-shortcut", 29 | "context", 30 | "custom-class-component-property", 31 | "error-boundaries" 32 | ], 33 | "FAQ": [ 34 | "im-having-a-type-error", 35 | "record-field-send-handle-not-found", 36 | "send-handle-callbacks-having-incompatible-types", 37 | "i-really-need-feature-x-from-reactjs", 38 | "i-want-to-create-a-dom-element-without-jsx" 39 | ] 40 | }, 41 | "examples": { 42 | "Recipes & Snippets": [ 43 | "simple", 44 | "adding-data-props", 45 | "working-with-optional-data", 46 | "render-props", 47 | "importing-js-into-reason", 48 | "importing-reason-into-js", 49 | "reason-using-js", 50 | "js-using-reason", 51 | "gentype", 52 | "example-projects", 53 | "graphql-apollo", 54 | "tailwind-css" 55 | ] 56 | }, 57 | "community-menu": { 58 | "Community": [ 59 | "community", 60 | "roadmap" 61 | ] 62 | }, 63 | "try-menu": { 64 | "Try": [ 65 | "playground" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ppx/test/react.t: -------------------------------------------------------------------------------- 1 | Demonstrate how to use the React JSX PPX 2 | 3 | $ cat > dune-project < (lang dune 3.8) 5 | > (using melange 0.1) 6 | > EOF 7 | 8 | $ cat > dune < (melange.emit 10 | > (target output) 11 | > (alias mel) 12 | > (compile_flags :standard -w -20) 13 | > (emit_stdlib false) 14 | > (libraries reason-react melange.belt) 15 | > (preprocess (pps melange.ppx reason-react-ppx))) 16 | > EOF 17 | 18 | $ cat > x.re < module App = { 20 | > [@react.component] 21 | > let make = () => 22 | > ["Hello!", "This is React!"] 23 | > ->Belt.List.map(greeting =>

greeting->React.string

) 24 | > ->Belt.List.toArray 25 | > ->React.array; 26 | > }; 27 | > let () = 28 | > Js.log2("Here's two:", 2); 29 | > ignore() 30 | > EOF 31 | 32 | $ dune build @mel 33 | 34 | $ cat _build/default/output/x.js 35 | // Generated by Melange 36 | 'use strict'; 37 | 38 | const Belt__Belt_List = require("melange.belt/belt_List.js"); 39 | const JsxRuntime = require("react/jsx-runtime"); 40 | 41 | function X$App(Props) { 42 | return Belt__Belt_List.toArray(Belt__Belt_List.map({ 43 | hd: "Hello!", 44 | tl: { 45 | hd: "This is React!", 46 | tl: /* [] */ 0 47 | } 48 | }, (function (greeting) { 49 | return JsxRuntime.jsx("h1", { 50 | children: greeting 51 | }); 52 | }))); 53 | } 54 | 55 | const App = { 56 | make: X$App 57 | }; 58 | 59 | console.log("Here's two:", 2); 60 | 61 | JsxRuntime.jsx(X$App, {}); 62 | 63 | module.exports = { 64 | App, 65 | } 66 | /* Not a pure module */ 67 | -------------------------------------------------------------------------------- /docs/tailwind-css.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Styling: Tailwind CSS 3 | --- 4 | 5 | [Tailwind CSS](https://tailwindcss.com) is a CSS framework that is rapidly 6 | growing in popularity. It's completely customizable and lightweight, making it 7 | a perfect companion to React. If you're not familiar with Tailwind, we recommend 8 | checking out [their docs](https://tailwindcss.com/#what-is-tailwind) for 9 | a gentle introduction before moving forward. 10 | 11 | ## Setting up Tailwind 12 | 13 | Now let's see how we can use Tailwind within ReasonReact and start building an 14 | app! 15 | 16 | First, you'll need to create a new ReasonReact project -- we recommend [this 17 | template](https://github.com/bodhish/create-reason-react-tailwind) (select the 18 | `tailwind-starter` option) which has Tailwind set up out of the box. Once you've 19 | installed the dependencies with `yarn install` or `npm install`, you should be 20 | ready to go. 21 | 22 | Let's see an example of a React component using Tailwind: 23 | 24 | ```reason 25 | [@react.component] 26 | let make = () => 27 |
28 |
29 | Sunset in the mountains 30 |
31 |
{React.string("Tailwind")}
32 |

33 | {React.string("A reason react starter with tailwind")} 34 |

35 |
36 |
37 |
; 38 | ``` 39 | 40 | which gives us the following UI: 41 | 42 | 43 | -------------------------------------------------------------------------------- /ppx/test/signature.t/run.t: -------------------------------------------------------------------------------- 1 | $ ../ppx.sh --output re input.re 2 | module Example = { 3 | [@mel.obj] 4 | external makeProps: 5 | ( 6 | ~cond: bool, 7 | ~noWrap: bool=?, 8 | ~href: string, 9 | ~id: string=?, 10 | ~color: color, 11 | ~mode: linkMode=?, 12 | ~children: React.element, 13 | ~key: string=?, 14 | unit 15 | ) => 16 | { 17 | . 18 | "cond": bool, 19 | "noWrap": option(bool), 20 | "href": string, 21 | "id": option(string), 22 | "color": color, 23 | "mode": option(linkMode), 24 | "children": React.element, 25 | }; 26 | external make: 27 | React.componentLike( 28 | { 29 | . 30 | "cond": bool, 31 | "noWrap": option(bool), 32 | "href": string, 33 | "id": option(string), 34 | "color": color, 35 | "mode": option(linkMode), 36 | "children": React.element, 37 | }, 38 | React.element, 39 | ); 40 | }; 41 | module MyPropIsOptionBool = { 42 | [@mel.obj] 43 | external makeProps: 44 | (~myProp: bool=?, ~key: string=?, unit) => {. "myProp": option(bool)}; 45 | external make: 46 | React.componentLike({. "myProp": option(bool)}, React.element) = 47 | "A"; 48 | }; 49 | module MyPropIsOptionOptionBool = { 50 | [@mel.obj] 51 | external makeProps: 52 | (~myProp: option(bool)=?, ~key: string=?, unit) => 53 | {. "myProp": option(option(bool))}; 54 | external make: 55 | React.componentLike({. "myProp": option(option(bool))}, React.element) = 56 | "B"; 57 | }; 58 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.9) 2 | 3 | (using melange 0.1) 4 | 5 | (generate_opam_files true) 6 | 7 | (cram enable) 8 | 9 | (name reason-react) 10 | 11 | (maintainers 12 | "David Sancho " 13 | "Antonio Monteiro ") 14 | 15 | (authors 16 | "Cheng Lou " 17 | "Ricky Vetter ") 18 | 19 | (source 20 | (github reasonml/reason-react)) 21 | 22 | (homepage "https://reasonml.github.io/reason-react") 23 | 24 | (documentation "https://reasonml.github.io/reason-react") 25 | 26 | (bug_reports "https://github.com/reasonml/reason-react/issues") 27 | 28 | (license "MIT") 29 | 30 | (package 31 | (name reason-react) 32 | (synopsis "Reason bindings for React.js") 33 | (description 34 | "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.") 35 | (depends 36 | ocaml 37 | (melange (<= 5.1.0)) 38 | (reason-react-ppx 39 | (= :version)) 40 | (reason 41 | (>= 3.12.0)) 42 | (ocaml-lsp-server :with-dev-setup) 43 | (opam-check-npm-deps 44 | (and 45 | (= 3.0.1) 46 | :with-dev-setup)) 47 | (ocamlformat 48 | (and 49 | (= 0.27.0) 50 | :with-dev-setup)))) 51 | 52 | (package 53 | (name reason-react-ppx) 54 | (synopsis "React.js JSX PPX") 55 | (description "reason-react JSX PPX") 56 | (depends 57 | (ocaml 58 | (>= 4.14)) 59 | (reason 60 | (>= 3.12.0)) 61 | (ppxlib 62 | (and (>= 0.33.0) (< 0.36.0))) 63 | (merlin :with-test) 64 | (ocamlformat 65 | (and 66 | (= 0.27.0) 67 | :with-dev-setup)))) 68 | -------------------------------------------------------------------------------- /docs/refs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Refs in React 3 | --- 4 | 5 | _Not to be confused with Reason `ref`, the language feature that enables mutation_. 6 | 7 | Refs in React come in two forms. One works a lot like Reason `ref` - it is an object with a single field `current` that gets mutated. The other is a function which gets called whenever the ref value changes. ReasonReact works with both. `React.Ref.t` is the ref with a value that mutates. You create this kind of ref with the `useRef` hook or with `createRef` in the Record API. 8 | 9 | There are many cases where you might want to use refs to track things that don't necessarily need to trigger a re-render but are useful for logging or some other side effect. In this case you can use `useRef` with any value you'd like to track! 10 | 11 | ```reason 12 | [@react.component] 13 | let make = () => { 14 | let clicks = React.useRef(0); 15 | 16 |
clicks.current = clicks.current + 1} />; 17 | }; 18 | ``` 19 | 20 | DOM elements allow you to pass refs to track specific elements that have been rendered for side-effects outside of React's control. To do this you can use the `ReactDOM.Ref` module. 21 | 22 | ```reason 23 | [@react.component] 24 | let make = () => { 25 | let divRef = React.useRef(Js.Nullable.null); 26 | 27 | React.useEffect(() => { 28 | doSomething(divRef); 29 | }); 30 | 31 |
; 32 | }; 33 | ``` 34 | 35 | For some cases it's easier to work with callback refs which get called when the DOM node changes. We support this use-case as well using `ReactDOM.Ref.callbackDomRef`. 36 | 37 | ```reason 38 | [@react.component] 39 | let make = () => { 40 |
41 | doEffectWhenRefChanges(ref) 42 | )} />; 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest! Below we describe reason-react development setup for the project. 4 | 5 | ```sh 6 | git clone https://github.com/reasonml/reason-react.git 7 | cd reason-react 8 | ``` 9 | 10 | ## Installation 11 | 12 | To set up a development environment using [opam](https://opam.ocaml.org/), run `make init` to set up an opam [local switch](https://opam.ocaml.org/blog/opam-local-switches/) and download the required dependencies. 13 | 14 | ## Developing 15 | 16 | ```sh 17 | make dev ## Build in watch mode 18 | make test ## Run the unit tests 19 | ``` 20 | 21 | Running `make help` you can see the common commands to interact with the project: 22 | 23 | ```sh 24 | build-prod Build for production (--profile=prod) 25 | build Build the project, including non installable libraries and executables 26 | clean Clean artifacts 27 | create-switch Create a local opam switch 28 | dev Build in watch mode 29 | format-check Checks if format is correct 30 | format Format the codebase with ocamlformat 31 | help Print this help message 32 | init Create a local opam switch, install deps 33 | install Update the package dependencies when new deps are added to dune-project 34 | test-promote Updates snapshots and promotes it to correct 35 | test-watch Run the unit tests in watch mode 36 | test Run the unit tests 37 | ``` 38 | 39 | ## Submitting a Pull Request 40 | 41 | When you are almost ready to open a PR, it's a good idea to run the test suite locally to make sure everything works: 42 | 43 | ```sh 44 | make test 45 | ``` 46 | 47 | If that all passes, then congratulations! You are well on your way to becoming a contributor 🎉 48 | -------------------------------------------------------------------------------- /website/static/js/redirectBlog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is not run through any build step! Don't add any fancy stuff 3 | */ 4 | 5 | (function() { 6 | var path = window.location.pathname.split('/'); 7 | var page = path[path.length - 1]; 8 | if (page.indexOf('blog') !== 0) { 9 | return; 10 | } 11 | // redirects[page][hash] => new page; 12 | var redirects = { 13 | '#reducers-are-here': '2017/09/01/reducers.html', 14 | '#reducers-are-here-design-decisions': '2017/09/01/reducers.html#design-decisions', 15 | '#021-released': '2017/07/05/021.html', 16 | '#015-released': '2017/06/21/015.html', 17 | '#major-new-release': '2017/06/09/major-release.html' 18 | }; 19 | 20 | var hash = window.location.hash 21 | var base = '/reason-react/blog/'; 22 | Object.keys(redirects).forEach(function(redirect) { 23 | if (redirect === hash) { 24 | // setup html 25 | var bannerString = '
Hello! This particular blog post has moved to . Please update the URLs to reflect it. Thanks!
'; 26 | var div = document.createElement('div'); 27 | div.innerHTML = bannerString; 28 | var redirectBanner = div.firstChild; 29 | var navPusher = document.querySelector('.navPusher'); 30 | navPusher.insertBefore(redirectBanner, navPusher.firstChild); 31 | 32 | var newHash = redirect.split(hash + '-')[1] || ''; 33 | newHash = newHash ? '#' + newHash : newHash; 34 | var link = document.getElementById('redirectLink'); 35 | var banner = document.getElementById('redirectBanner'); 36 | var location = base + redirects[redirect] + newHash; 37 | 38 | link.textContent = 'https://reasonml.github.io' + location; 39 | link.href = location; 40 | banner.style.display = 'block'; 41 | } 42 | }); 43 | })(); 44 | -------------------------------------------------------------------------------- /website/static/js/redirect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is not run through any build step! Don't add any fancy stuff 3 | */ 4 | 5 | (function() { 6 | var faq = { 7 | '#frequently-asked-questions-common-type-errors': 'im-having-a-type-error.html', 8 | '#frequently-asked-questions-how-do-i-do-props-spreading-div-thisprops': 'props-spread.html', 9 | default: 'im-having-a-type-error.html' 10 | }; 11 | var examples = { 12 | '#examples-simple': 'simple.html', 13 | '#examples-counter': 'counter.html', 14 | '#examples-reasonreact-using-reactjs': 'retained-props.html', 15 | '#examples-reasonreact-using-reactjs': 'reason-using-js.html', 16 | '#examples-reactjs-using-reasonreact': 'js-using-reason.html', 17 | default: 'simple.html' 18 | }; 19 | var gettingStarted = { 20 | '#getting-started': 'installation.html', 21 | '#getting-started-bsb': 'installation.html#bsb', 22 | default: 'installation.html' 23 | }; 24 | // redirects[page][hash] => new page; 25 | // yarn start only supports faq.html format, but gh pages upens up the other two. 26 | var redirects = { 27 | 'faq.html': faq, 28 | 'faq': faq, 29 | 'faq/': faq, 30 | 'examples.html': examples, 31 | 'examples': examples, 32 | 'examples/': examples, 33 | 'gettingStarted.html': gettingStarted, 34 | 'gettingStarted': gettingStarted, 35 | 'gettingStarted/': gettingStarted, 36 | }; 37 | var hash = window.location.hash; 38 | var base = '/reason-react/docs/en/'; 39 | var path = window.location.pathname.split('/'); 40 | var page = path[path.length - 1]; 41 | if (redirects[page]) { 42 | var link = document.getElementById('redirectLink'); 43 | var location = base + 44 | (redirects[page][hash] || redirects[page].default); 45 | link.textContent = 'https://reasonml.github.io' + location; 46 | link.href = location; 47 | } 48 | })(); 49 | -------------------------------------------------------------------------------- /website/static/js/redirectIndex.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is not run through any build step! Don't add any fancy stuff 3 | */ 4 | 5 | (function() { 6 | // redirects[page][hash] => new page; 7 | var redirects = { 8 | 'reason-react': 'reason-react.html', 9 | 'intro-example': 'intro-example.html', 10 | 'jsx': 'jsx.html', 11 | 'component-creation': 'creation-props-self.html', 12 | 'react-element': 'render.html', 13 | 'interop-with-existing-javascript-components': 'interop.html', 14 | 'events': 'event.html', 15 | 'styles': 'style.html', 16 | 'cloneelement': 'clone-element.html', 17 | 'working-with-children': 'children.html', 18 | 'working-with-dom': 'dom.html', 19 | 'convert-over-reactjs-idioms': 'convert.html', 20 | 'miscellaneous': 'context-mixins.html', 21 | 'common-type-errors': 'im-having-a-type-error.html', 22 | }; 23 | // they all start with reason-react 24 | var hash = window.location.hash 25 | if (hash.indexOf('reason-react') !== 1) { 26 | return; 27 | } 28 | if (hash === '#reason-react') { 29 | hash = 'reason-react'; 30 | } else { 31 | hash = hash.split('reason-react-')[1]; 32 | } 33 | var path = window.location.pathname.split('/'); 34 | var page = path[path.length - 1]; 35 | var base = '/reason-react/docs/en/'; 36 | Object.keys(redirects).forEach(function(redirect) { 37 | if (redirect.indexOf(hash) === 0) { 38 | var newHash = redirect.split(hash + '-')[1] || ''; 39 | newHash = newHash ? '#' + newHash : newHash; 40 | var link = document.getElementById('redirectLink'); 41 | var banner = document.getElementById('redirectBanner'); 42 | var location = base + redirects[redirect] + newHash; 43 | 44 | link.textContent = 'https://reasonml.github.io' + location; 45 | link.href = location; 46 | banner.style.display = 'block'; 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /website/blog/2020-05-05-080-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReasonReact 0.8.0 🎉 BuckleScript Upgrade & More! 3 | --- 4 | 5 | We're excited to share Version 0.8.0 of ReasonReact with the world today! ReasonReact adds a huge number of quality of life improvements and new api changes. This is our first big release since introducing hooks in 0.7.0. 6 | 7 | It's a breaking change primarily because it enforces a minimum BuckleScript version of 7.1.1. This is to ensure that we get consistent record and object runtime representation and to unlock more changes in the future. Going forward you can expect that ReasonReact will track BuckleScript more closely. The PPX lives in and ships with BuckleScript, so in order to make sweeping changes we have to work in both codebases. 8 | 9 | There are a number of additional breaking changes: 10 | 11 | * crossOrigin casing was incorrect 12 | * maxDuration removed from suspense api 13 | * Ref type has been changed to be modelled as a record instead of abstract type with get and set 14 | * The min attribute on dom nodes in now a string to match max 15 | 16 | As well as new additions: 17 | 18 | * Support for concurrent mode with createRoot, Suspense, SuspenseList, useTransition 19 | * React.float and int 20 | * Better Children mapping 21 | 22 | This is not an exhaustive list - I encourage you to check out the full set in https://github.com/reasonml/reason-react/blob/main/HISTORY.md. 23 | 24 | This release has been a long time coming and is the huge effort of the ReasonReact community. A heartfelt thanks to everyone in the HISTORY and to everyone who has created or interacted with issues! The first step to upgrading here is to make sure you're on BuckleScript ^7.1.1. From there you can visit [upgrade-reason-react](https://github.com/rickyvetter/upgrade-reason-react) for a script that will handle the ref upgrade, crossOrigin capitalization, and the type of min when used in JSX. Happy hacking! 25 | -------------------------------------------------------------------------------- /docs/intro-example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Intro Example 3 | --- 4 | 5 | Here is a small overview of the ReasonReact API before we start. No worries if some of these are unfamiliar; the docs cover all of them. 6 | 7 | ## The component "Greeting" 8 | 9 | ```reason 10 | /* file: Greeting.re */ 11 | 12 | [@react.component] 13 | let make = (~name) => ; 14 | ``` 15 | 16 | ## Using Greeting in your App 17 | 18 | If you're writing your entire React app in Reason, you'll probably have a `ReactDOM.render` in an index file. This is what that looks like in Reason: 19 | 20 | ```reason 21 | /* file: Index.re */ 22 | switch (ReactDOM.querySelector("#root")) { 23 | | Some(root) => ReactDOM.render(, root) 24 | | None => () 25 | } 26 | ``` 27 | 28 | This is how you used to write this in plain JavaScript (index.js): 29 | 30 | ```js 31 | /* file: index.js */ 32 | let root = document.getElementById("root"); 33 | if (root != null) { 34 | ReactDOM.render(, root); 35 | }; 36 | ``` 37 | 38 | ### Using Greeting in an existing JavaScript/Typescript application 39 | 40 | It's easy to import a ReasonReact component into your existing app. After being transpiled to JS, all Reason components will have `.js` as extension by default and export a function component called `make`. You can change it with [module_systems][commonjs-or-es6-modules] field inside a [`melange.emit` stanza](https://dune.readthedocs.io/en/stable/melange.html#melange-emit). 41 | 42 | ```js 43 | /* file: App.js */ 44 | 45 | import { make as Greeting } from "./Greeting.js"; 46 | 47 | export default function App() { 48 | return ( 49 |
50 | 51 |
52 | ); 53 | } 54 | ``` 55 | 56 | Make sure you check out [Examples](simple.md) for more! 57 | 58 | [commonjs-or-es6-modules]: https://melange.re/v4.0.0/build-system/#commonjs-or-es6-modules 59 | -------------------------------------------------------------------------------- /ppx/test/upper.t/run.t: -------------------------------------------------------------------------------- 1 | $ ../ppx.sh --output re input.re 2 | let upper = React.jsx(Upper.make, Upper.makeProps()); 3 | let upper_prop = React.jsx(Upper.make, Upper.makeProps(~count, ())); 4 | let upper_children_single = foo => 5 | React.jsx(Upper.make, Upper.makeProps(~children=foo, ())); 6 | let upper_children_multiple = (foo, bar) => 7 | React.jsxs( 8 | Upper.make, 9 | Upper.makeProps(~children=React.array([|foo, bar|]), ()), 10 | ); 11 | let upper_children = 12 | React.jsx( 13 | Page.make, 14 | Page.makeProps( 15 | ~children= 16 | ReactDOM.jsx( 17 | "h1", 18 | ([@merlin.hide] ReactDOM.domProps)( 19 | ~children=React.string("Yep"), 20 | (), 21 | ), 22 | ), 23 | ~moreProps="hgalo", 24 | (), 25 | ), 26 | ); 27 | let upper_nested_module = 28 | React.jsx(Foo.Bar.make, Foo.Bar.makeProps(~a=1, ~b="1", ())); 29 | let upper_child_expr = 30 | React.jsx(Div.make, Div.makeProps(~children=React.int(1), ())); 31 | let upper_child_ident = 32 | React.jsx(Div.make, Div.makeProps(~children=lola, ())); 33 | let upper_all_kinds_of_props = 34 | React.jsx( 35 | MyComponent.make, 36 | MyComponent.makeProps( 37 | ~children= 38 | ReactDOM.jsx( 39 | "div", 40 | ([@merlin.hide] ReactDOM.domProps)(~children="hello", ()), 41 | ), 42 | ~booleanAttribute=true, 43 | ~stringAttribute="string", 44 | ~intAttribute=1, 45 | ~forcedOptional=?Some("hello"), 46 | ~onClick=send(handleClick), 47 | (), 48 | ), 49 | ); 50 | let upper_ref_with_children = 51 | React.jsx( 52 | FancyButton.make, 53 | FancyButton.makeProps( 54 | ~children=ReactDOM.jsx("div", ([@merlin.hide] ReactDOM.domProps)()), 55 | ~ref=buttonRef, 56 | (), 57 | ), 58 | ); 59 | -------------------------------------------------------------------------------- /website/static/css/custom.css: -------------------------------------------------------------------------------- 1 | .spinner { 2 | margin: auto; 3 | position: absolute; 4 | width: 800px; 5 | left: 0px; 6 | bottom: -400px; 7 | animation:spin 750s linear infinite; 8 | transition: opacity 2s; 9 | } 10 | 11 | @media (max-width: 1024px) { 12 | .spinner { 13 | opacity: 0; 14 | } 15 | } 16 | 17 | @keyframes spin { 100% {transform:rotate(360deg); } } 18 | 19 | /* community examples page */ 20 | .community-examples-grid { 21 | display: grid; 22 | grid-gap: 20px; 23 | grid-template-columns: repeat(auto-fill, 250px); 24 | justify-content: space-evenly; 25 | } 26 | 27 | .community-examples-grid > a > div { 28 | text-align: center; 29 | } 30 | 31 | .productShowcaseSection .logos img { 32 | max-height: 70px; 33 | width: 70px; 34 | } 35 | 36 | 37 | /* overrides. Most of these should be upstreamed into docusaurus */ 38 | 39 | .quickStartAndExamples .blockImage { 40 | max-width: 226px; 41 | box-shadow: 0px 0 20px lightgrey; 42 | margin-bottom: 0; 43 | } 44 | 45 | .quickStartAndExamples .blockContent h2 { 46 | margin: 8px 0 0 0; 47 | } 48 | 49 | .quickStartAndExamples .examples { 50 | margin-left: 0px; 51 | } 52 | 53 | .quickStartAndExamples .blockimage img { 54 | border-radius: 4px; 55 | } 56 | 57 | .homeBanner { 58 | display: flex; 59 | justify-content: center; 60 | text-align: center; 61 | color: #979aad; 62 | background-color: rgba(10,13,47, 1); 63 | font-size: 2.2rem; 64 | text-align: center; 65 | padding: 1.5rem; 66 | } 67 | 68 | .heroButton { 69 | padding: 1rem; 70 | border: 1px; 71 | text-decoration: none; 72 | background-color: #e6484f; 73 | border-radius: 0.25rem; 74 | color: white; 75 | } 76 | 77 | .heroButton:hover { 78 | color: white; 79 | background-color: #C3373d; 80 | text-align: center; 81 | } 82 | 83 | .heroLink { 84 | color: white; 85 | text-decoration: underline; 86 | display: inline-block; 87 | text-align: center 88 | } 89 | 90 | /* end override */ 91 | -------------------------------------------------------------------------------- /test/ReactDOM__test.re: -------------------------------------------------------------------------------- 1 | open Jest; 2 | open Jest.Expect; 3 | 4 | module Stream = { 5 | type writable = Js.t({.}); 6 | 7 | [@mel.send] external setEncoding: (writable, string) => unit = "setEncoding"; 8 | 9 | [@mel.send] external on: (writable, string, string => unit) => unit = "on"; 10 | 11 | [@mel.module "stream"] [@mel.new] 12 | external make: unit => writable = "PassThrough"; 13 | }; 14 | 15 | describe("ReactDOM", () => { 16 | describe("ReactDOM.Server", () => { 17 | test("renderToString", () => { 18 | let string = 19 | ReactDOMServer.renderToString( 20 |
"Hello world!"->React.string
, 21 | ); 22 | expect(string)->toBe("
Hello world!
"); 23 | }); 24 | test("renderToStaticMarkup", () => { 25 | let string = 26 | ReactDOMServer.renderToStaticMarkup( 27 |
"Hello world!"->React.string
, 28 | ); 29 | expect(string)->toBe("
Hello world!
"); 30 | }); 31 | 32 | testAsync("renderToPipeableStream", finish => { 33 | let buffer = ref(""); 34 | let hasErrored = ref(false); 35 | let stream = Stream.make(); 36 | Stream.setEncoding(stream, "utf8"); 37 | Stream.on( 38 | stream, 39 | "data", 40 | data => { 41 | buffer := buffer.contents ++ data; 42 | expect(buffer.contents)->toBe("
Hello world!
"); 43 | expect(hasErrored.contents)->toBe(false); 44 | 45 | finish(); 46 | }, 47 | ); 48 | Stream.on( 49 | stream, 50 | "error", 51 | error => { 52 | buffer := buffer.contents ++ error; 53 | hasErrored := true; 54 | }, 55 | ); 56 | let {pipe, abort: _}: ReactDOMServerNode.pipeableStream(_) = 57 | ReactDOMServerNode.renderToPipeableStream( 58 |
"Hello world!"->React.string
, 59 | ); 60 | pipe(stream); 61 | }); 62 | }) 63 | }); 64 | -------------------------------------------------------------------------------- /website/pages/en/help-with-translations.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | 3 | const CompLibrary = require("../../core/CompLibrary.js"); 4 | const Container = CompLibrary.Container; 5 | const GridBlock = CompLibrary.GridBlock; 6 | 7 | const translate = require("../../server/translate.js").translate; 8 | 9 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 10 | 11 | class Help extends React.Component { 12 | render() { 13 | const supportLinks = [ 14 | { 15 | content: ( 16 | 17 | Learn more using the [documentation on this site.](/test-site/docs/en/doc1.html) 18 | 19 | ), 20 | title: Browse Docs 21 | }, 22 | { 23 | content: ( 24 | 25 | Ask questions about the documentation and project 26 | 27 | ), 28 | title: Join the community 29 | }, 30 | { 31 | content: Find out what's new with this project, 32 | title: Stay up to date 33 | } 34 | ]; 35 | 36 | return ( 37 |
38 | 39 |
40 |
41 |

42 | Need help? 43 |

44 |
45 |

46 | 47 | This project is maintained by a dedicated group of people. 48 | 49 |

50 | 51 |
52 |
53 |
54 | ); 55 | } 56 | } 57 | 58 | Help.defaultProps = { 59 | language: "en" 60 | }; 61 | 62 | module.exports = Help; 63 | -------------------------------------------------------------------------------- /ppx/test/lower.t/input.re: -------------------------------------------------------------------------------- 1 | let lower =
; 2 | let lower_empty_attr =
; 3 | let lower_inline_styles = 4 |
; 5 | let lower_inner_html =
; 6 | let lower_opt_attr =
; 7 | 8 | let lowerWithChildAndProps = foo => 9 | foo ; 10 | 11 | let lower_child_static =
; 12 | let lower_child_ident =
lolaspa
; 13 | let lower_child_single =
; 14 | let lower_children_multiple = (foo, bar) => foo bar ; 15 | let lower_child_with_upper_as_children =
; 16 | 17 | let lower_children_nested = 18 |
19 |
20 |

{"jsoo-react" |> s}

21 | 39 |
40 |
; 41 | 42 | let lower_ref_with_children = 43 | ; 44 | 45 | let lower_with_many_props = 46 |
47 | 48 | test picture/img.png 49 | 50 | 51 | 52 |
; 53 | 54 | let some_random_html_element = ; 55 | -------------------------------------------------------------------------------- /docs/error-boundaries.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Error boundaries 3 | --- 4 | 5 | > **Important note on the API described in this guide:** 6 | > As soon as React provides a mechanism for error-catching using functional component, 7 | > `ReasonReactErrorBoundary` is likely to be deprecated and/or move to user space. 8 | 9 | ReactJS provides [a way to catch errors](https://reactjs.org/docs/error-boundaries.html) thrown in descendent component render functions in its class API using `componentDidCatch`, it enables you to display a fallback: 10 | 11 | ```javascript 12 | class MyErrorBoundary extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | hasError: false, 17 | }, 18 | } 19 | componentDidCatch(error, info) { 20 | this.setState({hasError: true}) 21 | } 22 | // ... 23 | render() { 24 | if(this.state.hasError) { 25 | return this.props.fallback 26 | } else { 27 | return this.props.children 28 | } 29 | } 30 | }; 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | We're providing a lightweight component that does that just for you: `ReasonReactErrorBoundary`. 38 | 39 | ```reason 40 | "An error occured"->React.string} 42 | > 43 | 44 | 45 | ``` 46 | 47 | In case you need to log your errors to your analytics service, we pass a record containing `error` and `info` to your fallback function: 48 | 49 | ```reason 50 | module ErrorHandler = { 51 | [@react.component] 52 | let make = (~error, ~info) => { 53 | React.useEffect2(() => { 54 | reportError(error, info) // your analytics function 55 | None 56 | }, (error, info)); 57 | "An error occured"->React.string 58 | } 59 | }; 60 | 61 | } 63 | > 64 | 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /src/ReactDOMServerNode.re: -------------------------------------------------------------------------------- 1 | [@deriving (jsProperties, getSet)] 2 | type options = { 3 | [@mel.optional] 4 | bootstrapScriptContent: option(string), 5 | [@mel.optional] 6 | bootstrapScripts: option(array(string)), 7 | [@mel.optional] 8 | bootstrapModules: option(array(string)), 9 | [@mel.optional] 10 | identifierPrefix: option(string), 11 | [@mel.optional] 12 | namespaceURI: option(string), 13 | [@mel.optional] 14 | nonce: option(string), 15 | [@mel.optional] 16 | onAllReady: option(unit => unit), 17 | [@mel.optional] 18 | onError: option(Js.Exn.t => unit), 19 | [@mel.optional] 20 | onShellReady: option(unit => unit), 21 | [@mel.optional] 22 | onShellError: option(Js.Exn.t => unit), 23 | [@mel.optional] 24 | progressiveChunkSize: option(int), 25 | }; 26 | 27 | type pipeableStream('a) = { 28 | /* Using empty object instead of Node.stream since Melange don't provide a binding to node's Stream (https://nodejs.org/api/stream.html) */ 29 | pipe: Js.t({..} as 'a) => unit, 30 | abort: unit => unit, 31 | }; 32 | 33 | [@mel.module "react-dom/server"] 34 | external renderToPipeableStream: 35 | (React.element, options) => pipeableStream('a) = 36 | "renderToPipeableStream"; 37 | 38 | let renderToPipeableStream = 39 | ( 40 | ~bootstrapScriptContent=?, 41 | ~bootstrapScripts=?, 42 | ~bootstrapModules=?, 43 | ~identifierPrefix=?, 44 | ~namespaceURI=?, 45 | ~nonce=?, 46 | ~onAllReady=?, 47 | ~onError=?, 48 | ~onShellReady=?, 49 | ~onShellError=?, 50 | ~progressiveChunkSize=?, 51 | element, 52 | ) => 53 | renderToPipeableStream( 54 | element, 55 | options( 56 | ~bootstrapScriptContent?, 57 | ~bootstrapScripts?, 58 | ~bootstrapModules?, 59 | ~identifierPrefix?, 60 | ~namespaceURI?, 61 | ~nonce?, 62 | ~onAllReady?, 63 | ~onError?, 64 | ~onShellReady?, 65 | ~onShellError?, 66 | ~progressiveChunkSize?, 67 | (), 68 | ), 69 | ); 70 | -------------------------------------------------------------------------------- /src/ReasonReactErrorBoundary.re: -------------------------------------------------------------------------------- 1 | /** 2 | * Important note on this module: 3 | * As soon as React provides a mechanism for error-catching using functional component, 4 | * this is likely to be deprecated and/or move to user space. 5 | */ 6 | 7 | type info = {componentStack: string}; 8 | 9 | type params('error) = { 10 | error: 'error, 11 | info, 12 | }; 13 | 14 | [@mel.scope "Object"] external objCreate: 'a => Js.t({..}) = "create"; 15 | 16 | type reactComponentClass; 17 | 18 | [@mel.module "react"] external component: reactComponentClass = "Component"; 19 | [@mel.send] external componentCall: (reactComponentClass, _) => unit = "call"; 20 | 21 | type componentPrototype; 22 | [@mel.get] 23 | external componentPrototype: reactComponentClass => componentPrototype = 24 | "prototype"; 25 | 26 | let errorBoundary = 27 | [@mel.this] 28 | ( 29 | (self, _props) => { 30 | componentCall(component, self); 31 | self##state #= {"error": Js.undefined}; 32 | } 33 | ); 34 | 35 | [@mel.set] external setPrototype: (_, _) => unit = "prototype"; 36 | setPrototype(errorBoundary, objCreate(componentPrototype(component))); 37 | 38 | [@mel.set] [@mel.scope "prototype"] 39 | external setComponentDidCatch: 40 | (_, [@mel.this] (('self, 'error, 'info) => unit)) => unit = 41 | "componentDidCatch"; 42 | 43 | setComponentDidCatch(errorBoundary, [@mel.this] (self, error, info) => { 44 | self##setState({ 45 | "error": { 46 | error, 47 | info, 48 | }, 49 | }) 50 | }); 51 | 52 | [@mel.set] [@mel.scope "prototype"] 53 | external setRender: (_, [@mel.this] ('self => unit)) => unit = "render"; 54 | setRender(errorBoundary, [@mel.this] self => { 55 | switch (Js.Undefined.testAny(self##state##error)) { 56 | | false => self##props##fallback(self##state##error) 57 | | true => self##props##children 58 | } 59 | }); 60 | 61 | [@react.component] 62 | external make: 63 | (~children: React.element, ~fallback: params('error) => React.element) => 64 | React.element = 65 | "errorBoundary"; 66 | 67 | let make = make; 68 | -------------------------------------------------------------------------------- /src/ReasonReactRouter.rei: -------------------------------------------------------------------------------- 1 | /** update the url with the string path. Example: `push("/book/1")`, `push("/books#title")` */ 2 | let push: string => unit; 3 | /** update the url with the string path. modifies the current history entry instead of creating a new one. Example: `replace("/book/1")`, `replace("/books#title")` */ 4 | let replace: string => unit; 5 | type watcherID; 6 | type url = { 7 | /* path takes window.location.path, like "/book/title/edit" and turns it into `["book", "title", "edit"]` */ 8 | path: list(string), 9 | /* the url's hash, if any. The # symbol is stripped out for you */ 10 | hash: string, 11 | /* the url's query params, if any. The ? symbol is stripped out for you */ 12 | search: string, 13 | }; 14 | /** start watching for URL changes. Returns a subscription token. Upon url change, calls the callback and passes it the url record */ 15 | let watchUrl: (url => unit) => watcherID; 16 | /** stop watching for URL changes */ 17 | let unwatchUrl: watcherID => unit; 18 | /** this is marked as "dangerous" because you technically shouldn't be accessing the URL outside of watchUrl's callback; 19 | you'd read a potentially stale url, instead of the fresh one inside watchUrl. 20 | 21 | But this helper is sometimes needed, if you'd like to initialize a page whose display/state depends on the URL, 22 | instead of reading from it in watchUrl's callback, which you'd probably have put inside didMount (aka too late, 23 | the page's already rendered). 24 | 25 | So, the correct (and idiomatic) usage of this helper is to only use it in a component that's also subscribed to 26 | watchUrl. Please see https://github.com/reasonml-community/reason-react-example/blob/master/src/todomvc/TodoItem.re 27 | for an example. 28 | */ 29 | let dangerouslyGetInitialUrl: (~serverUrlString: string=?, unit) => url; 30 | /** hook for watching url changes. 31 | * serverUrl is used for ssr. it allows you to specify the url without relying on browser apis existing/working as expected 32 | */ 33 | let useUrl: (~serverUrl: url=?, unit) => url; 34 | -------------------------------------------------------------------------------- /website/blog/2018-01-09-subscriptions-send-router.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Router and Subscriptions! 3 | --- 4 | 5 | Happy new year! We've got a new, non-breaking release for you =). As always, **[here](https://github.com/chenglou/upgrade-reason-react)'s the migration script** and [here](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#031)'s the list of changes. 6 | 7 | V0.3.1 brings two important features, both super lightweight and performant, yet strike, we hope, a sweet spot in terms of usability: 8 | 9 | - [**Router**](https://reasonml.github.io/reason-react/docs/en/router.html)! It's tiny and has almost no learning overhead. 10 | - [Subscriptions helper](https://reasonml.github.io/reason-react/docs/en/subscriptions-helper.html). Helps you cut down on some boilerplate. 11 | 12 | These two features follow our spirit of keeping the learning and performance overhead low, while providing facilities that codifies your existing knowledge of ReactJS. In particular, note how: 13 | 14 | - The subscriptions helper prevents you from forgetting to free your event listeners and timers. 15 | - The router just uses pattern-matching to kill a majority of otherwise needed API surface. 16 | 17 | There are no FAQs for these two features. Check out the linked docs; you can use your existing understanding of Reason to answer your questions: is this performant? Will this work in my existing setup? How does nesting work? Etc. Special thanks to Ryan Florence and Michael Jackson for some small conversations. 18 | 19 | Additionally, we've **deprecated** `self.reduce` in favor of `self.send` (the migration script takes care of that): 20 | 21 | - Before: `onClick={self.reduce(_event => Click)}` 22 | - After: `onClick={_event => self.send(Click)}` 23 | 24 | - Before: `didMount: self => {self.reduce(() => Click, ()); NoUpdate}` 25 | - After: `didMount: self => {self.send(Click); NoUpdate}` 26 | 27 | This change should drastically reduce the confusion of an immediately called `self.reduce` (the form with two arguments). It also rolls on the tongue better: you "send" an action to the reducer. 28 | 29 | Happy coding! 30 | -------------------------------------------------------------------------------- /docs/dom.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReactDOM 3 | --- 4 | 5 | ReasonReact's ReactDOM module is called `ReactDOM`. The module exposes helpers that work with familiar ReactJS idioms: 6 | 7 | - `ReactDOM.querySelector` : `string => option(Dom.element)` 8 | - `ReactDOM.Client.createRoot` : `Dom.element => Client.root` 9 | - `ReactDOM.Client.render` : `(Client.root, React.element) => unit` 10 | - `ReactDOM.Client.hydrateRoot` : `(Dom.element, React.element) => Client.root` 11 | - `ReactDOM.createPortal` : `(React.element, Dom.element) => React.element` 12 | - `ReactDOM.unmountComponentAtNode` : `Dom.element => unit` 13 | 14 | More info about `ReactDOM` module can be found in the interface file: [ReactDOM.rei](https://github.com/reasonml/reason-react/blob/main/src/ReactDOM.rei) or in the [official react-dom documentation](https://react.dev/reference/react-dom). 15 | 16 | ### Usage 17 | 18 | In React 18, the `ReactDOM.render` function is replaced by `ReactDOM.Client.render`. 19 | 20 | ```reason 21 | let element = ReactDOM.querySelector("#root"); 22 | switch (element) { 23 | | None => Js.log("#root element not found"); 24 | | Some(element) => { 25 | let root = ReactDOM.Client.createRoot(element); 26 | ReactDOM.Client.render(, root); 27 | } 28 | } 29 | ``` 30 | 31 | ### Hydration 32 | 33 | Hydration is the process of converting a static HTML page into a dynamic React app. 34 | 35 | ```reason 36 | let element = ReactDOM.querySelector("#root"); 37 | switch (element) { 38 | | None => Js.log("#root element not found"); 39 | | Some(element) => { 40 | /* root is a ReactDOM.Client.root and used to render on top of the existing HTML 41 | with ReactDOM.Client.render or unmount, via ReactDOM.Client.unmount. */ 42 | let _root = ReactDOM.Client.hydrateRoot(, element); 43 | (); 44 | } 45 | } 46 | ``` 47 | 48 | ## ReactDOMServer 49 | 50 | ReasonReact's equivalent of `ReactDOMServer` from `react-dom/server` exposes 51 | 52 | - `renderToString` : `React.element => string` 53 | - `renderToStaticMarkup` : `React.element => string` 54 | 55 | More info about `ReactDOMServer` can be found in the [official React documentation](https://react.dev/reference/react-dom/server). 56 | -------------------------------------------------------------------------------- /docs/importing-js-into-reason.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Importing JS into Reason 3 | --- 4 | 5 | ### Importing a Javascript file into Reason 6 | 7 | ```js 8 | /* MyJavascriptFile.js */ 9 | 10 | export default function Greeting({ name }) { 11 | return Hey {name} 12 | } 13 | ``` 14 | 15 | ```reason 16 | /* App.re */ 17 | 18 | module Greeting = { 19 | [@mel.module "./MyJavascriptFile.js"] [@react.component] 20 | external make: (~name: string) => React.element = "default"; 21 | }; 22 | 23 | let make = () =>
; 24 | ``` 25 | 26 | ### Default props and JavaScript components 27 | 28 | Sometimes you need your JavaScript component to have a default prop. How you 29 | handle this will depend on the details of the situation. 30 | 31 | #### Scenario 1: The JavaScript component prop is already coded to have a default 32 | 33 | ```js 34 | function Greeting({ name }) { 35 | return Hey {name} 36 | }; 37 | 38 | Greeting.defaultProps = { 39 | name: "John" 40 | }; 41 | ``` 42 | 43 | Props (or any function argument) with a default is just an optional prop as far 44 | as its signature is concerned. To bind to it, we just make the prop optional. 45 | 46 | ```reason 47 | module Greeting = { 48 | [@mel.module "./Greeting.js"] [@react.component] 49 | external make: (~name: string=?) => React.element = "default"; 50 | }; 51 | ``` 52 | 53 | (Note that function signatures are only concerned with types, not values. Code 54 | like `external make: (~name="Peter") => React.element` is a syntax error. 55 | `"Peter"` is a value, whereas `string` is a type.) 56 | 57 | #### Scenario 2: The JavaScript component prop doesn't have a default, but we want to add one 58 | 59 | In JavaScript, the logic for default arguments is handled at runtime inside the 60 | function body. If our external does not already have a default, we'll need to 61 | wrap it with our own component to add the logic. 62 | 63 | ```reason 64 | module GreetingJs = { 65 | [@mel.module "./Greeting.js"] [@react.component] 66 | external make: (~name: string) => React.element = "default"; 67 | }; 68 | 69 | module Greeting = { 70 | [@react.component] 71 | let make = (~name="Peter") => ; 72 | }; 73 | ``` 74 | -------------------------------------------------------------------------------- /ppx/test/component-without-make.t/run.t: -------------------------------------------------------------------------------- 1 | Since we generate invalid syntax for the argument of the make fn `(Props : <>)` 2 | We need to output ML syntax here, otherwise refmt could not parse it. 3 | $ ../ppx.sh --output ml input.re 4 | module X_as_main_function = 5 | struct 6 | external xProps : ?key:string -> unit -> < > Js.t = ""[@@mel.obj ] 7 | let x () = ReactDOM.jsx "div" (((ReactDOM.domProps)[@merlin.hide ]) ()) 8 | let x = 9 | let Output$X_as_main_function$x (Props : < > Js.t) = x () in 10 | Output$X_as_main_function$x 11 | end 12 | module Create_element_as_main_function = 13 | struct 14 | external createElementProps : 15 | lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" 16 | [@@mel.obj ] 17 | let createElement = 18 | ((fun ~lola -> 19 | ReactDOM.jsx "div" 20 | (((ReactDOM.domProps)[@merlin.hide ]) 21 | ~children:(React.string lola) ())) 22 | [@warning "-16"]) 23 | let createElement = 24 | let Output$Create_element_as_main_function$createElement 25 | (Props : < lola: 'lola > Js.t) = 26 | createElement ~lola:(Props ## lola) in 27 | Output$Create_element_as_main_function$createElement 28 | end 29 | module Invalid_case = 30 | struct 31 | external makeProps : 32 | lola:'lola -> ?key:string -> unit -> < lola: 'lola > Js.t = "" 33 | [@@mel.obj ] 34 | let make = 35 | ((fun ~lola -> 36 | React.jsx Create_element_as_main_function.make 37 | (Create_element_as_main_function.makeProps ~lola ())) 38 | [@warning "-16"]) 39 | let make = 40 | let Output$Invalid_case (Props : < lola: 'lola > Js.t) = 41 | make ~lola:(Props ## lola) in 42 | Output$Invalid_case 43 | end 44 | module Valid_case = 45 | struct 46 | external makeProps : ?key:string -> unit -> < > Js.t = ""[@@mel.obj ] 47 | let make () = 48 | React.jsx Component_with_x_as_main_function.x 49 | (Component_with_x_as_main_function.xProps ()) 50 | let make = 51 | let Output$Valid_case (Props : < > Js.t) = make () in 52 | Output$Valid_case 53 | end 54 | -------------------------------------------------------------------------------- /website/static/img/logos/astrocoders.svg: -------------------------------------------------------------------------------- 1 | 2 | Vector 3 | Created using Figma 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ppx/test/locations-check.t: -------------------------------------------------------------------------------- 1 | Test the preprocessed reason-react components have well-formed locations. 2 | Uses https://github.com/ocaml-ppx/ppxlib/blob/44583fc14c3cc39ee6269ffd69f52146283f72c0/src/location_check.mli 3 | 4 | With no annotations (ppx does nothing) 5 | 6 | $ cat >input.ml < let make ~foo ~bar = 8 | > (div 9 | > ~children:[ React.string foo; bar |> string_of_int |> React.string ] 10 | > ()) 11 | > EOF 12 | 13 | $ reason-react-ppx -check -locations-check input.ml 14 | let make ~foo ~bar = 15 | div ~children:[React.string foo; (bar |> string_of_int) |> React.string] () 16 | 17 | With JSX annotation 18 | 19 | $ cat >input.ml < let make ~foo ~bar = 21 | > (div 22 | > ~children:[ React.string foo; bar |> string_of_int |> React.string ] 23 | > () [@JSX]) 24 | > EOF 25 | 26 | $ reason-react-ppx -check -locations-check input.ml 27 | File "input.ml", line 2, characters 3-6: 28 | 2 | (div 29 | ^^^ 30 | Error: invalid output from ppx, expression overlaps with expression at location: 31 | File "input.ml", line 2, characters 2-96: 32 | [1] 33 | 34 | With @react.component annotation 35 | 36 | $ cat >input.ml < let[@react.component] make ~foo ~bar = 38 | > (div 39 | > ~children:[ React.string foo; bar |> string_of_int |> React.string ] 40 | > () [@JSX]) 41 | > EOF 42 | 43 | $ reason-react-ppx -check -locations-check input.ml 44 | File "input.ml", line 1, characters 33-36: 45 | 1 | let[@react.component] make ~foo ~bar = 46 | ^^^ 47 | Error: invalid output from ppx, core type overlaps with core type at location: 48 | File "input.ml", line 1, characters 33-36: 49 | [1] 50 | 51 | Everything 52 | 53 | $ cat >input.ml < let[@react.component] make ~foo ~bar = 55 | > (div 56 | > ~children:[ React.string foo; bar |> string_of_int |> React.string ] 57 | > () [@JSX]) 58 | > EOF 59 | 60 | $ reason-react-ppx -check -locations-check input.ml 61 | File "input.ml", line 1, characters 33-36: 62 | 1 | let[@react.component] make ~foo ~bar = 63 | ^^^ 64 | Error: invalid output from ppx, core type overlaps with core type at location: 65 | File "input.ml", line 1, characters 33-36: 66 | [1] 67 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DUNE = opam exec -- dune 2 | 3 | .PHONY: help 4 | help: ## Print this help message 5 | @echo "List of available make commands"; 6 | @echo ""; 7 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'; 8 | @echo ""; 9 | 10 | .PHONY: build 11 | build: ## Build the project, including non installable libraries and executables 12 | @$(DUNE) build @all 13 | 14 | .PHONY: build-prod 15 | build-prod: ## Build for production (--profile=prod) 16 | @$(DUNE) build --profile=prod @@default 17 | 18 | .PHONY: dev 19 | dev: ## Build in watch mode 20 | @$(DUNE) build -w @@default 21 | 22 | .PHONY: clean 23 | clean: ## Clean artifacts 24 | @$(DUNE) clean 25 | 26 | .PHONY: jest 27 | jest: ## Run the jest unit tests 28 | @npx jest 29 | 30 | .PHONY: jest-watch 31 | jest-watch: ## Run the jest unit tests in watch mode 32 | @npx jest --watch 33 | 34 | .PHONY: jest-devtools 35 | jest-devtools: ## Run the jest unit tests in watch mode 36 | @echo "open Chrome and go to chrome://inspect" 37 | @node --inspect-brk node_modules/.bin/jest --runInBand --detectOpenHandles 38 | 39 | .PHONY: test 40 | test: ## Run the runtests from dune (snapshot) 41 | @$(DUNE) build @runtest 42 | 43 | .PHONY: test-watch 44 | test-watch: ## Run the unit tests in watch mode 45 | @$(DUNE) build @runtest -w 46 | 47 | .PHONY: test-promote 48 | test-promote: ## Updates snapshots and promotes it to correct 49 | @$(DUNE) build @runtest --auto-promote 50 | 51 | .PHONY: format 52 | format: ## Format the codebase with ocamlformat 53 | @$(DUNE) build @fmt --auto-promote 54 | 55 | .PHONY: format-check 56 | format-check: ## Checks if format is correct 57 | @$(DUNE) build @fmt 58 | 59 | .PHONY: install 60 | install: ## Update the package dependencies when new deps are added to dune-project 61 | @opam install . --deps-only --with-test --with-dev-setup 62 | @npm install --force 63 | 64 | .PHONY: init 65 | create-switch: ## Create a local opam switch 66 | @opam switch create . 5.2.0 --no-install 67 | 68 | .PHONY: init 69 | init: create-switch install ## Create a local opam switch, install deps 70 | 71 | .PHONY: demo-watch 72 | demo-watch: ## Build the demo in watch mode 73 | @$(DUNE) build @melange-app --watch 74 | 75 | .PHONY: demo-serve 76 | demo-serve: ## Build the demo and serve it 77 | npx http-server -p 8080 _build/default/demo/ 78 | -------------------------------------------------------------------------------- /docs/context.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Context 3 | --- 4 | 5 | In order to use React's context, you need to create two things: 6 | 7 | 1. The context itself 8 | 2. A context provider component 9 | 10 | ```reason 11 | /** as a separate file: ContextProvider.re */ 12 | 13 | // 1. The context itself 14 | let themeContext = React.createContext("light"); 15 | 16 | // 2. The provider 17 | include React.Context; // Adds the makeProps external 18 | let make = React.Context.provider(themeContext); 19 | ``` 20 | 21 | ```reason 22 | /** or inside any other module */ 23 | 24 | // 1. The context itself 25 | let themeContext = React.createContext("light"); 26 | 27 | // 2. The provider component 28 | module ContextProvider = { 29 | include React.Context; // Adds the makeProps external 30 | let make = React.Context.provider(themeContext); 31 | }; 32 | ``` 33 | 34 | That will give you a `ContextProvider` component you can use in your application later on, by wrapping any component with `ContextProvider`, to have access to the context value inside the component tree. To know more about Context, check the [createContext page of the React.js documentation](https://react.dev/reference/react/createContext) and [when to use it](https://react.dev/learn/passing-data-deeply-with-context). 35 | 36 | ```reason 37 | /** App.re */ 38 | [@react.component] 39 | let make = () => 40 |
41 | 42 | 43 | 44 |
45 | ``` 46 | 47 | Then you can consume the context by using the `React.useContext` hook 48 | 49 | ```reason 50 | /** ComponentToConsumeTheContext.re */ 51 | [@react.component] 52 | let make = () => { 53 | let theme = React.useContext(ContextProvider.themeContext); 54 | 55 |

theme->React.string

56 | } 57 | ``` 58 | 59 | ## Binding to an external Context 60 | 61 | Binding to a Context defined in a JavaScript file holds no surprises. 62 | 63 | ```js 64 | /** ComponentThatDefinesTheContext.js */ 65 | export const ThemeContext = React.createContext("light"); 66 | ``` 67 | 68 | ```reason 69 | /** ComponentToConsumeTheContext.re */ 70 | [@bs.module "ComponentThatDefinesTheContext"] 71 | external themeContext: React.Context.t(string) = "ThemeContext"; 72 | 73 | [@react.component] 74 | let make = () => { 75 | let theme = React.useContext(themeContext); 76 | 77 |

theme->React.string

78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /ppx/test/component.t/input.re: -------------------------------------------------------------------------------- 1 | module React_component_with_props = { 2 | [@react.component] 3 | let make = (~lola) => { 4 |
{React.string(lola)}
; 5 | }; 6 | }; 7 | 8 | let react_component_with_props = ; 9 | 10 | module Upper_case_with_fragment_as_root = { 11 | [@react.component] 12 | let make = (~name="") => 13 | <> 14 |
{React.string("First " ++ name)}
15 | {React.string("2nd " ++ name)} 16 | ; 17 | }; 18 | 19 | /* module Using_React_memo = { 20 | [@react.component] 21 | let make = 22 | React.memo((~a) => 23 |
{Printf.sprintf("`a` is %s", a) |> React.string}
24 | ); 25 | }; 26 | 27 | module Using_memo_custom_compare_Props = { 28 | [@react.component] 29 | let make = 30 | React.memoCustomCompareProps( 31 | (~a) =>
{Printf.sprintf("`a` is %d", a) |> React.string}
, 32 | (prevPros, nextProps) => false, 33 | ); 34 | }; */ 35 | 36 | module Forward_Ref = { 37 | [@react.component] 38 | let make = 39 | React.forwardRef((~children, ~buttonRef) => { 40 | 41 | }); 42 | }; 43 | 44 | module Ref_as_prop = { 45 | [@react.component] 46 | let make = (~children, ~ref) => { 47 | ; 48 | }; 49 | }; 50 | 51 | module Onclick_handler_button = { 52 | [@react.component] 53 | let make = (~name, ~isDisabled=?) => { 54 | let onClick = event => Js.log(event); 55 |