├── .gitignore ├── LICENSE ├── README.md ├── bsconfig.json ├── examples ├── components-1 │ └── components1.re ├── components-2 │ └── components2.re ├── components-3 │ └── components3.re ├── components-4 │ └── components4.re ├── hello-world-1 │ └── helloWorld1.re ├── jsx-1 │ └── jsx1.re └── jsx-2 │ └── jsx2.re ├── package.json └── quick-start.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /lib/ 4 | npm-debug.log 5 | .merlin 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Reason-React Quick Start guide 3 | 4 | **This guide is OBSOLETE, seeing as the reason-react API has been significantly revamped** 5 | 6 | Press [Start](https://github.com/glennsl/reason-react-quick-start/blob/master/quick-start.md) to continue. 7 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | /* This is the BuckleScript configuration file. Note that this is a comment; 2 | BuckleScript comes with a JSON parser that supports comments and trailing 3 | comma. If this screws with your editor highlighting, please tell us by filing 4 | an issue! */ 5 | { 6 | "name" : "reason-react-quick-start", 7 | "reason" : { "react-jsx" : true}, 8 | "bs-dependencies": ["reason-react"], 9 | "sources": [ 10 | { 11 | "dir": "examples", 12 | "public": "all", 13 | "type" : "dev", 14 | "subdirs": [ 15 | "hello-world-1", 16 | "jsx-1", 17 | "jsx-2", 18 | "components-1", 19 | "components-2", 20 | "components-3", 21 | "components-4", 22 | ], 23 | }, 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /examples/components-1/components1.re: -------------------------------------------------------------------------------- 1 | module Welcome = { 2 | /* Start */ 3 | module Welcome = { 4 | include ReactRe.Component; 5 | let name = "Welcome"; 6 | 7 | type props = { 8 | name: string 9 | }; 10 | 11 | let render {props} => 12 |

(ReactRe.stringToElement ("Hello, " ^ props.name))

; 13 | }; 14 | 15 | include ReactRe.CreateComponent Welcome; 16 | let createElement ::name ::children => 17 | wrapProps { name: name } ::children; 18 | /* End */ 19 | }; 20 | 21 | ReactDOMRe.renderToElementWithId "root"; -------------------------------------------------------------------------------- /examples/components-2/components2.re: -------------------------------------------------------------------------------- 1 | /* Part 1 */ 2 | module Welcome = { 3 | let createElement ::name ::children () => 4 |

(ReactRe.stringToElement ("Hello, " ^ name))

; 5 | }; 6 | 7 | /* Part 2 */ 8 | ReactDOMRe.renderToElementWithId "root"; 9 | -------------------------------------------------------------------------------- /examples/components-3/components3.re: -------------------------------------------------------------------------------- 1 | module Clock = { 2 | module ClockSpec = { 3 | include ReactRe.Component.Stateful; 4 | let name = "Clock"; 5 | 6 | type props = (); 7 | type state = { 8 | date: Js.Date.t 9 | }; 10 | 11 | let getInitialState _ => { 12 | date: Js.Date.make () 13 | }; 14 | 15 | let render {state} => 16 |
17 |

(ReactRe.stringToElement "Hello, World")

18 |

(ReactRe.stringToElement ("It is " ^ Js.Date.toLocaleTimeString state.date))

19 |
20 | }; 21 | 22 | include ReactRe.CreateComponent ClockSpec; 23 | let createElement ::children => 24 | wrapProps () ::children; 25 | }; 26 | 27 | let render () => 28 | ReactDOMRe.renderToElementWithId "root"; 29 | 30 | Js.Global.setInterval render 1000; 31 | -------------------------------------------------------------------------------- /examples/components-4/components4.re: -------------------------------------------------------------------------------- 1 | module Counter' = { 2 | /* Start */ 3 | module Counter = { 4 | include ReactRe.Component.Stateful; 5 | let name = "Counter"; 6 | 7 | type props = (); 8 | type state = { 9 | count: int 10 | }; 11 | 12 | let getInitialState _ => { 13 | count: 0 14 | }; 15 | 16 | let increment {state} _ => 17 | Some { count: state.count + 1 }; 18 | 19 | let render {state, updater} => 20 |
21 |

(ReactRe.stringToElement ("Current count: " ^ string_of_int state.count))

