Search code examples
reasonoptional-argumentsreason-react

How to pass onClick handler from parent to child components through optional labeled arguments


I want to pass an event handler down a series of components from parent to child. Here is a working example:

Index.re

ReactDOMRe.renderToElementWithId(
  <App myHandler={_evt => Js.log("my handler clicked")} />,
  "root",
);

App.re

let component = ReasonReact.statelessComponent(__MODULE__);

let make = (~myHandler, _children) => {
  ...component,
  render: _self => <MyComponent myHandler />,
};

MyComponent.re

let component = ReasonReact.statelessComponent(__MODULE__);

let make = (~myHandler, _children) => {
  ...component,
  render: _self =>
    <div onClick=myHandler> {ReasonReact.string("click me")} </div>,
}

As this code is now, the myHandler argument is required at each component usage otherwise we get error:

This call is missing an argument of type (~myHandler: ReactEvent.Mouse.t => unit)

Labeled arguments can be made optional by adding a =? in the function declaration like so:

let component = ReasonReact.statelessComponent(__MODULE__);

let make = (~myHandler=?, _children) => {
  ...component,
  render: _self =>
    <div onClick=myHandler> {ReasonReact.string("click me")} </div>,
};

However this gives a compilation error:

  5 │   ...component,
  6 │   render: _self =>
  7 │     <div onClick=myHandler> {ReasonReact.string("click me")} </div>,
  8 │ };

  This has type:
    option('a)
  But somewhere wanted:
    ReactEvent.Mouse.t => unit

I thought that perhaps the compiler might need a hint. So then I tried explicitly adding that type to the function declaration like this:

let component = ReasonReact.statelessComponent(__MODULE__);

let make = (~myHandler: ReactEvent.Mouse.t => unit=?, _children) => {
  ...component,
  render: _self =>
    <div onClick=myHandler> {ReasonReact.string("click me")} </div>,
};

But then the error flips on me and gives me:

  4 │ let make = (~myHandler: ReactEvent.Mouse.t => unit=?, _children) => {
  5 │   ...component,
  6 │   render: _self =>

  This pattern matches values of type
    ReactEvent.Mouse.t => unit
  but a pattern was expected which matches values of type
    option('a)

And now I'm all confused.


Solution

  • Just to provide a proper answer, since it seems unlikely that this will get closed as a duplicate

    Optional arguments are turned into options on the callee's side of things. Otherwise, how would you represent the absence of the argument. There are no nulls, remember, so nullability must be expressed explicitly.

    myHandler is therefore an option(ReactEvent.Mouse.t => unit). onClick is of course also an optional argument which will be turned into an option, but since this is, normally, done automatically we can't just feed it an option directly.

    Fortunately, someone already thought of this scenario and added a syntactic construct to be able to pass options explicitly as optional arguments. Just add a ? before the expression passed to the argument, and you should be good:

    <div onClick=?myHandler> {ReasonReact.string("click me")} </div>,