Promo Component 
Your work so far has left a deep impression on Madame Jellobutter, so much so that she even sends you on an all-expenses paid trip to an OCaml Hackathon. As soon as you arrive, you make several new friends, and you even meet MonadicFanatic1984 in person![1] You’re extremely psyched to start applying the things you’re learning during the hackathon.
RR utility module 
While having lunch during the hackathon, fellow attendee FunctorPunk[2] suggests that you create a dedicated module for your ReasonReact-related helper functions. Add a new file RR.re:
/** Get string value from the given event's target */
let getValueFromEvent = (evt): string => React.Event.Form.target(evt)##value;
/** Alias for [React.string] */
let s = React.string;
/** Render a list of [React.element]s */
let list = list => list |> Stdlib.Array.of_list |> React.array;
/** Render a float as currency */
let currency = value => value |> Js.Float.toFixed(~digits=2) |> React.string;/** Get string value from the given event's target */
let getValueFromEvent = (evt): string => React.Event.Form.target(evt)##value;
/** Alias for [React.string] */
let s = React.string;
/** Render a list of [React.element]s */
let list = list => list |> Stdlib.Array.of_list |> React.array;
/** Render a float as currency */
let currency = value => value |> Js.Float.toFixed(~digits=2) |> React.string;These are all functions we used before, but now they’re in one handy place and also have documentation comments.
Documentation comment markup language 
In a couple of the documentation comments, you see square brackets ([]):
/** Alias for [React.string] *//** Alias for [React.string] */Whatever is inside the brackets gets rendered in a monospace source code font. This is one example of the markup language used inside documentation comments.
Promo component 
Add new file Promo.re:
[@react.component]
let make = () => {
  let (code, setCode) = React.useState(() => "");
  <form onSubmit={evt => evt |> React.Event.Form.preventDefault}>
    <input
      value=code
      onChange={evt => setCode(_ => RR.getValueFromEvent(evt))}
    />
    {RR.s("Todo: Discount value or error")}
  </form>;
};[@react.component]
let make = () => {
  let (code, setCode) = React.useState(() => "");
  <form onSubmit={evt => evt |> React.Event.Form.preventDefault}>
    <input
      value=code
      onChange={evt => setCode(_ => RR.getValueFromEvent(evt))}
    />
    {RR.s("Todo: Discount value or error")}
  </form>;
};For now, it just shows an input that allows the user to enter a promo code.
React.useReducer 
Another neat trick you learn from FunctorPunk is that you can substitute React.useState with React.useReducer[3] when defining state variables:
[@react.component]
let make = () => {
  let (code, dispatch) =
    React.useReducer((_state, newState) => newState, "");
  <form onSubmit={evt => evt |> React.Event.Form.preventDefault}>
    <input
      value=code
      onChange={evt => dispatch(RR.getValueFromEvent(evt))}
    />
    {RR.s("Todo: Discount value or error")}
  </form>;
};[@react.component]
let make = () => {
  let (code, dispatch) =
    React.useReducer((_state, newState) => newState, "");
  <form onSubmit={evt => evt |> React.Event.Form.preventDefault}>
    <input
      value=code
      onChange={evt => dispatch(RR.getValueFromEvent(evt))}
    />
    {RR.s("Todo: Discount value or error")}
  </form>;
};The advantage of this is that you can update state by passing a value instead of a callback. To make it even nicer, we can also rename dispatch to setCode and use function chaining inside the onChange callback:
[@react.component]
let make = () => {
  let (code, setCode) =
    React.useReducer((_state, newState) => newState, "");
  <form onSubmit={evt => evt |> React.Event.Form.preventDefault}>
    <input
      value=code
      onChange={evt => evt |> RR.getValueFromEvent |> setCode}
    />
    {RR.s("Todo: Discount value or error")}
  </form>;
};[@react.component]
let make = () => {
  let (code, setCode) =
    React.useReducer((_state, newState) => newState, "");
  <form onSubmit={evt => evt |> React.Event.Form.preventDefault}>
    <input
      value=code
      onChange={evt => evt |> RR.getValueFromEvent |> setCode}
    />
    {RR.s("Todo: Discount value or error")}
  </form>;
};RR.useStateValue helper function 
This state variable pattern can be made more convenient by adding a new helper function called RR.useStateValue:
/** Like [React.useState] but doesn't use callback functions */
let useStateValue = initial =>
  React.useReducer((_state, newState) => newState, initial);/** Like [React.useState] but doesn't use callback functions */
let useStateValue = initial =>
  React.useReducer((_state, newState) => newState, initial);The state setup code in Promo then becomes very simple:
let (code, setCode) = 
    React.useReducer((_state, newState) => newState, ""); 
let (code, setCode) = RR.useStateValue(""); let (code, setCode) = 
    React.useReducer((_state, newState) => newState, ""); 
