The challenge is to create a todo list that you can:
- Add items to.
- Check off items
- Filter items by completion status
- Whatever you want!
This is to teach basic Reason and the React bindings (called ReasonReact).
We recommend you use VSCode, you can search "reasonml" for an extension that supports Reason. We recommend: "OCaml and Reason IDE"
To get the app running you can do:
npm i
npm start
Then open up http://localhost:1414
If you're going to switch off with other people we reccomend a mob timer to help you remember to switch. Pick whatever
- In browser: https://agility.jahed.io/timer.html
- Mac app: http://mobster.cc/
Basically you want to be able to add todo items to the todo list by typing something into the box and clicking add or pushing enter. Example:
Hints:
There's a lot of usefull functions in Belt
which is like a lodash for any ReasonML project.
Namely here's a few useful ones:
/* This will add something to the front of a list */
List.add([1,2,3], -1) /* will return [-1, 1, 2, 3] */
/* this will add something to the back of the list */
List.concat([1,2,3], [4, 5, 6]) /* will return [1, 2, 3, 4, 5, 6] */
Solution: https://github.com/justgage/reason-todo-dojo/pull/4/files
At this step you'll want to check items off the list.
Tip 1:
the []
by default are immutable linked lists. Indexing into them to change them doesn't work.
Try using Belt.List.mapWithIndex
to change one item. The type signiture is:
let mapWithIndex: (list(todo), (int, todo) => todo) => list;
Tip 2:
To update one item of a record you do it like this:
{...record, thing: "new value"}
Gotcha 2:
the onClick function on dom element must take an event so if you don't care about this event so if you do this:
// ERROR! typing () means you're passing "unit", which isn't compatible with the event type.
<div onClick={() => Js.log("I clicked!")}></div>
It will throw an error! So you have to do this:
/* throwing a `_` in front of a variable name lets ReasonML know that you want to ignore it. */
<div onClick={_ignoredEvent => Js.log("I clicked!")}></div>
Solution: https://github.com/justgage/reason-todo-dojo/pull/5/files
In this you should have tabs at the top of the todo list that you can sort them by either done, pending or all.
Example:
This one is more of a stretch goal so I won't provide any tips but try to use a variant type to model your tabs: https://reasonml.github.io/docs/en/variant
Please show off your stuff by making pushing up a PR or deploying it here:
npm run deploy
Then post your solutions in the UtahJS #reasonml channel: https://slack.utahjs.com/
There's a really great "Cheat Sheet" for JS to Reason Syntax. It's no replacement for actually learning the language but it's useful for people just trying to read ReasonML: https://reasonml.github.io/docs/en/syntax-cheatsheet
Reason can look a lot like JavaScript, however it's quite a bit different.
In ReasonML everything is double quotes. No more fights, just get used to it.
Single quotes are reserved for a single character like in C, and C++. This is not very useful in JavaScript however if you compile ReasonML to native it can be nice.
In Reason let
is used instead of const
.
For the most part you shouldn't need any mutable variables but if you really do there's ways to do it in the ReasonML docs.
There's no return
keyword in ReasonML. The last line of a function always returns:
let add = (x, y) => {
let solution = x + y;
solution; /* <- this get's automatically returned */
}
Again, ReasonML doesn't allow you to omit semicolons, you just have to get used to it if you're used to leaving them off. Currently the parser can get pretty confused when you omit them so make sure to look for them as it's one of the most common source of hard to read compiler errors.
Reason has a cool operator called "Pipe first" or sometimes "Fast Pipe". Basically it lets you call any group of functions in a chaining style:
/* this is BuckleScript standard library, List is in it and many other modules */
/* that support "Pipe First" style. */
open Belt;
/* you could do this, but what do you name the variables??? */
let i = [1, 2, 3];
let ii = List.map(i, x => x * 2);
let iii = List.map(ii, x => x - 2);
/* terser, but not very readable */
let i = List.map(
List.map([1, 2, 3], x => x * 2),
x => x - 2
)
/* More readable and compiles to the thing above */
let i =
[1, 2, 3]
->List.map(x => x * 2)
->List.map(x => x - 2)
Because ReasonML is statically typed (in a way that doesn't allow polymorphism) you have to specify what type the children of a component are:
<div>
{React.string("Way 1")}
"Way 2"->React.string
</div>
Again, the official docs are better than this short explanation but ReasonML's module system is very different from JS.
In JavaScript it's file-path based. Meaning you have to point Webpack to the
actual file using folder paths like ./App/App.js
. For example:
import App from "./App/App.js";
import Math from '../lib/Math.js"
const three = Math.add(1, 2);
<App>...</App>
In contrast ReasonML is a global non-path based system. Basically it works like this:
/* no imports! Just access the modules directly */
let three = Math.add(1,2);
<App>...</App>
You just forget the imports, and it finds it because the filename is App.re
and Math.re
!
If you have more questions about this, just ask! It can be confusing at times.