22 | 25 |
; 26 | }; 27 | /* End */ 28 | 29 | include ReactRe.CreateComponent Counter; 30 | let createElement ::children => 31 | wrapProps () ::children; 32 | }; 33 | 34 | ReactDOMRe.renderToElementWithId "root"; 35 | -------------------------------------------------------------------------------- /examples/hello-world-1/helloWorld1.re: -------------------------------------------------------------------------------- 1 | ReactDOMRe.renderToElementWithId 2 |

(ReactRe.stringToElement "Hello, world!")

3 | "root"; 4 | -------------------------------------------------------------------------------- /examples/jsx-1/jsx1.re: -------------------------------------------------------------------------------- 1 | type user = { firstName: string, lastName: string }; 2 | 3 | let user: user = { 4 | firstName: "Harper", 5 | lastName: "Perez" 6 | }; 7 | 8 | let formatName user => 9 | user.firstName ^ " " ^ user.lastName; 10 | 11 | let hello = 12 | (ReactRe.stringToElement ("Hello, " ^ formatName user)); 13 | 14 | let header = 15 |

16 | hello 17 |

; 18 | 19 | ReactDOMRe.renderToElementWithId header "root"; 20 | -------------------------------------------------------------------------------- /examples/jsx-2/jsx2.re: -------------------------------------------------------------------------------- 1 | let items = [ 2 | "apples", 3 | "pears", 4 | "cashews", 5 | "pecans" 6 | ]; 7 | 8 | let isNuts = fun 9 | | "cashews" 10 | | "pecans" => true 11 | | _ => false; 12 | 13 | let nutIcon = 14 | ; 15 | 16 | let item name => 17 |
  • 18 | (ReactRe.stringToElement name) 19 | (isNuts name ? nutIcon : ReactRe.nullElement) 20 |
  • ; 21 | 22 | let list = 23 |
      24 | (items |> List.map item |> Array.of_list |> ReactRe.arrayToElement) 25 |
    ; 26 | 27 | ReactDOMRe.renderToElementWithId list "root"; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reason-react-quick-start", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "quick-start.md", 6 | "scripts": { 7 | "test": "bsb -make-world", 8 | "start": "bsb -make-world -w", 9 | "clean": "bsb -clean-world" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bs-platform": "bloomberg/bucklescript", 16 | "react": "^15.4.2", 17 | "react-dom": "^15.4.2", 18 | "reason-react": "reasonml/reason-react" 19 | }, 20 | "devDependencies": { 21 | }} 22 | -------------------------------------------------------------------------------- /quick-start.md: -------------------------------------------------------------------------------- 1 | 2 | # Reason-React quick start 3 | 4 | **This guide is OBSOLETE, seeing as the reason-react API has been significantly revamped. Please refer to the [official documentation](https://reasonml.github.io/reason-react/)** 5 | 6 | ## Introduction 7 | 8 | Reason-React is a pretty thin layer of reason/OCaml bindings to React. 9 | 10 | The next few sections will gradually introduce you to using Reason-React. We will gradually introduce new concepts and show more complex examples. Once you master these, you should be ready to go! 11 | 12 | You can also clone this repo and build the examples yourself, to see the full context and how they transpile to js. 13 | 14 | ### Prerequisites and additional resources 15 | 16 | Reason-React is a library built on React and BuckleScript and best used with Reason. You are expected to know the basics of both Reason and React. If you don't feel very confident, we recommend refreshing your knowledge so you can follow along more easily: 17 | * [An introduction to Reason](https://kennetpostigo.gitbooks.io/an-introduction-to-reason/content/) 18 | * [React Quick Start](https://facebook.github.io/react/docs/hello-world.html) 19 | 20 | You are not expected to know much about BuckleScript for the purpose of this guide alone, other than the fact that it somehow, as if by magic, converts OCaml/Reason code to Javascript. But you will eventually want to refer to the BuckleScript documentation, for configuration of `bsb`, js interop and such. And you'll want to refer to the Reason-React documentation for the gritty details: 21 | * [BuckleScript User Manual](https://bucklescript.github.io/bucklescript/Manual.html) 22 | * [Reason-React documentation proper](https://github.com/reasonml/reason-react/blob/master/documentation.md) 23 | 24 | ## Getting started 25 | 26 | A good place to start is by cloning and setting up `reason-react-example`: 27 | 28 | ```sh 29 | git clone https://github.com/chenglou/reason-react-example.git 30 | cd reason-react-example 31 | npm install 32 | npm start 33 | ``` 34 | 35 | Then in a different terminal do: 36 | ```sh 37 | npm run build 38 | ``` 39 | 40 | `npm start` will run the bucklescript build system (`bsb`) in watch mode. `npm run build` runs `webpack` in watch mode, which will pick up the artifacts produced by `bsb` and bundle them together with its dependencies to produce a single JavaScript file (for each configured target). 41 | 42 | The example project has `bsb` configured to watch the `src` folder, and webpack configured to pick up the artifact produced by bsb from `index.re`, and output `index.js` in the `src` folder alongside `index.html`, which can then be used to run it in a web browser. To start with you will most likely want to use the existing configuration with `index.re` as your entrypoint. You can still split your code into multiple source files. As long as they are alongside and used by `index.re` they'll get picked up by `bsb` and `webpack`. 43 | 44 | ## A small step for... oh, "hello world" 45 | 46 | The smallest Reason-React example looks like this: 47 | 48 | [//]: # "hello-world1" 49 | ```reason 50 | ReactDOMRe.renderToElementWithId 51 |

    (ReactRe.stringToElement "Hello, world!")

    52 | "root"; 53 | ``` 54 | 55 | Not too scary is it! This will render a header saying "Hello World" on the page. Ok, thnx, let's move on... 56 | 57 | 58 | ## There's JSX? There's JSX! 59 | 60 | Reason has excellent support for the JSX syntax, but there are some differences to be aware of: 61 | 62 | 63 | ### No curly braces around embedded expressions 64 | 65 | Reason's JSX does not need curly braces to embed expressions. Though complex expressions need to be surrounded by parentheses. And in Reason, most everything is an expression, so there is barely any limit to what you can intermingle in your JSX. Also, you _can_ surround expressions by curly braces if you really want to, you just don't need to. Surrounding something in curly braces anywhere will merely introduce a new lexical scope. 66 | 67 | [//]: # "jsx1" 68 | ```reason 69 | type user = { firstName: string, lastName: string }; 70 | 71 | let user: user = { 72 | firstName: "Harper", 73 | lastName: "Perez" 74 | }; 75 | 76 | let formatName user => 77 | user.firstName ^ " " ^ user.lastName; 78 | 79 | let hello = 80 | (ReactRe.stringToElement ("Hello, " ^ formatName user)); 81 | 82 | let header = 83 |

    84 | hello 85 |

    ; 86 | 87 | ReactDOMRe.renderToElementWithId header "root"; 88 | ``` 89 | 90 | 91 | ### The JSX is typechecked 92 | 93 | As with the babel JSX transforms for JavaScript, Reason's JSX is really just syntax sugar for plain Reason code. And because of this, your JSX will be typechecked too. If you mistype an attribute, the typechecker will give you a nudge, as it also will if you pass a `boolean` to an attribute that expects a `string`. 94 | 95 | But this also means `null`s and `string`s or even `list`s and `array`s aren't valid types for expressions embedded in JSX. Components expect their children to be React elements, and so we need to wrap them as elements. Luckily Reason-React provides functions to do exactly that: 96 | 97 | * `ReactRe.stringToElement` will wrap a string 98 | * `ReactRe.arrayToElement` will wrap an array of React elements 99 | * `ReactRe.nullElement` will pretend to be an element for the typechecker, but is really just a null 100 | 101 | [//]: # "jsx2" 102 | ```reason 103 | let items = [ 104 | "apples", 105 | "pears", 106 | "cashews", 107 | "pecans" 108 | ]; 109 | 110 | let isNuts = fun 111 | | "cashews" 112 | | "pecans" => true 113 | | _ => false; 114 | 115 | let nutIcon = 116 | ; 117 | 118 | let item name => 119 |
  • 120 | (ReactRe.stringToElement name) 121 | (isNuts name ? nutIcon : ReactRe.nullElement) 122 |
  • ; 123 | 124 | let list = 125 |
      126 | (items |> List.map item |> Array.of_list |> ReactRe.arrayToElement) 127 |
    ; 128 | 129 | ReactDOMRe.renderToElementWithId list "root"; 130 | ``` 131 | 132 | ## Components 133 | 134 | Components require that we dabble a bit in the black arts. But just a little bit. A basic component looks like this 135 | 136 | [//]: # "components1" 137 | ```reason 138 | module Welcome = { 139 | include ReactRe.Component; 140 | let name = "Welcome"; 141 | 142 | type props = { 143 | name: string 144 | }; 145 | 146 | let render {props} => 147 |

    (ReactRe.stringToElement ("Hello, " ^ props.name))

    ; 148 | }; 149 | 150 | include ReactRe.CreateComponent Welcome; 151 | let createElement ::name ::children => 152 | wrapProps { name: name } ::children; 153 | ``` 154 | 155 | So we define a module, which isn't and won't be the actual component but its specification. `include ReactRe.Component` is the bit of black magic that tells Reason-React what kind of component it is, and what it should expect to see defined in it. `ReactRe.Component` is the most basic kind, and requires us to define a `name` a type called `props`, and a `render` function. 156 | * `name` is the component's display name, which is useful for debugging. 157 | * `props` specifies the type of the component's attributes/properties. 158 | * `render` is, as you'd expect, the function that's called when the component is to be rendered. 159 | 160 | The actual component is defined by the second piece of black magic: `include ReactRe.CreateComponent Welcome`. Under the hood this will create a `comp` variable, which is the actual component and, as we'll learn later, is important for js interop. It will also create a `wrapProps` function, which we then use to define the `createElement` function, and which really just translates our properties from a labeled argument form (`::name`) into the form of a record that fits our `props` type. 161 | 162 | ### The elusive "component bag" 163 | 164 | Since idiomatic Reason/OCaml code does not use `this`, Reason-React defines a data structure that's called the "component bag" which fulfills the same role. Which is to avoid having to specify every damn thing you might want to use, just in case. Therefore many of the functions you define on your components, like the `render` function, will get passed a "component bag". It is a record of the shape `{props, state, updater, refSetter, instanceVars, setState}`, and you'll usually want to destructure the argument directly. If you need access to the state, you just add that like so: `let render { props, state } => ...` 165 | 166 | ### Look, ma, no magic! 167 | 168 | As far as Reason's JSX syntax is concerned, a component is nothing more than a module with a createElement function of a certain shape. So if we remove all the magic (and all the convenient functionality) we can define a component like this: 169 | 170 | [//]: # "components2 - Part 1" 171 | ```reason 172 | module Welcome = { 173 | let createElement ::name ::children () => 174 |

    (ReactRe.stringToElement ("Hello, " ^ name))

    ; 175 | }; 176 | ``` 177 | 178 | This is significantly less code, and for tiny components, this is sometimes a convenient trick, but most often you'll want all the magical convenience. Both components can be used like this: 179 | 180 | [//]: # "components2 - Part 2" 181 | ```reason 182 | ReactDOMRe.renderToElementWithId "root"; 183 | ``` 184 | 185 | ### Stateful components 186 | 187 | With the magic back in, adding a local state to a component is as easy telling Reason-React this is a stateful component, what the type of our state is, and what the initial state is: 188 | 189 | [//]: # "components3" 190 | ```reason 191 | module Clock = { 192 | module ClockSpec = { 193 | include ReactRe.Component.Stateful; 194 | let name = "Clock"; 195 | 196 | type props = (); 197 | type state = { 198 | date: Js.Date.t 199 | }; 200 | 201 | let getInitialState _ => { 202 | date: Js.Date.make () 203 | }; 204 | 205 | let render {state} => 206 |
    207 |

    (ReactRe.stringToElement "Hello, World")

    208 |

    (ReactRe.stringToElement ("It is " ^ Js.Date.toLocaleTimeString state.date))

    209 |
    210 | }; 211 | 212 | include ReactRe.CreateComponent ClockSpec; 213 | let createElement ::children => 214 | wrapProps () ::children; 215 | }; 216 | 217 | let render () => 218 | ReactDOMRe.renderToElementWithId "root"; 219 | 220 | Js.Global.setInterval render 1000; 221 | ``` 222 | 223 | Compared to our earlier component, not much fundamental has changed. `include ReactRe.Component` is now `include ReactRe.Component.Stateful`, we've defined a `state` type and a `getInitialState` function which takes `props` as its argument and returns the initial state. 224 | 225 | There's not much use in the state if it doesn't change, though. So let's get to that. 226 | 227 | #### Updating state 228 | 229 | Reason-React does have a setState function, but you shouldn't normally use it! Instead, you'll usually use the `updater` function that's passed to you in the component bag. Let's make a counter, where the count increases on each click of a button. 230 | 231 | [//]: # "components4" 232 | ```reason 233 | module Counter = { 234 | include ReactRe.Component.Stateful; 235 | let name = "Counter"; 236 | 237 | type props = (); 238 | type state = { 239 | count: int 240 | }; 241 | 242 | let getInitialState _ => { 243 | count: 0 244 | }; 245 | 246 | let increment {state} _ => 247 | Some { count: state.count + 1 }; 248 | 249 | let render {state, updater} => 250 |
    251 |

    (ReactRe.stringToElement ("Current count: " ^ string_of_int state.count))

    252 | 255 |
    ; 256 | }; 257 | ``` 258 | 259 | Here we get the `updater` function from the "component bag" passed to our `render` function, which we use to wrap our `increment` function before setting it as the button's `onClick` handler. When the button is clicked, `increment` will be called with the latest state (and whatever else is in the "component bag"). It then returns the new state, which will cause the component to rerender with the new state. 260 | 261 | Note that `updater` expects a function that returns `option state`, where returning None avoids the rerender because no state has changed. Also, beware that `updater` will return a function that only takes the unit. If you don't pass it on to a handler and intend to execute it, don't forget actually to do it. There's a little syntactic difference between function application and partial application, and the compiler might not warn about it (see https://github.com/reasonml/reason-react/issues/36). Compare these: 262 | 263 | ```reason 264 | let f : string => unit = fun value => 265 | updater (fun { state } () => Some { ...state, value }) (); 266 | ``` 267 | ```reason 268 | let f : string => unit = fun value => 269 | updater (fun { state } () => Some { ...state, value }); 270 | ``` 271 | 272 | The `setState` function differs from `updater` in that the latter is bound to the state a component has when `updater` gets created, while setState will always give you the fresh state. The downside of `setState` is that it must return a new state, and will cause a rerender even if nothing changed. This is due to a shortcoming with `setState` in React proper, and hopefully a very temporary problem (see https://github.com/reasonml/reason-react/issues/37). 273 | 274 | ### Lifecycle hooks 275 | 276 | ### Events 277 | 278 | ## Javascript interop 279 | 280 | ## Community 281 | 282 | We gather around the bonfire on the reasonml discord. [You should come join us!](https://discord.gg/reasonml) 283 | 284 | ## Advanced topics 285 | 286 | ### Desugaring of uncapitalized JSX 287 | 288 | As you plod along you might start wondering how JSX handles the out-of-the-box HTML elements, or uncapitalized components more generally. If you're really curious you might have noticed that `
    child1 child2
    ` desugars `div foo::bar children::[child1, child2] () [@JSX]`. So where's the `div` function? It actually doesn't exist. The `[@JSX]` annotation invokes some even more arcane black magic, a ppx transform that translates that into `ReactDOMRe.createElement "div" props::(ReactDOMRe.props foo::bar ()) [|child1, child2|];` 289 | 290 | So why is the middle step even there? I suspect it's in order to decouple the JSX from Reason-React and its ReactDOMRe module. Not sure how you'd avoid the ppx in practice, though... [TODO: explain better] 291 | 292 | 293 | ### High-order components and other patterns 294 | 295 | ## Appendix: Esoteric but useful resources 296 | 297 | Much of the BuckleScript and Reason ecosystem is in a state of flux and is therefore only partially documented, or not documented at all! It can, therefore, be very useful to look at the source code, or interface files if available, to get a better idea of what's available and how it works. Uhmm, Good Luck! 298 | 299 | * [ReasonJs source code](https://github.com/BuckleTypes/reason-js/blob/master/src/reasonJs.re) 300 | * [ReactRe interface source](https://github.com/reasonml/reason-react/blob/master/src/reactRe.rei) 301 | --------------------------------------------------------------------------------