let (code, setCode) = RR.useStateValue(""); Add Promo to Index 
The Promo component isn’t finished yet, but let’s add it to Index so that we can see how it looks as we’re building it:
[@react.component]
let make = () =>
  <div>
    <h1> {RR.s("Promo")} </h1>
    <Promo items date={Js.Date.fromString("2024-05-10T00:00")} />
    <h1> {RR.s("Order confirmation")} </h1>
    <Order items />
  </div>;[@react.component]
let make = () =>
  <div>
    <h1> {RR.s("Promo")} </h1>
    <Promo items date={Js.Date.fromString("2024-05-10T00:00")} />
    <h1> {RR.s("Order confirmation")} </h1>
    <Order items />
  </div>;We know that, sooner or later, we must pass items and date to Promo, so we just do it now. However, this breaks the build because Promo doesn’t yet define those props.
Temporarily ignore props 
We can quickfix it by adding the ~items and ~date props to Promo.make and then immediately ignoreing them in the body of the function:
[@react.component]
let make = (~items: list(Item.t), ~date: Js.Date.t) => {
  ignore(items);
  ignore(date);[@react.component]
let make = (~items: list(Item.t), ~date: Js.Date.t) => {
  ignore(items);
  ignore(date);A more concise way to temporarily ignore props is to use the as keyword in the function argument list:
[@react.component]
let make = (~items as _: list(Item.t), ~date as _: Js.Date.t) => {[@react.component]
let make = (~items as _: list(Item.t), ~date as _: Js.Date.t) => {Here we rename ~items to _, effectively ignoring it, and also do the same for ~date. Note that this renaming only extends to the scope of the function itself.
styled-ppx package 
During dinner, you meet a cool human named Dave who introduces you to styled-ppx, the package he authored to allow writing type-safe CSS inside OCaml. It’s inspired by Emotion, and also uses Emotion under the hood.
To install styled-ppx, open your terminal and run:
npm install @emotion/css
opam install styled-ppxnpm install @emotion/css
opam install styled-ppxAfter these commands finish, see what version of styled-ppx was installed by running opam list:
...
stdune                3.15.2      Dune's unstable standard library
stringext             1.6.0       Extra string functions for OCaml
styled-ppx            0.56.0      Type-safe styled components for ReScript and Melange
time_now              v0.16.0     Reports the current time
topkg                 1.0.7       The transitory OCaml software packager
......
stdune                3.15.2      Dune's unstable standard library
stringext             1.6.0       Extra string functions for OCaml
styled-ppx            0.56.0      Type-safe styled components for ReScript and Melange
time_now              v0.16.0     Reports the current time
topkg                 1.0.7       The transitory OCaml software packager
...Add "styled-ppx" {>= "0.56.0"} to the depends section of your .opam file. (Adjust the version based on what you see from ocaml list.)
To make sure that the version of @emotion/css you got from npm is compatible with the version of styled-ppx you got from opam, run
npm run check-npm-depsnpm run check-npm-depsYou should see some output like this:
Ok: opam package "reason-react.0.14.0" requires npm package: "react-dom" with constraint "^18.0.0", version installed: "18.2.0"
Ok: opam package "reason-react.0.14.0" requires npm package: "react" with constraint "^18.0.0", version installed: "18.2.0"
Ok: opam package "styled-ppx.0.56.0" requires npm package: "@emotion/css" with constraint ">=11.0.0", version installed: "11.11.2"Ok: opam package "reason-react.0.14.0" requires npm package: "react-dom" with constraint "^18.0.0", version installed: "18.2.0"
Ok: opam package "reason-react.0.14.0" requires npm package: "react" with constraint "^18.0.0", version installed: "18.2.0"
Ok: opam package "styled-ppx.0.56.0" requires npm package: "@emotion/css" with constraint ">=11.0.0", version installed: "11.11.2"TIP
Underneath the covers, npm run check-npm-deps is just running opam exec opam-check-npm-deps.
WARNING
The output from npm run check-npm-deps might not show anything for styled-ppx yet, that should be fixed soon.
Add styles to Promo 
Try adding a style to Promo’s form element:
<form
  className=[%cx {|display: flex; flex-direction: column;|}] 
  onSubmit={evt => evt |> React.Event.Form.preventDefault}><form
  className=[%cx {|display: flex; flex-direction: column;|}] 
  onSubmit={evt => evt |> React.Event.Form.preventDefault}>You’ll get a compilation error:
File "src/order-confirmation/Promo.re", line 6, characters 16-18:
6 |     className=[%cx {|display: flex; flex-direction: column;|}]
                    ^^
Error: Uninterpreted extension 'cx'.File "src/order-confirmation/Promo.re", line 6, characters 16-18:
6 |     className=[%cx {|display: flex; flex-direction: column;|}]
                    ^^
Error: Uninterpreted extension 'cx'.styled-ppx is installed, but it can’t be used until the libraries and preprocess/pps stanzas of your dune file have been updated:
(libraries reason-react melange-fest styled-ppx.emotion)
 (preprocess
  (pps melange.ppx reason-react-ppx styled-ppx))(libraries reason-react melange-fest styled-ppx.emotion)
 (preprocess
  (pps melange.ppx reason-react-ppx styled-ppx))For reference:
- Add styled-ppx.emotiontolibrariesto enable theCssandCssJsmodules
- Add styled-ppxtopreprocess/ppsto enable the%cxextension node
If you run npm run serve, you’ll see that the styles are now applied.
Promo.Style submodule 
Although you can use the %cx extension node inline within your JSX, it’s usually a good idea to put all of them in a Style submodule within your component module:
module Style = {
  let form = [%cx {|
    display: flex;
    flex-direction: column;
  |}];
  let input = [%cx
    {|
    font-family: monospace;
    text-transform: uppercase;
    |}
  ];
};module Style = {
  let form = [%cx {|
    display: flex;
    flex-direction: column;
  |}];
  let input = [%cx
    {|
    font-family: monospace;
    text-transform: uppercase;
    |}
  ];
};Then update your JSX:
<form
  className=Style.form
  onSubmit={evt => evt |> React.Event.Form.preventDefault}>
  <input
    className=Style.input
    value=code
    onChange={evt => evt |> RR.getValueFromEvent |> setCode}
  />
  {RR.s("Todo: Discount value or error")}
</form>;<form
  className=Style.form
  onSubmit={evt => evt |> React.Event.Form.preventDefault}>
  <input
    className=Style.input
    value=code
    onChange={evt => evt |> RR.getValueFromEvent |> setCode}
  />
  {RR.s("Todo: Discount value or error")}
</form>;Using a Style submodule encourages reuse of class names and tends to make your JSX more readable.
styled-ppx is type-safe 
The strings we pass to the %cx extension must be valid CSS and each property must have a valid value. To see this in action, change the value of the flex-direction property:
flex-direction: column; 
flex-direction: col; flex-direction: column; 
flex-direction: col; You’ll get this compilation error:
File "src/order-confirmation/Promo.re", lines 3-4, characters 37-43:
2 | ...................
3 | .................;
4 |     flex-direction: col.
Error: Property 'flex-direction' has an invalid value: 'col'File "src/order-confirmation/Promo.re", lines 3-4, characters 37-43:
2 | ...................
3 | .................;
4 |     flex-direction: col.
Error: Property 'flex-direction' has an invalid value: 'col'Introduce some other errors to your CSS code and see what happens.
Install vscode-styled-ppx extension 
Now that we’re writing our CSS inside our OCaml code, the CSS is no longer highlighted. VS Code users can resolve this by simply installing Dave’s vscode-styled-ppx extension.
Add submittedCode state variable 
Add a new state variable named submittedCode:
let (code, setCode) = RR.useStateValue("");
let (submittedCode, setSubmittedCode) = RR.useStateValue(None);
<form
  className=Style.form
  onSubmit={evt => {
    evt |> React.Event.Form.preventDefault;
    setSubmittedCode(Some(code));
  }}>
  <input
    className=Style.input
    value=code
    onChange={evt => {
      evt |> RR.getValueFromEvent |> setCode;
      setSubmittedCode(None);
    }}
  />
  {RR.s("Todo: Discount value or error")}
</form>;let (code, setCode) = RR.useStateValue("");
let (submittedCode, setSubmittedCode) = RR.useStateValue(None);
<form
  className=Style.form
  onSubmit={evt => {
    evt |> React.Event.Form.preventDefault;
    setSubmittedCode(Some(code));
  }}>
  <input
    className=Style.input
    value=code
    onChange={evt => {
      evt |> RR.getValueFromEvent |> setCode;
      setSubmittedCode(None);
    }}
  />
  {RR.s("Todo: Discount value or error")}
</form>;A breakdown:
- The initial value of submittedCodeisNone, meaning the user hasn’t yet submitted a promo code
- Pressing the Enterkey while editing the input submits the promo code, settingsubmittedCodetoSome(code)
- Editing the input cancels the submitted promo code by setting submittedCodeback toNone
Add discountFunction derived variable 
Add a discountFunction variable that derives its value from submittedCode and date:
let (submittedCode, setSubmittedCode) = RR.useStateValue(None);
let discountFunction =
  submittedCode
  |> Option.map(code => Discount.getDiscountFunction(code, date));let (submittedCode, setSubmittedCode) = RR.useStateValue(None);
let discountFunction =
  submittedCode
  |> Option.map(code => Discount.getDiscountFunction(code, date));We have to use Option.map since submittedCode’s type is option(string). Also, we must stop ignoring the date prop in order to use its value:
[@react.component]
let make = (~items as _: list(Item.t), ~date as _: Js.Date.t) => { 
let make = (~items as _: list(Item.t), ~date: Js.Date.t) => { [@react.component]
let make = (~items as _: list(Item.t), ~date as _: Js.Date.t) => { 
let make = (~items as _: list(Item.t), ~date: Js.Date.t) => { Render discountFunction 
Render discountFunction under the input:
<input /* ... */ />
{switch (discountFunction) {
 | None
 | Some(Ok(_)) => React.null
 | Some(Error(error)) =>
   <div className=Style.codeError>
     {let errorType =
        switch (error) {
        | Discount.InvalidCode => "Invalid"
        | ExpiredCode => "Expired"
        };
      {j|$errorType promo code|j} |> RR.s}
   </div>
 }}<input /* ... */ />
{switch (discountFunction) {
 | None
 | Some(Ok(_)) => React.null
 | Some(Error(error)) =>
   <div className=Style.codeError>
     {let errorType =
        switch (error) {
        | Discount.InvalidCode => "Invalid"
        | ExpiredCode => "Expired"
        };
      {j|$errorType promo code|j} |> RR.s}
   </div>
 }}We only render discountFunction when it’s in the error state.
We also need to add the codeError class name to the Style submodule, which will cause promo code error messages to be rendered in red:
let codeError = [%cx {|color: red|}];let codeError = [%cx {|color: red|}];Add discount derived variable 
Add a discount variable that derives its value from discountFunction and items:
let discountFunction =
  submittedCode
  |> Option.map(code => Discount.getDiscountFunction(code, date));
let discount =
  switch (discountFunction) {
  | None
  | Some(Error(_)) => None
  | Some(Ok(discountFunction)) => Some(discountFunction(items))
  };
let discountFunction =
  submittedCode
  |> Option.map(code => Discount.getDiscountFunction(code, date));
let discount =
  switch (discountFunction) {
  | None
  | Some(Error(_)) => None
  | Some(Ok(discountFunction)) => Some(discountFunction(items))
  };To use the value of the items prop, we must stop ignoring it:
[@react.component]
let make = (~items as _: list(Item.t), ~date: Js.Date.t) => { 
let make = (~items: list(Item.t), ~date: Js.Date.t) => { [@react.component]
let make = (~items as _: list(Item.t), ~date: Js.Date.t) => { 
let make = (~items: list(Item.t), ~date: Js.Date.t) => { Render discount 
Render discount under where we render discountFunction:
{switch (discountFunction) {
 | None
 | Some(Ok(_)) => React.null
 /* ... */
 }}
{switch (discount) {
 | None => React.null
 | Some(Ok(value)) => value |> Float.neg |> RR.currency
 | Some(Error(_code)) =>
   <div className=Style.discountError>
     {RR.s("Todo: discount error message")}
   </div>
 }}{switch (discountFunction) {
 | None
 | Some(Ok(_)) => React.null
 /* ... */
 }}
{switch (discount) {
 | None => React.null
 | Some(Ok(value)) => value |> Float.neg |> RR.currency
 | Some(Error(_code)) =>
   <div className=Style.discountError>
     {RR.s("Todo: discount error message")}
   </div>
 }}- When discountis of the formSome(Ok(value)), then the discount must exist and we show its value (with a minus sign in front to indicate that the amount will be subtracted from the total value).
- When discountis of the formSome(Error(code)), then an error occurred and we should render an error message based on the value ofcode(this is left as an exercise at the end of the chapter).
We also need to add discountError to the Style submodule, which will cause the discount error to be rendered in purple:
let discountError = [%cx {|color: purple|}];let discountError = [%cx {|color: purple|}];Use one switch expression 
The current rendering logic is unsatisfactory. On first glance, it looks as if discountFunction is rendered first, and then discount is rendered below it. But in reality, only one of the derived variables is ever rendered at a time. That suggests that we could use a single switch expression for the render logic. It’s helpful to list the four mutually-exclusive states we need to render:
| State | What to render | 
|---|---|
| No promo code submitted | Nothing | 
| Promo code invalid or expired | Error message, e.g. “Invalid promo code” | 
| Error applying discount | Error message, e.g. “Buy at least one more burger to enjoy this promotion” | 
| Discount | The value of the discount with a minus sign in front | 
One way to refactor to a single switch expression is to change the input of the switch expression to be the tuple (discountFunction, discount):
{switch (discountFunction, discount) {
 | (Some(_), Some(Ok(value))) => value |> Float.neg |> RR.currency
 | (Some(Error(error)), _) =>
   <div className=Style.codeError>
     {let errorType =
        switch (error) {
        | Discount.InvalidCode => "Invalid"
        | ExpiredCode => "Expired"
        };
      {j|$errorType promo code|j} |> RR.s}
   </div>
 | (_, Some(Error(_code))) =>
   <div className=Style.discountError>
     {RR.s("Todo: discount error message")}
   </div>
 | (None, None)
 | (Some(_), None)
 | (None, Some(_)) => React.null
 }}{switch (discountFunction, discount) {
 | (Some(_), Some(Ok(value))) => value |> Float.neg |> RR.currency
 | (Some(Error(error)), _) =>
   <div className=Style.codeError>
     {let errorType =
        switch (error) {
        | Discount.InvalidCode => "Invalid"
        | ExpiredCode => "Expired"
        };
      {j|$errorType promo code|j} |> RR.s}
   </div>
 | (_, Some(Error(_code))) =>
   <div className=Style.discountError>
     {RR.s("Todo: discount error message")}
   </div>
 | (None, None)
 | (Some(_), None)
 | (None, Some(_)) => React.null
 }}This works, but it’s a lot of pattern matching to render just four states.
Use a single discount derived variable 
We cannot have better rendering logic without first refactoring the derived variables. Replace discountFunction and discount with just a single derived variable discount:
let discount =
  switch (submittedCode) {
  | None => `NoSubmittedCode
  | Some(code) =>
    switch (Discount.getDiscountFunction(code, date)) {
    | Error(error) => `CodeError(error)
    | Ok(discountFunction) =>
      switch (discountFunction(items)) {
      | Error(error) => `DiscountError(error)
      | Ok(value) => `Discount(value)
      }
    }
  };let discount =
  switch (submittedCode) {
  | None => `NoSubmittedCode
  | Some(code) =>
    switch (Discount.getDiscountFunction(code, date)) {
    | Error(error) => `CodeError(error)
    | Ok(discountFunction) =>
      switch (discountFunction(items)) {
      | Error(error) => `DiscountError(error)
      | Ok(value) => `Discount(value)
      }
    }
  };Here we used three nested switch expressions to explicitly map the values from all the inputs to the four mutually-exclusive states we want to render. The flow of data goes like this:
- If submittedCodeisNone, return`NoSubmittedCode
- If submittedCodeisSome(code), then invokeDiscount.getDiscountFunction(code, date)- If the result of Discount.getDiscountFunction(code, date)isError(error), then return`CodeError(error)
- If the result of Discount.getDiscountFunction(code, date)isOk(discountFunction), then invokediscountFunction(items)- If the result of discountFunction(items)isError(error), then return`DiscountError(error)
- If the result of discountFunction(items)isOk(value), then return`Discount(value)
 
- If the result of 
 
- If the result of 
Because we use polymorphic variant tags, we don’t need to define a new type. If you hover over the discount variable in your editor you’ll see that its inferred type is:
[> `CodeError(Discount.error)
 | `Discount(float)
 | `DiscountError([> `MissingSandwichTypes
                   | `NeedMegaBurger
                   | `NeedOneBurger
                   | `NeedTwoBurgers ])
 | `NoSubmittedCode ][> `CodeError(Discount.error)
 | `Discount(float)
 | `DiscountError([> `MissingSandwichTypes
                   | `NeedMegaBurger
                   | `NeedOneBurger
                   | `NeedTwoBurgers ])
 | `NoSubmittedCode ]Render discount polymorphic variant 
Now change the switch expression in your render logic to take the discount polymorphic variant as its only input:
{switch (discount) {
 | `NoSubmittedCode => React.null
 | `Discount(discount) => discount |> Float.neg |> RR.currency
 | `CodeError(error) =>
   <div className=Style.codeError>
     {let errorType =
        switch (error) {
        | Discount.InvalidCode => "Invalid"
        | ExpiredCode => "Expired"
        };
      {j|$errorType promo code|j} |> RR.s}
   </div>
 | `DiscountError(_code) =>
   <div className=Style.discountError>
     {RR.s("Todo: discount error message")}
   </div>
 }}{switch (discount) {
 | `NoSubmittedCode => React.null
 | `Discount(discount) => discount |> Float.neg |> RR.currency
 | `CodeError(error) =>
   <div className=Style.codeError>
     {let errorType =
        switch (error) {
        | Discount.InvalidCode => "Invalid"
        | ExpiredCode => "Expired"
        };
      {j|$errorType promo code|j} |> RR.s}
   </div>
 | `DiscountError(_code) =>
   <div className=Style.discountError>
     {RR.s("Todo: discount error message")}
   </div>
 }}The pattern matching is now more concise and more readable, and it is still type-safe. Try commenting out the  `NoSubmittedCode branch and you’ll get a compilation error:
File "src/order-confirmation/Promo.re", lines 52-68, characters 4-7:
52 | ....{switch (discount) {
53 |      //  | `NoSubmittedCode => React.null
54 |      | `Discount(discount) => discount |> Float.neg |> RR.currency
55 |      | `CodeError(error) =>
56 |        <div className=Style.codeError>
...
65 |        <div className=Style.discountError>
66 |          {RR.s("Todo: discount error message")}
67 |        </div>
68 |      }}
Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
`NoSubmittedCodeFile "src/order-confirmation/Promo.re", lines 52-68, characters 4-7:
52 | ....{switch (discount) {
53 |      //  | `NoSubmittedCode => React.null
54 |      | `Discount(discount) => discount |> Float.neg |> RR.currency
55 |      | `CodeError(error) =>
56 |        <div className=Style.codeError>
...
65 |        <div className=Style.discountError>
66 |          {RR.s("Todo: discount error message")}
67 |        </div>
68 |      }}
Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
`NoSubmittedCodeMuy bueno! You’ve created a Promo component that can be used to submit promo codes and see the discounts they produce, along with any errors that might occur. In the next chapter, we’ll integrate this Promo component into your Order component.
Overview 
- Documentation comments have a markup language
- Use React.useReducerto create state variables that can be set without callback functions- You should create a helper function to make this pattern more convenient to use
 
- Temporarily ignore function arguments using the askeyword, e.g.~items as _will cause the~itemsargument to be ignored
- The styled-ppxpackage allows you to write type-safe CSS inside your OCaml code- You must add some extra values to the librariesandpreprocess/ppsfields of yourdunefile to usestyled-ppx
- Write your CSS inside the %cxextension node
- It’s a good idea to group the class names you create using %cxinside a submodule of your component module (you can name itStyleor something similar)
- Install the vscode-styled-ppxVS Code extension to get syntax highlighting for CSS written inside%cx
 
- You must add some extra values to the 
- Run opam exec opam-check-npm-depsto check that your NPM dependencies are compatible with your Melange packages
- A good strategy for rendering mutually-exclusive states is to create a single switch expression that maps your inputs to each state, where each state is represented by a different variant tag.
Exercises 
1. Render the different error messages for the  `DiscountError branch of Promo’s switch expression. Here’s what message should be shown for each tag:
| Variant tag | Message | 
|---|---|
|  `NeedOneBurger | Buy at least 1 more burger to enjoy this promotion | 
|  `NeedTwoBurgers | Buy at least 1 more burgers to enjoy this promotion | 
|  `NeedMegaBurger | Buy a burger with every topping to enjoy this promotion | 
|  `MissingSandwichTypes | Buy every sandwich to enjoy this promotion | 
Solution
{switch (discount) {
 /* ... */
 | `DiscountError(code) =>
   let buyWhat =
     switch (code) {
     | `NeedOneBurger => "at least 1 more burger"
     | `NeedTwoBurgers => "at least 2 burgers"
     | `NeedMegaBurger => "a burger with every topping"
     | `MissingSandwichTypes => "every sandwich"
     };
   <div className=Style.discountError>
     {RR.s({j|Buy $buyWhat to enjoy this promotion|j})}
   </div>;
 }}{switch (discount) {
 /* ... */
 | `DiscountError(code) =>
   let buyWhat =
     switch (code) {
     | `NeedOneBurger => "at least 1 more burger"
     | `NeedTwoBurgers => "at least 2 burgers"
     | `NeedMegaBurger => "a burger with every topping"
     | `MissingSandwichTypes => "every sandwich"
     };
   <div className=Style.discountError>
     {RR.s({j|Buy $buyWhat to enjoy this promotion|j})}
   </div>;
 }}2. Refactor the Order component to use styled-ppx and your new RR helper function module.
Hint
styled-ppx supports nested selectors.
Solution
After refactoring, Order.re might look something like this:
type t = list(Item.t);
module OrderItem = {
  module Style = {
    let item = [%cx {|border-top: 1px solid lightgray;|}];
    let emoji = [%cx {|font-size: 2em;|}];
    let price = [%cx {|text-align: right;|}];
  };
  [@react.component]
  let make = (~item: Item.t) =>
    <tr className=Style.item>
      <td className=Style.emoji> {item |> Item.toEmoji |> RR.s} </td>
      <td className=Style.price> {item |> Item.toPrice |> RR.currency} </td>
    </tr>;
};
module Style = {
  let order = [%cx
    {|
    border-collapse: collapse;
    td {
      padding: 0.5em;
    }
    |}
  ];
  let total = [%cx
    {|
    border-top: 1px solid gray;
    font-weight: bold;
    text-align: right;
    |}
  ];
};
[@react.component]
let make = (~items: t) => {
  let total =
    items
    |> ListLabels.fold_left(~init=0., ~f=(acc, order) =>
         acc +. Item.toPrice(order)
       );
  <table className=Style.order>
    <tbody>
      {items
       |> List.mapi((index, item) =>
            <OrderItem key={"item-" ++ string_of_int(index)} item />
          )
       |> RR.list}
      <tr className=Style.total>
        <td> {RR.s("Total")} </td>
        <td> {total |> RR.currency} </td>
      </tr>
    </tbody>
  </table>;
};type t = list(Item.t);
module OrderItem = {
  module Style = {
    let item = [%cx {|border-top: 1px solid lightgray;|}];
    let emoji = [%cx {|font-size: 2em;|}];
    let price = [%cx {|text-align: right;|}];
  };
  [@react.component]
  let make = (~item: Item.t) =>
    <tr className=Style.item>
      <td className=Style.emoji> {item |> Item.toEmoji |> RR.s} </td>
      <td className=Style.price> {item |> Item.toPrice |> RR.currency} </td>
    </tr>;
};
module Style = {
  let order = [%cx
    {|
    border-collapse: collapse;
    td {
      padding: 0.5em;
    }
    |}
  ];
  let total = [%cx
    {|
    border-top: 1px solid gray;
    font-weight: bold;
    text-align: right;
    |}
  ];
};
[@react.component]
let make = (~items: t) => {
  let total =
    items
    |> ListLabels.fold_left(~init=0., ~f=(acc, order) =>
         acc +. Item.toPrice(order)
       );
  <table className=Style.order>
    <tbody>
      {items
       |> List.mapi((index, item) =>
            <OrderItem key={"item-" ++ string_of_int(index)} item />
          )
       |> RR.list}
      <tr className=Style.total>
        <td> {RR.s("Total")} </td>
        <td> {total |> RR.currency} </td>
      </tr>
    </tbody>
  </table>;
};You should do some cleanup:
- Delete Formatmodule, since it has been replaced byRR.
- Delete order-item.module.cssandorder.module.cssfiles, since they are no longer used.
- Delete the melange.emit/runtime_depsstanza from yourdunefile, since there are no more.cssfiles to copy into the build directory.
3. Right now, Item.toPrice is a nondeterministic function—it returns a different price for a turducken sandwich depending on the day of the week[4]. This makes writing some discount tests more complicated than necessary, and could cause more problems down the road. Refactor Item.toPrice by adding a labeled argument ~date, then:
- Fix the resulting compilation errors by adding ~datearguments to functions higher on the call chain
- Simplify the tests that have this comment: // Don't use hardcoded value since Item.toPrice is non-deterministic
- You might break the tests in DiscountTests.GetDiscount, but don’t worry about that for now. We’ll fix those tests in the following exercise.
Hint
Let the compilation errors guide your refactoring.
Hint
Feel free to make liberal use of partial application.
Solution
Most of the changes are fairly mechanical, consisting of adding a ~date argument up the function call chain until you get to Index.App.make.
There is at least one point of interest—you can opt to not give Discount.getFreeBurgers a ~date argument, since it only computes prices of burgers, which don’t fluctuate with time. But then you must use partial application in Discount.getDiscountFunction:
let getDiscountFunction = (code, date) => {
  let month = date |> Js.Date.getMonth;
  let dayOfMonth = date |> Js.Date.getDate;
  switch (code |> Js.String.toUpperCase) {
  | "FREE" when month == 4.0 => Ok(getFreeBurgers)
  | "HALF" when month == 4.0 && dayOfMonth == 28.0 => Ok(getHalfOff(~date))
  | "HALF" when month == 10.0 && dayOfMonth == 3.0 =>
    Ok(getSandwichHalfOff(~date))
  | "FREE"
  | "HALF" => Error(ExpiredCode)
  | _ => Error(InvalidCode)
  };
};let getDiscountFunction = (code, date) => {
  let month = date |> Js.Date.getMonth;
  let dayOfMonth = date |> Js.Date.getDate;
  switch (code |> Js.String.toUpperCase) {
  | "FREE" when month == 4.0 => Ok(getFreeBurgers)
  | "HALF" when month == 4.0 && dayOfMonth == 28.0 => Ok(getHalfOff(~date))
  | "HALF" when month == 10.0 && dayOfMonth == 3.0 =>
    Ok(getSandwichHalfOff(~date))
  | "FREE"
  | "HALF" => Error(ExpiredCode)
  | _ => Error(InvalidCode)
  };
};The indirect consequence of this is that tests like this fail:
test(
  "HALF promo code returns getHalfOff on May 28 but not other days of May",
  () => {
  for (dayOfMonth in 1 to 31) {
    let date =
      Js.Date.makeWithYMD(
        ~year=2024.,
        ~month=4.0,
        ~date=float_of_int(dayOfMonth),
      );
    expect
    |> deepEqual(
         Discount.getDiscountFunction("HALF", date),
         dayOfMonth == 28
           ? Ok(Discount.getHalfOff(~date)) : Error(ExpiredCode),
       );
  }
});test(
  "HALF promo code returns getHalfOff on May 28 but not other days of May",
  () => {
  for (dayOfMonth in 1 to 31) {
    let date =
      Js.Date.makeWithYMD(
        ~year=2024.,
        ~month=4.0,
        ~date=float_of_int(dayOfMonth),
      );
    expect
    |> deepEqual(
         Discount.getDiscountFunction("HALF", date),
         dayOfMonth == 28
           ? Ok(Discount.getHalfOff(~date)) : Error(ExpiredCode),
       );
  }
});Comparing two partially-applied functions for strict equality will always fail, because they are two newly-created objects. See this playground snippet for a concise demonstration of this.
The next exercise will explore how we can make the tests pass without adding an extraneous ~date argument to Discount.getFreeBurgers.
4. Fix the tests in DiscountTests.GetDiscount that were broken by your refactoring in the previous exercise.
Hint
Make a helper function that is similar to Discount.getDiscountFunction but can return something like this: Ok((`FreeBurgers, getFreeBurgers))
Hint
Use Result.map, fst, and snd.
Solution
Inside Discount, define a helper function called getDiscountPair, and redefine getDiscountFunction using it:
let getDiscountPair = (code, date) => {
  let month = date |> Js.Date.getMonth;
  let dayOfMonth = date |> Js.Date.getDate;
  switch (code |> Js.String.toUpperCase) {
  | "FREE" when month == 4.0 => Ok((`FreeBurgers, getFreeBurgers))
  | "HALF" when month == 4.0 && dayOfMonth == 28.0 =>
    Ok((`HalfOff, getHalfOff(~date)))
  | "HALF" when month == 10.0 && dayOfMonth == 3.0 =>
    Ok((`SandwichHalfOff, getSandwichHalfOff(~date)))
  | "FREE"
  | "HALF" => Error(ExpiredCode)
  | _ => Error(InvalidCode)
  };
};
let getDiscountFunction = (code, date) =>
  getDiscountPair(code, date) |> Result.map(snd);let getDiscountPair = (code, date) => {
  let month = date |> Js.Date.getMonth;
  let dayOfMonth = date |> Js.Date.getDate;
  switch (code |> Js.String.toUpperCase) {
  | "FREE" when month == 4.0 => Ok((`FreeBurgers, getFreeBurgers))
  | "HALF" when month == 4.0 && dayOfMonth == 28.0 =>
    Ok((`HalfOff, getHalfOff(~date)))
  | "HALF" when month == 10.0 && dayOfMonth == 3.0 =>
    Ok((`SandwichHalfOff, getSandwichHalfOff(~date)))
  | "FREE"
  | "HALF" => Error(ExpiredCode)
  | _ => Error(InvalidCode)
  };
};
let getDiscountFunction = (code, date) =>
  getDiscountPair(code, date) |> Result.map(snd);The type signature of getDiscountFunction remains unchanged, returning a function encased in Ok when it succeeds. In contrast, getDiscountPair returns a 2-tuple encased in Ok:
- The first element of the tuple is a variant tag indicating which function was returned, e.g. `FreeBurger,`HalfOff, etc.
- The second element of the tuple is the discount function
The variant tag serves to give test code something to compare against:
module GetDiscount = {
  let getDiscountFunction = (code, date) =>
    Discount.getDiscountPair(code, date) |> Result.map(fst);
  // ...
  test(
    "HALF promo code returns getHalfOff on May 28 but not other days of May",
    () => {
    for (dayOfMonth in 1 to 31) {
      let date =
        Js.Date.makeWithYMD(
          ~year=2024.,
          ~month=4.0,
          ~date=float_of_int(dayOfMonth),
        );
      expect
      |> deepEqual(
           getDiscountFunction("HALF", date),
           dayOfMonth == 28 ? Ok(`HalfOff) : Error(ExpiredCode),
         );
    }
  });
  // ...
};module GetDiscount = {
  let getDiscountFunction = (code, date) =>
    Discount.getDiscountPair(code, date) |> Result.map(fst);
  // ...
  test(
    "HALF promo code returns getHalfOff on May 28 but not other days of May",
    () => {
    for (dayOfMonth in 1 to 31) {
      let date =
        Js.Date.makeWithYMD(
          ~year=2024.,
          ~month=4.0,
          ~date=float_of_int(dayOfMonth),
        );
      expect
      |> deepEqual(
           getDiscountFunction("HALF", date),
           dayOfMonth == 28 ? Ok(`HalfOff) : Error(ExpiredCode),
         );
    }
  });
  // ...
};Recall that the runtime representation of a variant tag without arguments is just a string. They can be compared using Fest.deepEqual without any surprises.
Run npm run test to verify that all tests are passing again.
View source code and demo for this chapter.
- MonadicFanatic1984 in real life is a genetically-modified gorilla who escaped from a mad scientist’s laboratory. He used to be in charge of developing the mad scientist’s website until he escaped because he wanted to migrate the website to Melange but the mad scientist didn’t approve. ↩︎ 
- FunctorPunk is a cybernetically-enhanced wombat who escaped from a mad scientist’s laboratory (no, not the same one). He escaped because FREEDOM. ↩︎ 
- React.useReduceris the ReasonReact binding for React’s useReducer hook. ↩︎
- Recall the long-running Turducken Tuesdays promotion, in which turducken sandwiches are half off every Tuesday. ↩︎