;
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
;
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 |
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 |
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 |
;
86 | };
87 |
--------------------------------------------------------------------------------
/.github/workflows/opam.yml:
--------------------------------------------------------------------------------
1 | name: opam CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | build:
15 | name: Build
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | os:
20 | - ubuntu-latest
21 | - macos-latest
22 | # - windows-latest
23 | # It seems like dune 3.8 isn't available under opam-repository-mingw for Windows
24 | # disabling this for now
25 |
26 | ocaml-compiler:
27 | - 5.2.0
28 |
29 | runs-on: ${{ matrix.os }}
30 |
31 | steps:
32 | - name: Checkout code
33 | uses: actions/checkout@v5
34 |
35 | - name: Load opam cache when not Windows
36 | if: runner.os != 'Windows'
37 | id: opam-cache
38 | uses: actions/cache/restore@v4
39 | with:
40 | path: ~/.opam
41 | key: opam-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('**.opam') }}
42 |
43 | - name: Load opam cache when Windows
44 | if: runner.os == 'Windows'
45 | id: opam-cache-windows
46 | uses: actions/cache/restore@v4
47 | with:
48 | path: _opam
49 | key: opam-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('**.opam') }}
50 |
51 | - name: Use OCaml ${{ matrix.ocaml-compiler }}
52 | uses: ocaml/setup-ocaml@v3.3.5
53 | with:
54 | ocaml-compiler: ${{ matrix.ocaml-compiler }}
55 | dune-cache: true
56 |
57 | - name: Use Node.js
58 | uses: actions/setup-node@v6
59 | with:
60 | node-version: 24
61 |
62 | - name: Install dependencies
63 | run: make install
64 |
65 | - name: Build
66 | run: make build
67 |
68 | - name: Test
69 | run: make test
70 |
71 | - name: Jest
72 | run: make jest
73 |
74 | - name: Save cache when not Windows
75 | uses: actions/cache/save@v4
76 | if: steps.opam-cache.outputs.cache-hit != 'true' && runner.os != 'Windows'
77 | with:
78 | path: ~/.opam
79 | key: opam-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('**.opam') }}
80 |
81 | - name: Save cache when Windows
82 | uses: actions/cache/save@v4
83 | if: steps.opam-cache-windows.outputs.cache-hit != 'true' && runner.os == 'Windows'
84 | with:
85 | path: _opam
86 | key: opam-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('**.opam') }}
87 |
--------------------------------------------------------------------------------
/website/blog/2017-11-17-power-children.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: RR 0.3.0 - Power Children
3 | ---
4 |
5 | 0.3.0 is here! We've bumped the minor version because it's a breaking change (just a small one, see [HISTORY.md](https://github.com/reasonml/reason-react/blob/main/HISTORY.md#030). We've been _really_ careful with breaking changes). Stay tuned for more exciting features coming in the next few weeks (non-breaking). In the meantime, here's the big improvement of this release: **you're now allowed to pass any data type to JSX children**, not just `array(reactElement)`. And yes, they all type check, naturally!
6 |
7 | Head over to the improved [JSX](https://reasonml.github.io/reason-react/docs/en/jsx.html#children) docs and then [Children](https://reasonml.github.io/reason-react/docs/en/children.html) docs to know the details. Here's a summary.
8 |
9 | Previously, ` child1 child2 ` desugars to passing an array of `[|child1, child2|]` to `Comp` as `children`. This is likewise the case for ` child1 ` where, due to syntax constraints, we're _still_ passing an array of `[|child1|]` to `Comp`. Since Reason 3, we've introduced the [children spread syntax](https://reasonml.github.io/docs/en/jsx.html#children-spread) which simply makes ` ...child1 ` _not_ pass the array wrapper, but directly `child1`, to `children`.
10 |
11 | What does this unlock? Crazy tricks like these:
12 |
13 | - A form component that mandates a tuple of 2 react element and shows them side-by-side:
14 |
15 | ```reason
16 | ...(, )
17 | ```
18 |
19 | - A component that mandates a callback, no need for ReactJS' `React.Children.only` runtime check:
20 |
21 | ```reason
22 | ...((name) => )
23 | ```
24 |
25 | - A layout helper that accepts a variant of `TwoRows | ThreeRows | FourColumns`
26 |
27 | ```reason
28 | ...(ThreeRows(, child2, child3))
29 | ```
30 |
31 | The type system will ensure that the data you're passing matches its usage inside these component. No need to explicitly annotate them unless you want to.
32 |
33 | In reality, this change was around 2 or 3 lines of code (albeit with lots of thinking time). It's a nice indicator of us leveraging the language itself rather than reinventing such concept within the framework. Notice also that the new spread syntax addition is _still_ ReasonReact-agnostic (under the hood, the `children` label simply isn't passed a wrapper list/array in the presence of `...`). This kind of simple, gradual and vertically-integrated-yet-agnostic improvement is a large part of what we're hoping to do more in the future.
34 |
--------------------------------------------------------------------------------
/docs/graphql-apollo.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: GraphQL & Apollo
3 | ---
4 |
5 | GraphQL and React play well together, and ReasonReact builds upon this
6 | relationship. With
7 | [`graphql-ppx`](https://github.com/reasonml-community/graphql_ppx), you can get
8 | type-safe, compile-time validated GraphQL queries. This means that you don't
9 | have to wait till runtime to see if your queries or mutations are typed
10 | correctly! See the example below.
11 |
12 | ```reason
13 | /* Username.re */
14 |
15 | module UserQuery = [%graphql {|
16 | query UserQuery {
17 | currentUser {
18 | name
19 | }
20 | }
21 | |}];
22 | ```
23 |
24 | The `%graphql` name tells the compiler that a GraphQL query or mutation is
25 | coming, and some extra compile-time logic should be run that validates what's
26 | inside the `{|...|}` (multiline string syntax). In short, `graphql-ppx` will see
27 | if the query/mutation you wrote is consistent with your defined
28 | `graphql_schema.json`; if it's not, an error will be thrown by the compiler. See
29 | the following invalid example:
30 |
31 | ```reason
32 | /* Username.re */
33 |
34 | module UserQuery = [%graphql {|
35 | query UserQuery {
36 | currentUser {
37 | namee // ERROR: Unknown field on type currentUser
38 | }
39 | }
40 | |}];
41 | ```
42 |
43 | Say goodbye to runtime GraphQL errors!
44 |
45 | ## Apollo Client
46 |
47 | [Apollo Client](https://www.apollographql.com/docs/react/) is a well-known
48 | GraphQL client for React. Fortunately,
49 | [bindings](https://github.com/Astrocoders/reason-apollo-hooks) have been
50 | produced to allow us to use Apollo within ReasonReact.
51 |
52 | Let's see `graphql-ppx` and Apollo client in action. In the following example,
53 | we're going to query for the `name` of the `currentUser`, just like before.
54 | With Reason variants, we can handle each possible state in a readable and
55 | idiomatic way, all the while knowing our `UserQuery` is valid.
56 |
57 | The result is a React component that's (a) easy to reason about and (b)
58 | completely type-safe (from a Reason and GraphQL perspective).
59 |
60 | ```reason
61 | /* Username.re */
62 |
63 | module UserQuery = [%graphql {|
64 | query UserQuery {
65 | currentUser {
66 | name
67 | }
68 | }
69 | |}];
70 |
71 | [@react.component]
72 | let make = () => {
73 | let (currentUserName, _) = ApolloHooks.useQuery(UserQuery.definition);
74 |
75 |
86 | }
87 | ```
88 |
--------------------------------------------------------------------------------
/website/static/img/logos/pupilfirst.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/working-with-optional-data.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Working with Optional Data
3 | ---
4 |
5 | If you're coming from JavaScript, optional data can be a real pain. ReasonML removes a *whole class* of `null` and `undefined` bugs which makes your code safer and easier to write, but it takes some good examples to get you there :smile:
6 |
7 | ReasonML uses the `option` type to represent optional data. As defined in the standard library [here](https://reasonml.github.io/api/Option.html).
8 |
9 | Here are a few examples of how to work with optional data in ReasonML, using the [Belt][melange-belt] library from `melange.belt`.
10 |
11 | ### Accessing Optional Nested Data
12 |
13 | Say you have have an array of colors and you want to match one of those colors:
14 |
15 | ```reason
16 | let selected = colors->Belt.Array.getBy(c => c##id === id);
17 | ```
18 |
19 | ```javascript
20 | // Javascript
21 | const selected = colors.find(c => c.id === id);
22 | ```
23 |
24 | In both cases `selected` could be optional. The compiler may yell at you to handle that case, here's how you could handle it:
25 |
26 | ```reason
27 | let label = selected->Belt.Option.mapWithDefault(
28 | "Select a Color",
29 | selected => selected##name
30 | );
31 | ```
32 |
33 | What this is doing: "if selected exists, go look into `selected##name` otherwise return `Select a Color`".
34 |
35 | Read more about [`mapWithDefault`][melange-belt-option-map-with-default] here.
36 |
37 | ### Something or Nothing
38 |
39 | We need to grab the name of the person, but we also know that `name` can come back as `undefined`. We still want that label to be a string.
40 |
41 | ```reason
42 | /* create the type for the record, totally ok that it's the same name! */
43 |
44 | type person = {
45 | name: option(string)
46 | };
47 |
48 | let person = {
49 | name: None
50 | };
51 |
52 | let label = switch(person.name) {
53 | | Some(name) => name
54 | | None => "Peter"
55 | }
56 | ```
57 |
58 | You can also use `Belt.Optional.getWithDefault` which is sugar for above:
59 |
60 | ```reason
61 | let label = Belt.Option.getWithDefault(person.name, "Peter");
62 | ```
63 |
64 | If you need to return null instead of a component:
65 |
66 | ```
67 |
;
75 | ```
76 |
77 | Read more about [`getWithDefault`][melange-belt-option-get-with-default] here.
78 |
79 | [melange-belt]: https://melange.re/v4.0.0/api/re/melange/Belt
80 | [melange-belt-option-get-with-default]: https://melange.re/v4.0.0/api/re/melange/Belt/Option/index.html#val-getWithDefault
81 | [melange-belt-option-map-with-default]: https://melange.re/v4.0.0/api/re/melange/Belt/Option/index.html#val-mapWithDefault
82 |
--------------------------------------------------------------------------------
/docs/simple.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: A List of Simple Examples
3 | ---
4 |
5 | ### A Basic Greeting Component
6 |
7 | **Some things to note**:
8 |
9 | - Reason's `return` statements are implicit so you don't need to write `return`. It'll always be the last item in the block
10 | - Reason has labelled parameters (arguments) that are prefixed by a tilde, eg: `~message`
11 | - Since everything in ReasonReact is typed, we need to wrap our message in `React.string(message)`
12 |
13 | ```reason
14 | /* Greeting.re */
15 |
16 | [@react.component]
17 | let make = (~message) =>
{React.string(message)}
;
18 | ```
19 |
20 | Usage in another file. Looks very similar to JSX in Javascript!
21 |
22 | ```reason
23 | /* ... */
24 |
25 |
26 |
27 |
28 |
29 | /* ... */
30 | ```
31 |
32 | ### A Component with Optional Arguments and React.Fragment
33 |
34 | ```reason
35 | [@react.component]
36 | let make = (~title, ~description=?) =>
37 | <>
38 | /* React.Fragment works the same way as in React.js! */
39 |
title
40 | /* Handling optional variables where you don't want to render anything */
41 | {
42 | switch (description) {
43 | | Some(description) => {React.string(description)}
44 | /* Since everything is typed, React.null is required */
45 | | None => React.null
46 | }
47 | }
48 | >;
49 | ```
50 |
51 | ### A Form Component with React.js Differences
52 |
53 | ```reason
54 | [@react.component]
55 | let make = () => {
56 | /* unused variables are prefixed with an underscore */
57 | let onSubmit = _event => Js.log("Hello this is a log!");
58 |
59 | /* onSubmit=onSubmit turns to just onSubmit */
60 | ;
72 | };
73 | ```
74 |
75 | ### A Component that Renders a List of Items
76 |
77 | This component uses [Belt][melange-belt], from [melange][melange-home].
78 |
79 | ```reason
80 | /* We define the type of the item (this is a record) */
81 | type item = {
82 | id: string,
83 | text: string,
84 | };
85 |
86 | [@react.component]
87 | let make = (~items) =>
88 |
93 | )
94 | /* Since everything is typed, the arrays need to be, too! */
95 | ->React.array
96 | }
97 |
;
98 | ```
99 |
100 | [melange-belt]: https://melange.re/v4.0.0/api/re/melange/Belt
101 | [melange-home]: https://melange.re/v4.0.0/
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > **Warning**
2 | > For **ReScript** users, we recommend using [@rescript/react](https://github.com/rescript-lang/rescript-react) instead. Before, ReasonReact was designed to be used with BuckleScript and it got packaged into @rescript/react. More context on the move [here](https://rescript-lang.org/blog/bucklescript-is-rebranding).
3 |
4 | # [ReasonReact](https://reasonml.github.io/reason-react/)
5 |
6 | [](http://makeapullrequest.com)
7 | 
8 | [](https://discord.gg/reasonml)
9 | [](https://twitter.com/reasonml)
10 |
11 | ReasonReact is a safer, simpler way to build React components. You get a great type system with an even better developer experience. Why choose ReasonReact? Read more [here](https://reasonml.github.io/reason-react/docs/en/what-and-why)
12 |
13 | ReasonReact is just React.js under the hood. This makes it super easy to integrate with your current Next.js, Create React App, JavaScript, Flowtype or TypeScript project. Learn more about getting started [here](https://reasonml.github.io/reason-react/docs/en/installation#adding-reason-to-an-existing-reactjs-project-create-react-app-nextjs-etc)
14 |
15 | > Watch Ricky Vetter's Reason Conf talk, ["Why React is Just Better in Reason"](https://www.youtube.com/watch?v=i9Kr9wuz24g) to learn more about how Facebook & Messenger are using ReasonReact
16 |
17 | > Watch Jordan Walke's Reason Conf talk, ["React to the Future"](https://www.youtube.com/watch?v=5fG_lyNuEAw) to learn more about the future of ReasonML and React
18 |
19 | ## Example
20 |
21 | ```reason
22 | /* Greeting.re */
23 |
24 | [@react.component]
25 | let make = (~name) =>
{React.string("Hello " ++ name)}
26 | ```
27 |
28 | See all of our examples [here](https://reasonml.github.io/reason-react/docs/en/simple). For a full application, check out [reason-react-hacker-news](https://github.com/reasonml-community/reason-react-hacker-news).
29 |
30 |
31 |
32 | ## Using Your Favorite Javascript Libraries
33 |
34 | The same way that TypeScript has `type annotations`, we have `bindings`. Bindings are libraries that allow you to import a popular project (like lodash) or to import your own local file. ReasonReact is in fact an example of a binding!
35 |
36 | ## Documentation
37 |
38 | See [https://reasonml.github.io/reason-react](https://reasonml.github.io/reason-react)
39 |
40 | ## Contribute
41 |
42 | We welcome all contributors! Anything from docs to issues to pull requests. Please help us :smile:
43 |
44 | [./CONTRIBUTING.md](./CONTRIBUTING.md)
45 |
46 | ## Editor Support
47 |
48 | Looking for syntax highlighting for your favorite editor? Check out [ReasonML Editor Plugins](https://reasonml.github.io/docs/en/editor-plugins)
49 |
--------------------------------------------------------------------------------
/website/blog/2019-04-10-react-hooks.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ReasonReact 0.7.0: Support for React Hooks
3 | ---
4 |
5 | Version 0.7.0 of ReasonReact adds support for the [React Hooks API](https://reactjs.org/docs/hooks-intro.html). This allows for writing function components with state and complex interactions.
6 |
7 | You can now write components like
8 |
9 | ```reason
10 | [@react.component]
11 | let make = (~className, ~text) =>
{text}
;
12 | ```
13 |
14 | which will compile to a 0-cost React component that looks like
15 |
16 | ```js
17 | let make = ({className, text}) =>
{text}
;
18 | ```
19 |
20 | In these new components you can make use of the full Hooks api:
21 |
22 | ```reason
23 | type action =
24 | | Tick;
25 |
26 | type state = {
27 | count: int,
28 | };
29 |
30 | [@react.component]
31 | let make = () => {
32 | let (state, dispatch) = React.useReducer(
33 | (state, action) =>
34 | switch (action) {
35 | | Tick => {count: state.count + 1}
36 | },
37 | {count: 0}
38 | );
39 |
40 | React.useEffect0(() => {
41 | let timerId = Js.Global.setInterval(() => dispatch(Tick), 1000);
42 | Some(() => Js.Global.clearInterval(timerId))
43 | });
44 |
45 |
{ReasonReact.string(string_of_int(state.count))}
;
46 | };
47 | ```
48 |
49 | Please read the [new documentation](https://reasonml.github.io/reason-react/docs/en/components) for an in-depth explanation of how to write these components and how they compile to existing ReactJS code.
50 |
51 | These components make use of a [new jsx](https://reasonml.github.io/reason-react/docs/en/jsx) implementation that desugars exactly to `React.createElement` calls. In order to use these new components with JSX you will need to use `[@bs.config {jsx: 3}]` at the top of your file and use `^5.0.4` or `^6.0.1` BuckleScript which supports this new version. If you are in a greenfield project or you have fully converted, you can also [change the JSX version](https://bucklescript.github.io/docs/en/build-configuration.html#reason-refmt) in your `bsconfig.json` file to `3` and drop the in-file annotations.
52 |
53 | This release also makes use of a new `React` namespace for APIs for all of the code related to this new way of writing components. Some functions have been copied over (like `React.string`, `React.array`) and many are new (like hooks)!
54 |
55 | The only breaking change for this API is the introduction of new namespaces, which should be a fairly straightforward fix that most people won't need to do. However, since lots of the new code won't work without a more recent version of BuckleScript, we do recommend doing an upgrade on this version first and treating this as a breaking change even if you have no `React` namespace conflict. If you're interested in migrating some components the upgrade script is [provided](https://github.com/chenglou/upgrade-reason-react#installation). It will wrap existing ReasonReact components as if they are Hooks components. This script will _not_ attempt to re-write your logic as hooks because this is often a complicated process and it is not guaranteed to be correct. Please always inspect and test the work of the migration script to make sure it does what you're expecting!
56 |
--------------------------------------------------------------------------------
/website/blog/2021-05-07-rescript-migration.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: We are rebranding to ReScript / React
3 | ---
4 |
5 | BuckleScript & Reason are now called [ReScript](https://rescript-lang.org/blog/bucklescript-is-rebranding), therefore the **ReasonReact** bindings will now be known as **ReScript / React** and need to be moved to a different npm package.
6 |
7 | This means...
8 | - Our `reason-react` npm package will now be published as [`@rescript/react`](https://www.npmjs.com/package/@rescript/react)
9 | - `rescript-react@0.10.1` is basically a cleaned up version of `reason-react@0.9.1` (see the [CHANGELOG](https://github.com/rescript-lang/rescript-react/blob/master/Changes.md#v0101) for minor breaking changes).
10 | - The `reason-react` github repository will be archived. Please refer to our new repository at [github.com/rescript-lang/rescript-react](https://github.com/rescript-lang/rescript-react).
11 | - The next upcoming `@rescript/react` release will come with a few cool new features! Check out our [RFC discussion](https://forum.rescript-lang.org/t/rfc-rescript-react/901) for more infos.
12 |
13 | ## Migration
14 |
15 | From an API perspective, upgrading from `reason-react` to `@rescript/react` should be very easy, since most changes are a just matter of doing a few global search & replaces.
16 | Details for the changed APIs can be found in our revamped [ReScript / React migration guide](https://rescript-lang.org/docs/react/latest/migrate-from-reason-react).
17 |
18 | ### Important: All Libraries need to be upgraded to `rescript/react`
19 |
20 | Unfortunately `@rescript/react` based projects are not compatible with libraries that depend on the old `reason-react` package due to dependency conflicts.
21 |
22 | In other words: If you try to compile a project that uses both, `@rescript/react` and `reason-react`, the compiler will not compile due to a `React` module name collision.
23 |
24 | **There are currently three strategies to deal this issue:**
25 |
26 | - As a temporary workaround, use a patching tool like [`npm patch-package`](https://www.npmjs.com/package/patch-package) to adjust the package dependencies of indidvidual bindings to `@rescript/react` (adapt `package.json` & `bsconfig.json`)
27 | - Ask the maintainer of your outdated third-party library to create a new major version release with `@rescript/react` as a dependency (don't forget to mention which version supports `@rescript/react` in the README).
28 | - Or as a third alternative, create an updated fork of the project, or copy the bindings directly in your ReScript project.
29 | - Also take the occasion to adapt `bs-` prefixed library names to our new [ReScript package conventions](https://rescript-lang.org/docs/guidelines/publishing-packages). Otherwise your new libraries will not show up on our [package index](https://rescript-lang.org/packages).
30 |
31 | For more details on the name collision issue, please refer to [this forum post](https://forum.rescript-lang.org/t/discussion-reason-react-rescript-react-migration-path/1086).
32 |
33 | We apologize for the inconvenience; we promise that the migration work will be worth it!
34 |
35 | If you have any questions or migration issues, please open a discussion on the [ReScript Forum](https://forum.rescript-lang.org) to get support, or ping @ryyppy / @rickyvetter on Github.
36 |
37 | Thanks for being part of the ReasonReact community and see you on the ReScript side!
38 |
--------------------------------------------------------------------------------
/website/languages.js:
--------------------------------------------------------------------------------
1 | const languages = [
2 | {
3 | enabled: true,
4 | name: "日本語",
5 | tag: "ja"
6 | },
7 | {
8 | enabled: true,
9 | name: "English",
10 | tag: "en"
11 | },
12 | {
13 | enabled: false,
14 | name: "العربية",
15 | tag: "ar"
16 | },
17 | {
18 | enabled: false,
19 | name: "Bosanski",
20 | tag: "bs-BA"
21 | },
22 | {
23 | enabled: false,
24 | name: "Català",
25 | tag: "ca"
26 | },
27 | {
28 | enabled: false,
29 | name: "Čeština",
30 | tag: "cs"
31 | },
32 | {
33 | enabled: false,
34 | name: "Dansk",
35 | tag: "da"
36 | },
37 | {
38 | enabled: false,
39 | name: "Deutsch",
40 | tag: "de"
41 | },
42 | {
43 | enabled: false,
44 | name: "Ελληνικά",
45 | tag: "el"
46 | },
47 | {
48 | enabled: true,
49 | name: "Español",
50 | tag: "es-ES"
51 | },
52 | {
53 | enabled: false,
54 | name: "فارسی",
55 | tag: "fa-IR"
56 | },
57 | {
58 | enabled: false,
59 | name: "Suomi",
60 | tag: "fi"
61 | },
62 | {
63 | enabled: true,
64 | name: "Français",
65 | tag: "fr"
66 | },
67 | {
68 | enabled: false,
69 | name: "עִברִית",
70 | tag: "he"
71 | },
72 | {
73 | enabled: false,
74 | name: "Magyar",
75 | tag: "hu"
76 | },
77 | {
78 | enabled: false,
79 | name: "Bahasa Indonesia",
80 | tag: "id-ID"
81 | },
82 | {
83 | enabled: false,
84 | name: "Italiano",
85 | tag: "it"
86 | },
87 | {
88 | enabled: false,
89 | name: "Afrikaans",
90 | tag: "af"
91 | },
92 | {
93 | enabled: true,
94 | name: "한국어",
95 | tag: "ko"
96 | },
97 | {
98 | enabled: false,
99 | name: "मराठी",
100 | tag: "mr-IN"
101 | },
102 | {
103 | enabled: false,
104 | name: "Nederlands",
105 | tag: "nl"
106 | },
107 | {
108 | enabled: false,
109 | name: "Norsk",
110 | tag: "no-NO"
111 | },
112 | {
113 | enabled: false,
114 | name: "Polskie",
115 | tag: "pl"
116 | },
117 | {
118 | enabled: false,
119 | name: "Português",
120 | tag: "pt-PT"
121 | },
122 | {
123 | enabled: true,
124 | name: "Português (Brasil)",
125 | tag: "pt-BR"
126 | },
127 | {
128 | enabled: false,
129 | name: "Română",
130 | tag: "ro"
131 | },
132 | {
133 | enabled: true,
134 | name: "Русский",
135 | tag: "ru"
136 | },
137 | {
138 | enabled: false,
139 | name: "Slovenský",
140 | tag: "sk-SK"
141 | },
142 | {
143 | enabled: false,
144 | name: "Српски језик (Ћирилица)",
145 | tag: "sr"
146 | },
147 | {
148 | enabled: false,
149 | name: "Svenska",
150 | tag: "sv-SE"
151 | },
152 | {
153 | enabled: false,
154 | name: "Türkçe",
155 | tag: "tr"
156 | },
157 | {
158 | enabled: false,
159 | name: "Українська",
160 | tag: "uk"
161 | },
162 | {
163 | enabled: false,
164 | name: "Tiếng Việt",
165 | tag: "vi"
166 | },
167 | {
168 | enabled: true,
169 | name: "中文",
170 | tag: "zh-CN"
171 | },
172 | {
173 | enabled: true,
174 | name: "繁體中文",
175 | tag: "zh-Hant"
176 | },
177 | {
178 | enabled: false,
179 | name: "ɥsᴉlƃuƎ uʍopǝpᴉsd∩",
180 | tag: "en-UD"
181 | }
182 | ];
183 | module.exports = languages;
184 |
--------------------------------------------------------------------------------
/website/static/img/logos/appier.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/blog/2019-10-18-new-bsb-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: New ReasonReact bsb template!
3 | ---
4 |
5 | 
6 |
7 | BuckleScript 6.2.1 just got released, and with it came a freshly updated bsb ReasonReact template. To try it, just use the [official installation](https://reasonml.github.io/reason-react/docs/en/installation#bucklescript): `bsb -init my-react-app -theme react-hooks`.
8 |
9 | If you like what ReasonReact's Router did to your routing experience, you'll love what this template does to your project scaffolding. Highlights:
10 |
11 | - **No more development-time bundler needed!**
12 | - Files are dynamically `require`d every page reload. **No caching, no stale bundle, no race condition**. Bsb provided us some much needed sanity in the build system. We're now applying the same philosophy to the subsequent bundling step at development time.
13 | - Thanks to that, the entire edit-reload cycle is now in milliseconds, with **cold** reloading. No state bugs.
14 | - An optional, example bundler (Webpack) for production is provided, just for completeness.
15 | - **This starter boilerplate now serves the extra purpose of being a ReasonReact examples repo.**
16 | - Evergreen collection of use-cases right in your template. Supersedes the now-deprecated [reason-react-examples](https://github.com/reasonml-old/reason-react-example) repo.
17 | - Informative comments on possible, lean architecture choices. No decision paralysis.
18 | - **Table of ReasonReact features used** in the README, so that you can eye the ReasonReact API you need and directly jump into the corresponding file.
19 | - **Finally, a front-end boilerplate you can 100% fit in your head!**
20 | - **Only two production dependencies**: React and ReasonReact.
21 | - **Only two development-time dependencies**: BuckleScript and moduleserve (the latter resolves the `require`s during development).
22 | - Simple, transparent and great for learners. Nothing in the repo is opaque; everything can be tweaked. You get to make structural changes as you see fit.
23 |
24 | Ever seen newcomers subconsciously blame themselves for not understanding a boilerplate when it's too rigid or fragile? It hurts to watch that. Front-end boilerplate generators have become complex beasts that have exceed the threshold acceptable for learning. For us, a boilerplate needs to enable folks to:
25 |
26 | - get to their first working screen quickly, and
27 | - understand the entirely of what's going on. Hard to change what you don't understand!
28 |
29 | Most boilerplates overfit the first, at the **huge expense** of the second. There are simply too many layers of shaky abstractions piled on top of each other for a person to truly grasp it; all the bells and whistles one might think newcomers appreciate pale in comparison to the disempowerment they'd feel facing such large scope and fragile setups. It shouldn't feel this elaborate to put pixels on the screen!
30 |
31 | Such boilerplate is no way to design learnability. That's why ours keeps things at a minimum, and should ironically feel much easier to get started with and to fully grasp than the opposite, overbearing approach. We encourage you to go through every file in the project; they're well-commented, succinct, and immediately useful. It goes without saying that we did not compromise on the polish of the examples =).
32 |
33 | For a long time, folks thought their edit-reload experience would be achieved through adding more layers on top of their bundler; we believe, just like for our ReasonReact Router, that we've achieved this by removing the layers.
34 |
--------------------------------------------------------------------------------
/website/blog/2023-06-11-reborn.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ReasonReact is back (and powered by Melange)
3 | ---
4 |
5 | ReasonReact moved to `@rescript/react` after the ReScript announcement. The future of ReasonReact has been uncertain ever since. In the meantime, the Melange team announced the first major release of [Melange](https://anmonteiro.substack.com/p/melange-10-is-here) and took the first steps towards deeper integration with the OCaml ecosystem.
6 |
7 | Today, we are happy to announce that ReasonReact is back and will be maintained by the Melange team going forward.
8 |
9 | ## What's changing?
10 |
11 | We released ReasonReact to the [opam repository](https://ocaml.org/p/reason-react/latest). In the Melange docs, we document [how to depend](https://melange.re/v4.0.0/package-management/) on packages in opam.
12 |
13 | We updated the documentation to reflect these new changes and we will continue to improve it over time.
14 |
15 | ## Adopting ReasonReact
16 |
17 | To use ReasonReact, you must be using Melange. [Get started here](https://melange.re/v4.0.0/getting-started/).
18 |
19 | Next:
20 |
21 | - Install the latest version of ReasonReact: `opam install reason-react`
22 | - In your dune file, add `reason-react` to `libraries` and preprocess with `reactjs-jsx-ppx`:
23 |
24 | > (libraries reason-react)
25 | > (preprocess (pps react-jsx-ppx))
26 |
27 | - Replace any previously deprecated modules with their new names: `ReactDOMRe` -> `ReactDOM`, `ReactEventRe` -> `ReactEvent`, `ReasonReact` -> `React`.
28 | - Remove any usage of `ReasonReactCompat` and `ReasonReactOptimizedCreateClass`. These have long been deprecated, and have been removed in this version.
29 |
30 | ## Future
31 |
32 | We started moving all ReasonReact related packages to Melange and 0.11 and want to make sure the ReasonML community can update the 3rd party libraries to use Melange as well.
33 |
34 | Some of them are published in [github.com/melange-community](https://github.com/melange-community/) or [github.com/ahrefs](https://github.com/ahrefs). Examples include [melange-react-dates](https://github.com/ahrefs/melange-react-dates) or [melange-recharts](https://github.com/ahrefs/melange-recharts).
35 |
36 | There's lots to do for the next version. We are committed to continue improving ReasonReact and make it a great library for the Melange and ReasonML community. In the future, we expect to make the following changes:
37 |
38 | - Track `reacjs-jsx-ppx` in this repository, and rename it to `reason-react-ppx`, a more suitable name
39 | - Use the [new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) from React 17
40 | - Fix code-generation locations for JSX and props
41 | - Continue improving the documentation
42 |
43 | Feel free to reach out if you have any feedback. You can [open an issue](https://github.com/reasonml/reason-react/issues) in this repository, or join the [ReasonML Discord](https://discord.gg/reasonml).
44 |
45 | ## Full changelog
46 |
47 | The version of ReasonReact published to npm is compatible with ReScript 0.9.0. Since then, we've fixed a few things:
48 |
49 | - [0.9.2] Add drag event API binding
50 | - [0.9.2] Add `aria-haspopup` as ariaHaspopup as prop
51 | - [0.9.2] Add `aria-current` as ariaCurrent as prop
52 | - [0.9.2] Add `aria-checked` as ariaChecked as prop
53 | - [0.10.0] Deprecate legacy modules (ReasonReact, ReasonReactCompat, ReasonReactOptimizedCreateClass)
54 |
55 | ### Version 0.11
56 |
57 | - [0.11.0] Add suppressHydrationWarning
58 | - [0.11.0] Created opam package (reason-react.opam)
59 | - [0.11.0] Requires dune 3.8.0 (via dune-project)
60 | - [0.11.0] Full migration to Melange
61 |
62 | Have a good day!
63 |
--------------------------------------------------------------------------------
/website/blog/2023-09-13-reason-react-ppx.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Support for the JSX transform under a new name `reason-react-ppx`
3 | ---
4 |
5 | Today, we're releasing ReasonReact 0.12! The most important changes are renaming the JSX transform PPX from `reactjs-jsx-ppx` to `reason-react-ppx`, along with many other improvements:
6 |
7 | - `reason-react-ppx` now emits the React 17 [JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html).
8 | - Prop locations have been greatly improved, resulting in much better editor integration.
9 | - Check the full changes below for more details around other fixes and small breaking changes.
10 |
11 | ### Locations from domProps in action (Thanks @jchavarri)
12 | 
13 |
14 | ### new JSX transform (Thanks @anmonteiro)
15 |
16 |
17 | ## What's changing?
18 |
19 | Track `reactjs-jsx-ppx` in the same repository as `reason-react`, and rename it to `reason-react-ppx`, a more suitable name with a few improvements:
20 | - Use the [new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) from React 17
21 | - Fix code-generation locations for JSX and props
22 | - Fix to allow using `React.memoCustomCompareProps`
23 | - Fix Type of optional prop not checked correctly in JSX
24 | - The ppx will be synced with reason-react (and tested together)
25 |
26 | Take a look at [reasonml/reason-react/releases/tag/0.12.0](https://github.com/reasonml/reason-react/releases/tag/0.12.0) for a full listing of all the changes.
27 |
28 | ## Updating to latest
29 |
30 | ReasonReact works best with Melange. [Get started here](https://melange.re/v4.0.0/getting-started/).
31 |
32 | Next:
33 |
34 | - Update the package definitions with `opam update`
35 | - Install the latest versions with `opam install reason-react reason-react-ppx` (or upgrade `opam upgrade reason-react` & install `opam install reason-react-ppx`)
36 | - In your dune file, add `reason-react` to `libraries` and `reason-react-ppx` to `preprocess`:
37 |
38 | ```clojure
39 | (libraries reason-react)
40 | (preprocess (pps reason-react-ppx))
41 | ```
42 |
43 | - Remove any usage of `ReactDOM.props`
44 | - Bump React.js version to v17/v18 to use the new JSX transformation
45 | - We added `depexts` in opam for `react` and `react-dom` to ensure you have the correct versions installed from npm. [`depexts`](https://opam.ocaml.org/packages/opam-depext/) is the mechanism by opam to ensure the correct versions of external dependencies are present in your system. Learn more here on the Melange documentation page about [bindings and package management](https://melange.re/v4.0.0/package-management/#bindings-and-package-management):
46 |
47 | ```clojure
48 | depexts: [
49 | ["react"] {npm-version = "^16.0.0 || ^17.0.0"}
50 | ["react-dom"] {npm-version = "^16.0.0 || ^17.0.0"}
51 | ]
52 | ```
53 |
54 | ## Future plans
55 |
56 | - OCaml 5.1 + Melange v2
57 | - Reason 3.10 support
58 | - React 18 bindings
59 | - Continue improving the state of the ppx (locations, avoid raising, etc..)
60 | - Deprecate some legacy modules: `ReasonReact` and `ReactDOMRe`
61 | - Work on interface files
62 |
63 | Feel free to reach out if you have any feedback or want to get involved. You can [open an issue](https://github.com/reasonml/reason-react/issues) in this repository, or join the [ReasonML Discord](https://discord.gg/reasonml) and contact me (@davesnx) directly.
64 |
--------------------------------------------------------------------------------
/docs/usestate-hook.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useState
3 | ---
4 |
5 | The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.
6 |
7 | [React.js docs for useState](https://reactjs.org/docs/hooks-reference.html#usestate)
8 |
9 | ### Usage
10 |
11 | The `useState` hook takes a function that returns the initial state value and returns a tuple with the current state value and a function to update the state.
12 |
13 | ReasonReact exposes the `useState` hook with the initialiser function, not with the inmediate value.
14 |
15 | ```reason
16 | let useState: (unit => 'state) => ('state, 'state => unit);
17 | ```
18 |
19 | ### A Simple Counter Example
20 |
21 | ```reason
22 | [@react.component]
23 | let make = (~initialCount) => {
24 | let (count, setCount) = React.useState(_ => initialCount);
25 |
26 |
27 | {React.string("Count: " ++ Int.to_string(count))}
28 |
31 |
34 |
37 | ;
38 | };
39 | ```
40 |
41 | ## Using Event values with useState
42 |
43 | In ReactJS, it's common to update a component's state based on an event's
44 | value. Because ReasonReact's `useState` is slightly different than ReactJS,
45 | directly translating JavaScript components to Reason can lead to a common bug.
46 |
47 | ```js
48 | /* App.js */
49 | function App() {
50 | const [name, setName] = React.useState("John");
51 | return (
52 | setName(event.target.value)}
56 | />
57 | );
58 | }
59 | ```
60 |
61 | If we convert this component to reason, we may attempt to write this:
62 |
63 | ```reason
64 | /* App.re */
65 | /* WRONG! Don't do this! */
66 | [@react.component]
67 | let make = () => {
68 | let (name, setName) = React.useState(() => "John");
69 | setName(_ => React.Event.Form.target(event)##value)
73 | />;
74 | };
75 | ```
76 |
77 | Can you spot the bug?
78 |
79 | In the Reason version, the `onChange` callback won't correctly update the state.
80 | This happens because the callback passed to `setName` is run *after* the `event`
81 | variable is cleaned up by React, so the `value` field won't exist when it's
82 | needed.
83 |
84 | This isn't actually any different than how events and `useState` hooks work in
85 | ReactJS when you choose to use a callback with `setName`. The only difference
86 | is that ReasonReact enforces that we always use callbacks.
87 |
88 | ## Solution
89 |
90 | Fortunately, fixing this bug is simple:
91 |
92 | ```reason
93 | /* App.re */
94 | /* CORRECT! This code is bug-free. 👍 */
95 | [@react.component]
96 | let make = () => {
97 | let (name, setName) = React.useState(() => "John");
98 | {
103 | let value = React.Event.Form.target(event)##value;
104 | setName(_ => value)
105 | }
106 | }
107 | />;
108 | };
109 | ```
110 |
111 | The key is to extract the `value` from the `event` *before* we send it to
112 | `setName`. Even if React cleans up the event, we don't lose access to the
113 | value we need.
114 |
115 | [melange-url-docs]: https://melange.re/v4.0.0
116 |
--------------------------------------------------------------------------------
/docs/testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Testing components
3 | ---
4 |
5 | Even though types make your code safer, it doesn't remove the need for testing!
6 |
7 | If you want to test your ReasonReact components using your JavaScript testing stack, that's perfectly alright, but if you prefer to write them in Reason, here are some testing frameworks and bindings to make your life easier:
8 |
9 | ### Test framework
10 |
11 | Pick the test framework you like the best (both use Jest under the hood):
12 |
13 | - [reason-test-framework](https://github.com/bloodyowl/reason-test-framework)
14 | - [bs-jest](https://github.com/glennsl/bs-jest)
15 |
16 | ### Test utilities
17 |
18 | We provide test utilities in the [`ReactDOMTestUtils`](https://github.com/reasonml/reason-react/blob/main/src/ReactDOMTestUtils.re) module.
19 |
20 | If you want to have a look at a real example, here's what a test might look like from reason-react's test suite [Hooks__test](https://github.com/reasonml/reason-react/blob/main/test/Hooks__test.re).
21 |
22 | Here's is a basic example of what a test might look like:
23 |
24 | ```reason
25 | open ReactDOMTestUtils;
26 | open TestFramework;
27 | // TestFramework isn't a real module, just an imaginary set of bindings
28 | // to a JavaScript testing framework
29 |
30 | describe("My basic test", ({test, beforeEach, afterEach}) => {
31 | // Here, we prepare an empty ref that will eventually be
32 | // the root node for our test
33 | let container = ref(None);
34 |
35 | // Before each test, creates a new div root
36 | beforeEach(prepareContainer(container));
37 | // After each test, removes the div
38 | afterEach(cleanupContainer(container));
39 |
40 | test("can render DOM elements", ({expect}) => {
41 | // The following function gives us the div
42 | let container = getContainer(container);
43 | let root = ReactDOM.Client.createRoot(container);
44 |
45 | // Most of the ReactDOMTestUtils API is there
46 | act(() => {
47 | ReactDOM.Client.render(root,