Getting Started with ReasonML and ReasonReact
In this tutorial, we will go over the structure of a Reason project and take
a look at the example ReasonReact example project provided by Facebook. I will
explain the React code and go into it in further detail in a future tutorial.
Reason, or ReasonML, is a JavaScript-like syntax for OCaml with built-in
support for JSX. Generally it is used in conjunction with the BuckleScript
compiler that compiles OCaml 4.02 code to readable JavaScript without depending
on a separate runtime system. ReasonReact is a great way to write modern
websites. Reason also takes advantage of existing JS ecosystem and uses
npm/yarn to build programs.
For the rest of the tutorial the word Reason will mean a project that uses
ReasonML syntax and the BuckleScript compiler.
Download BuckleScript and ReasonML
I prefer to use yarn, but everything can be done with npm as well.
Init a Reason project with support for React Hooks.
yarn global add bs-platform
bsb -init test-reason -theme react-hooks
Try running the Reason project in a webpack development server.
cd test-reason
yarn
PORT=12345 yarn server
In a browser go to localhost:12345
. You should see a simple web app.
Understanding the Structure of a ReasonML Project
You should see the following files in your ReasonML directory. We will
go over them all in detail.
- .gitignore
- .merlin
- bsconfig.json
- build
- lib
- node_modules
- package.json
- README.md
- src
- webpack.config.js
- yarn.lock
package.json
If you have done ES6 development before, this file should be familiar to
you. This is the main build file for Reason projects.
name
: the name of the project.version
: the version.scripts
: various commands you can perform by typingyarn
then the command name.keywords
: npm search keywords if you upload this project as a library to npm.author
: the name of the author.license
: the license you release this project under, list of licenses available.dependencies
: libraries that your project directly depends on (from npm, local file system or github).devDependencies
: libraries that your project depends on for building and testing (from npm, local file system or github).
When you want to add a package to dependencies
, use yarn add <package-name>
.
When you want to add a package to devDependencies
, use
yarn add <package-name> --dev
.
yarn.lock
This file contains a list of the exact version of a library, its location, a
hash of the contents and a list of its dependencies. You should not edit this file
directly. It is created by yarn and should be checked into your versioning
system.
When you run yarn
, yarn add
or yarn remove
, the yarn.lock
is updated.
Sometimes you might get version conflicts or multiple versions of a single
package saved to yarn.lock
. Usually if I make changes to the dependencies, I
delete yarn.lock
, run yarn clean
then yarn
, make sure all of the
scripts
work in package.json
, then commit the new yarn.lock
.
bsconfig.json
This file is also a build file specific to BuckleScript. Any libraries it depends
on must be in package.json
and any libraries you call directly in Reason must
be in bsconfig.json
.
name
: the name of the project. Generally should be the same as the name in package.json.reason
: currently the only option isreact-jsx
. Set it to3
for react hooks support. Set it to2
for reason classes only.sources
: location of Reason code.package-specs
: option to output Commonjs or ES6suffix
: suffix of generated file names.namespace
: if true then use the directories as part of the namespace, if false then only use the file name. If it is a string then it provides a namespace for all files.bs-dependencies
: Reason dependencies used directly in the Reason code.bs-dependencies-dev
: Reason dependencies used for building or testing.refmt
: The version of reason syntax used:3
or2
.
webpack.config.js
A simple JavaScript program that packages the compiled result from Reason. It
also provides a small development server.
Here is a simple tutorial on webpack.
src
Where we place our Reason code. This can be changed in sources
in
bsconfig.json
.
node_modules
The dependency packages downloaded when yarn
is run. This is not checked into versioning.
lib
Output from compiling with Reason.
build
Output from running webpack.
README.md
A document with a project introduction. I recommend adding a change list to the bottom
with an entry per version or creating a separate change log file.
.merlin
A file generated by Reason to help IDEs navigate Reason code in the project.
.gitignore
The following files are excluded from versioning.
- .DS_Store
- .merlin
- .bsb.lock
- npm-debug.log
- /lib/bs/
- /node_modules/
Exploring the Reason Code in src
.re
files are Reason files. .bs.js
are files compiled from Reason to
JavaScript as specified by the suffix
in bsconfig.js
.
Index.re
This is the main file that gets loaded by index.html
. It calls the other
two Reason files in this project.
ReactDOMRe.renderToElementWithId(<Component1 message="Hello! Click this text." />, "index1");
ReactDOMRe.renderToElementWithId(<Component2 greeting="Hello!" />, "index2");
ReactDOMRe
is module that comes from the package reason-react
.
renderToElementWithId
is a function that renders a react component on the
given html id.
If you look in index.html
, you can see the two ids that these components are
attached to.
Component1.re
Component1
is a stateless react component. If you have ever used ES6 then the
syntax should be familiar to you. It starts by defining a function to handle a
button click.
let handleClick = (_event) => Js.log("clicked!");
let
: used when we want to define something.handleClick
: the name of the value/function.= (_event) =>
: define a function that takes one parameter.
By placing_
in front of the parameter name we tell the compiler we are
going to ignore it.Js.log("clicked!");
: print "clicked!" to the browser console.
Now we will define the stateless react component.
[@react.component]
let make = (~message) =>
<div onClick={handleClick}>
{ReasonReact.string(message)}
</div>;
[@react.component]
: a compiler directive we need when defining a React component. The component is named after the file.let make = (~message) =>
: a function that the Reason compoment compiler directive expects.~
means we pass a named parameter.<div onClick={handleClick}>
: this is JSX. This defines a div that uses the handleClick function we defined above.{ReasonReact.string(message)}
: display the string message parameter in the div.</div>;
: close the div and end the function.
Component2.re
Component2 is a stateful component.
Define a record (product) type to represent component's state. The state
maintains to values: an incrementing count and a value that indicates whether
or not to show a message.
type state = {
count: int,
show: bool,
};
Define an enum (sum) type to represent the available actions for the component.
This component supports Click
and Toggle
.
type action =
| Click
| Toggle;
Here is the main body of the component. It also starts with the React component
compiler directive and make
.
[@react.component]
let make = (~greeting) => {
let (state, dispatch) = React.useReducer((state, action) =>
switch (action) {
| Click => {...state, count: state.count + 1}
| Toggle => {...state, show: ! state.show}
}, {count: 0, show: true});
let message =
"You've clicked this " ++ string_of_int(state.count) ++ " times(s)";
<div>
<button onClick={_event => dispatch(Click)}>
{ReasonReact.string(message)}
</button>
<button onClick={_event => dispatch(Toggle)}>
{ReasonReact.string("Toggle greeting")}
</button>
{state.show ? ReasonReact.string(greeting) : ReasonReact.null}
</div>;
};
The most important part is let (state, dispatch)
. React.useReducer
is a
function that takes a function to define the action
behavior and an initial
state. React.useReducer
returns a value state
and a function dispatch
.
The function passed to React.useReducer
takes
two values: state of type state
and action of type action
. Then it matches
on all possible action
s and returns a new state
.
When action
is Click
it increments the value of count
by one.
Whenaction
is Toggle
it alters the show
state.
Finally, React.useReducer
takes the initial state, {count: 0, show: true}
.
These are the values of the component when it is first rendered.
After the state
and dispatch
definitions, message
is defined as
"You've clicked this " ++ string_of_int(state.count) ++ " times(s)";
.
The most interesting part is that state.count
uses the count
value from the
component's state
. Every time state.count
changes, message
gets
updated.
In the JSX body starting with <div>
we can see the use of the dispatch
function.
When <button onClick={_event => dispatch(Click)}>
is clicked, it emits a
Click
action and the reducer logic is run. When
<button onClick={_event => dispatch(Toggle)}>
is clicked, it emits a Toggle
action to the reducer.
The last part of the JSX body uses state.show
. Every time show
gets updated,
{state.show ? ReasonReact.string(greeting) : ReasonReact.null}
will update
as well. When show
is true it renders greeting. When it is false it renders nothing.
Conclusion
I hope that has piqued your interest in Reason and ReasonReact. It should be enough
to help you start exploring the official Reason documents. In the next tutorials,
I will discuss Reason and ReasonReact in greater depth and we will make our own
project.