1

I'm trying to define a ProductRow and ProductCategoryRow from Thinking in React.

productRow.re

let component = ReasonReact.statelessComponent("ProductRow");

let make = (~name, ~price, _children) => {
  ...component,
  render: (_self) => {
    <tr>
      <td>{ReasonReact.stringToElement(name)}</td>
      <td>{ReasonReact.stringToElement(price)}</td>
    </tr>
  }
};

productCategoryRow.re

let component = ReasonReact.statelessComponent("ProductCategoryRow");

let make = (~title: string, ~productRows, _children) => {
  ...component,
  render: (_self) => {
    <div>
        <th>{ReasonReact.stringToElement(title)}</th>
    </div>
  }
};

I believe that I need to map over the productRows, i.e. List of ProductRow's, with a function of: productRow => <td>productRow</td>.

How can I do that in this example?

Or, if I'm completely off the mark, please explain how I can achieve the above.

glennsl
  • 28,186
  • 12
  • 57
  • 75
Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384

1 Answers1

2

In the 'Thinking in React' page, the component nesting hierarchy is such that a ProductTable contains several ProductRows. We can model that in ReasonReact by mapping over an array of products and producing ProductRows as the output. E.g.:

type product = {name: string, price: float};

/* Purely for convenience */
let echo = ReasonReact.stringToElement;

module ProductRow = {
  let component = ReasonReact.statelessComponent("ProductRow");
  let make(~name, ~price, _) = {
    ...component,
    render: (_) => <tr>
      <td>{echo(name)}</td>
      <td>{price |> string_of_float |> echo}</td>
    </tr>
  };
};

module ProductTable = {
  let component = ReasonReact.statelessComponent("ProductTable");
  let make(~products, _) = {
    ...component,
    render: (_) => {
      let productRows = products
        /* Create a <ProductRow> from each input product in the array. */
        |> Array.map(({name, price}) => <ProductRow key=name name price />)
        /* Convert an array of elements into an element. */
        |> ReasonReact.arrayToElement;

      <table>
        <thead>
          <tr> <th>{echo("Name")}</th> <th>{echo("Price")}</th> </tr>
        </thead>
        /* JSX can happily accept an element created from an array */
        <tbody>productRows</tbody>
      </table>
    }
  };
};

/* The input products. */
let products = [|
  {name: "Football", price: 49.99},
  {name: "Baseball", price: 9.99},
  {name: "Basketball", price: 29.99}
|];

ReactDOMRe.renderToElementWithId(<ProductTable products />, "index");
Yawar
  • 11,272
  • 4
  • 48
  • 80
  • Thanks, Yawar! Is the choice of 'name' as the key a good idea since names could have duplicates, as I understand? – Kevin Meredith Jan 30 '18 at 10:39
  • @KevinMeredith that's a good point, ideally each product in stock should have a SKU which should serve as a unique key. – Yawar Jan 30 '18 at 14:37
  • I'm seeing `Unbound record field name` in the lambda of `Array.map`. Any idea, why, please? – Kevin Meredith Feb 10 '18 at 21:07
  • 1
    @KevinMeredith usually this means your record is defined in a different module than where you're using it. You can tell Reason where by prefixing one of the fields with the module name, e.g. `Array.map(({MyModule.name, price}) => ...)` – Yawar Feb 10 '18 at 21:36
  • In order to use `MyModule.name`, must I define a `module` as you have? Or is it sufficient to create `MyModule.re`, which contains both a `component` and `make` `let`'s? – Kevin Meredith Feb 11 '18 at 14:57
  • 1
    @KevinMeredith if you're seeing an `unbound record field` error, that means you either haven't defined the record type, or have defined it in another module. So you would prefix one of the fields with that module name in the latter case. Anyway you can get more help in the Discord (I'm temporarily locked out but there are a lot of folks who can help). – Yawar Feb 11 '18 at 18:23
  • I posted https://stackoverflow.com/questions/48779363/unbound-record-field-name-in-reason-component. Thanks for your help! – Kevin Meredith Feb 14 '18 at 03:53