Skip to content

Commit

Permalink
Merge pull request #10 from melange-re/order-conf-fixes
Browse files Browse the repository at this point in the history
Add 'Type transformation functions' section to order confirmation chapter
  • Loading branch information
feihong authored Dec 13, 2023
2 parents 7543861 + f580d45 commit 2dd370c
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 225 deletions.
10 changes: 7 additions & 3 deletions docs/celsius-converter-exception/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ converts it to Fahrenheit. Create a new file called `CelsiusConverter.re`:

<<< Snippets.re#celsius-converter-v1

## `Js.t` object

Inside the `input`'s `onChange` handler, we get the event target using
`ReactEvent.Form.target`, which has the type `ReactEvent.Form.t => {_.. }`. What
is `{_.. }`? It's shorthand for the `Js.t({..})` type[^1], which consists of two
Expand Down Expand Up @@ -109,7 +111,7 @@ argument](https://melange.re/v2.1.0/communicate-with-javascript/#labeled-argumen
In this case, the labeled argument is named `digits` and it's receiving a value
of `2`. It's not possible to pass in the value of a labeled argument without
using the `~label=value` syntax. We'll see more of labeled arguments in the
following chapters when we introduce [props](/todo).
following chapters after we [introduce props](/order-confirmation/#item-make).

## Partial application

Expand Down Expand Up @@ -138,8 +140,10 @@ feature to create a one-argument function by writing

<<< Snippets.re#partial-application{12,15}

We have a working component now, but catching exceptions isn't The OCaml Way! In
the next chapter, you'll see how to rewrite the logic using `option`.
-----

Nice, we have a working component now, but catching exceptions isn't The OCaml
Way! In the next chapter, you'll see how to rewrite the logic using `option`.

## Exercises

Expand Down
6 changes: 4 additions & 2 deletions docs/celsius-converter-option/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ here and not somewhere else. You are starting to experience the power of
[pattern matching](https://reasonml.github.io/docs/en/pattern-matching) in
OCaml.

## Option.map
## `Option.map`

It's a shame we had to give up the long chain of function calls from when we
were still using `float_of_string`:
Expand Down Expand Up @@ -110,7 +110,7 @@ At this point, your switch expression might look like this:

<<< Snippets.re#option-map

## when guard
## `when` guard

What if we wanted to render a message of complaint when the temperature goes
above 212° F (the boiling point of water) and not even bother to render the
Expand All @@ -127,6 +127,8 @@ The [when guard](https://reasonml.github.io/docs/en/pattern-matching#when)
allows you to add extra conditions to a switch expression branch, keeping
nesting of conditionals to a minimum and making your code more readable.

-----

Hooray! Our Celsius converter is finally complete. Later, we'll see how to
[create a component that can convert back and forth between Celsius and
Fahrenheit](/todo). But first, we'll explore [Dune, the build
Expand Down
21 changes: 13 additions & 8 deletions docs/counter/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ serve`. As a side effect, it will open a browser tab pointed to
<a href="http://localhost:8080/" target="_blank" rel="noreferrer
noopener">http://localhost:8080/</a>.

## The `App` component

Open `Index.re` and you'll see this:

<<< Snippets.re#app-v1
Expand All @@ -36,7 +38,7 @@ can be multiples modules inside a single file. For now, you just need to know
that all components in ReasonReact are modules, and the name of the component
comes from the name of the module.

## The make function
## The `make` function

The `make` function has the type `unit => React.element`, meaning it takes `()`
as the only argument and returns an object of type `React.element`. You'll need
Expand Down Expand Up @@ -77,10 +79,11 @@ following contents:
<<< Snippets.re#counter-v1

This is a component with a single `useState` hook. It should look fairly
familiar if you know about [hooks in React](https://react.dev/reference/react).
Note that we didn't need to manually define a module for `Counter`, because all
source files in OCaml are automatically modules, with the name of the module
being the same as the name of the file.
familiar if you know about [hooks in
React](https://react.dev/reference/react/hooks). Note that we didn't need to
manually define a module for `Counter`, because all source files in OCaml are
automatically modules, with the name of the module being the same as the name of
the file.

Now let's modify `App` so that it uses our new `Counter` component:

Expand Down Expand Up @@ -109,8 +112,10 @@ Let's add a bit of styling to the root element of `Counter`:

Unlike in React, the `style` prop in ReasonReact doesn't take a generic object,
instead it takes an object of type `ReactDOMStyle.t` that is created by calling
`ReactDOMStyle.make`. This isn't a sustainable way to style our app---in the
[orders chapter](/todo), we'll see how to style using CSS classes.
`ReactDOMStyle.make`. This isn't a sustainable way to style our app---later,
we'll see how to [style using CSS classes](/todo).

----

Congratulations! You've created your first ReasonReact app and component. In
future chapters we'll create more complex and interesting components.
Expand All @@ -133,7 +138,7 @@ What we covered in this section:
- How to create and run a basic ReasonReact app
- ReasonReact components are also modules
- OCaml has an `option` type whose value can be either `None` or `Some(_)`
- The pipe last operator is an alternate way to invoke functions that enables
- The pipe last operator (`|>`) is an alternate way to invoke functions that enables
easy chaining of function calls
- The `style` prop doesn't take generic objects

Expand Down
2 changes: 2 additions & 0 deletions docs/intro-to-dune/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ great advice if your repo only contains a single app!
:::
-----
Huzzah! You created a new `dune` file to build an app and a `Makefile` to serve
that app locally. In future chapters, we assume that you will use the same
directory structure for each new app you build.
Expand Down
10 changes: 6 additions & 4 deletions docs/intro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ teaches the language from the ground up and goes much deeper into its features.

| Title | Summary | Topics covered |
| ------ | ------- | -------------- |
| Counter | Number that can be incremented or decremented | module, Option, pipe last operator, function chaining, switch |
| Melange Playground | Use Melange Playground to explore OCaml’s numeric types | Playground, Int, Float |
| Celsius Converter | Single input that converts from Celsius to Fahrenheit | polymorphic object, exception handling, ternary expression, if-else expression, labeled argument, partial application |
| Celsius Converter using Option | The same component from the last chapter but replacing exception handling with Option | Option, Option.map, when guard |
| Counter | Number that can be incremented or decremented | module, Option, `React.string`, pipe last operator, function chaining, switch expression |
| Numberic Types | Use Melange Playground to explore OCaml’s numeric types | Int, Float, Playground, sharing snippets, comparison operators, arithmetic operators, widgets in Playground |
| Celsius Converter | Single input that converts from Celsius to Fahrenheit | `Js.t` object, string concatenation (`++`), exception handling, ternary expression, if-else expression, labeled argument, partial application |
| Celsius Converter using Option | The same component from the last chapter but replacing exception handling with Option | Option, `Option.map`, `when` guard |
| Introduction to Dune | A introduction to the Dune build system | `dune-project` file, `dune` file, `melange.emit` stanza, `Makefile`, monorepo structure |
| Order Confirmation | An order confirmation for a restaurant website | variant type, primary type of module (`t`), wildcard (`_`) in switch, `fun` syntax, `Js.Array` functions, `React.array`, type transformation functions |

...and much more to come!

Expand Down
4 changes: 3 additions & 1 deletion docs/numeric-types/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Js.log(Js.typeof(bar)); // prints "number"
Refer to the [Melange docs](https://melange.re/v2.1.0/communicate-with-javascript/#data-types-and-runtime-representation)
for a complete rundown of how OCaml types get translated to JavaScript types.

## Widgets in the playground
## Widgets in the Playground

Melange Playground can also render ReasonReact components! Click the **Live**
button next to the **JavaScript output** button. Now paste in the code to render
Expand All @@ -202,6 +202,8 @@ And then update the JSX in our `make` function to use the style objects in

Here's the [playground link](https://melange.re/v2.1.0/playground/?language=Reason&code=bW9kdWxlIFN0eWxlcyA9IHsKICBsZXQgbWFrZSA9IFJlYWN0RE9NU3R5bGUubWFrZTsKCiAgbGV0IHJvb3QgPQogICAgbWFrZSgKICAgICAgfmZvbnRTaXplPSIyZW0iLAogICAgICB%2BcGFkZGluZz0iMWVtIiwKICAgICAgfmRpc3BsYXk9ImZsZXgiLAogICAgICB%2BZ3JpZEdhcD0iMWVtIiwKICAgICAgfmFsaWduSXRlbXM9ImNlbnRlciIsCiAgICAgICgpLAogICAgKTsKCiAgbGV0IGJ1dHRvbiA9CiAgICBtYWtlKAogICAgICB%2BZm9udFNpemU9IjFlbSIsCiAgICAgIH5ib3JkZXI9IjFweCBzb2xpZCB3aGl0ZSIsCiAgICAgIH5ib3JkZXJSYWRpdXM9IjAuNWVtIiwKICAgICAgfnBhZGRpbmc9IjAuNWVtIiwKICAgICAgKCksCiAgICApOwoKICBsZXQgbnVtYmVyID0gbWFrZSh%2BbWluV2lkdGg9IjJlbSIsIH50ZXh0QWxpZ249ImNlbnRlciIsICgpKTsKfTsKCm1vZHVsZSBDb3VudGVyID0gewogIFtAcmVhY3QuY29tcG9uZW50XQogIGxldCBtYWtlID0gKCkgPT4gewogICAgbGV0IChjb3VudGVyLCBzZXRDb3VudGVyKSA9IFJlYWN0LnVzZVN0YXRlKCgpID0%2BIDApOwoKICAgIDxkaXYgc3R5bGU9U3R5bGVzLnJvb3Q%2BCiAgICAgIDxidXR0b24gc3R5bGU9U3R5bGVzLmJ1dHRvbiBvbkNsaWNrPXtfZXZ0ID0%2BIHNldENvdW50ZXIodiA9PiB2IC0gMSl9PgogICAgICAgIHtSZWFjdC5zdHJpbmcoIi0iKX0KICAgICAgPC9idXR0b24%2BCiAgICAgIDxzcGFuIHN0eWxlPVN0eWxlcy5udW1iZXI%2BCiAgICAgICAge2NvdW50ZXIgfD4gSW50LnRvX3N0cmluZyB8PiBSZWFjdC5zdHJpbmd9CiAgICAgIDwvc3Bhbj4KICAgICAgPGJ1dHRvbiBzdHlsZT1TdHlsZXMuYnV0dG9uIG9uQ2xpY2s9e19ldnQgPT4gc2V0Q291bnRlcih2ID0%2BIHYgKyAxKX0%2BCiAgICAgICAge1JlYWN0LnN0cmluZygiKyIpfQogICAgICA8L2J1dHRvbj4KICAgIDwvZGl2PjsKICB9Owp9OwoKc3dpdGNoIChSZWFjdERPTS5xdWVyeVNlbGVjdG9yKCIjcHJldmlldyIpKSB7CnwgTm9uZSA9PiBKcy5sb2coIkZhaWxlZCB0byBzdGFydCBSZWFjdDogY291bGRuJ3QgZmluZCB0aGUgI3ByZXZpZXcgZWxlbWVudCIpCnwgU29tZShyb290KSA9PiBSZWFjdERPTS5yZW5kZXIoPENvdW50ZXIgLz4sIHJvb3QpCn07Cg%3D%3D&live=on) for the fully-styled Counter component.

-----

W00t! You are now empowered to use numbers in your OCaml programs.

## Exercises
Expand Down
17 changes: 17 additions & 0 deletions docs/order-confirmation/Index.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module App = {
let items: Order.t = [|Sandwich, Burger, Sandwich|];

[@react.component]
let make = () =>
<div>
<h1> {React.string("Order confirmation")} </h1>
<Order items />
</div>;
};

let node = ReactDOM.querySelector("#root");
switch (node) {
| None =>
Js.Console.error("Failed to start React: couldn't find the #root element")
| Some(root) => ReactDOM.render(<App />, root)
};
65 changes: 65 additions & 0 deletions docs/order-confirmation/Item.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// #region type-t
type t =
| Sandwich
| Burger;
// #endregion type-t

let _ = {
// #region to-price
let toPrice = t =>
switch (t) {
| Sandwich => 10.
| Burger => 15.
};
// #endregion to-price
toPrice(Sandwich) |> ignore;
};

// #region to-price-fun
let toPrice =
fun
| Sandwich => 10.
| Burger => 15.;
// #endregion to-price-fun

// #region to-emoji
let toEmoji =
fun
| Sandwich => {js|🥪|js}
| Burger => {js|🍔|js};
// #endregion to-emoji

// #region make
[@react.component]
let make = (~item: t) =>
<tr>
<td> {item |> toEmoji |> React.string} </td>
<td>
{item
|> toPrice
|> Js.Float.toFixedWithPrecision(~digits=2)
|> React.string}
</td>
</tr>;
// #endregion make

module AddHotdog = {
// #region hotdog
type t =
| Sandwich
| Burger
| Hotdog;

let toPrice =
fun
| Sandwich => 10.
| Burger => 15.
| Hotdog => 5.;

let toEmoji =
fun
| Sandwich => {js|🥪|js}
| Burger => {js|🍔|js}
| Hotdog => {js|🌭|js};
// #endregion hotdog
};
56 changes: 56 additions & 0 deletions docs/order-confirmation/Order.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// #region order
type t = array(Item.t);

[@react.component]
let make = (~items: t) => {
let total =
items |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);

<table>
<tbody>
{items |> Js.Array.map(item => <Item item />) |> React.array}
<tr>
<td> {React.string("Total")} </td>
<td>
{total |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string}
</td>
</tr>
</tbody>
</table>;
};
// #endregion order

let _ = {
let items = [||];
// #region mapi
items
|> Js.Array.mapi((item, index) =>
<Item key={"item-" ++ string_of_int(index)} item />
)
|> React.array
// #endregion mapi
|> ignore;
};

let _ = {
let items = [||];
// #region order-make-item-rows
let total =
items |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);

let itemRows: array(React.element) =
items |> Js.Array.map(item => <Item item />);

<table>
<tbody>
{itemRows |> React.array}
<tr>
<td> {React.string("Total")} </td>
<td>
{total |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string}
</td>
</tr>
</tbody>
</table>;
// #endregion order-make-item-rows
};
Loading

0 comments on commit 2dd370c

Please sign in to comment.