2

I don't understand the way make work:

let component = ReasonReact.reducerComponent("Greeting");

let make = (~name, _children) => {
  ...component,
  initialState: () => 0, /* here, state is an `int` */
  render: (self) => {
    let greeting =
      "Hello " ++ name ++ ". You've clicked the button " ++ string_of_int(self.state) ++ " time(s)!";
    <div>{ReasonReact.stringToElement(greeting)}</div>
  }
};

As far as I understand, make will be called every time the <Greeting> component is used in a parent component render method, so it will be called multiple times.

But this also means the component record will create multiple times the initialState function right?

I don't understand how it makes sense to assign the initialState some function every time we create the React element, while it will only be called when the element gets mounted and will have no effect on updates.

I take initialState as an example, but could also say the same for other lifecycle callbacks too.

Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
  • If your initial state doesn't depend on your initial props, you can lift it out: `let initialState = () => 0; let make = (~name, _children) => { ...component, initialState, render: ... };` – Yawar Feb 22 '18 at 01:05
  • sure I can do that but when it depends on initial props I still don't see the value of recreating that function everytime :) not really looking for perf workarounds but more to understand the design decision behind this – Sebastien Lorber Feb 22 '18 at 08:41
  • Ah, sorry. So, since `make` is called on every render, `initialState` needs to be lazily-evaluated (what you'd model as a call-by-name in Scala). This is because it should be evaluated only on the first `make` call, not after that. They simply made a choice to use a function for this. It's also possible to model as an OCaml `lazy` value ( https://caml.inria.fr/pub/docs/manual-ocaml/libref/Lazy.html ), but it comes to more or less the same thing in the compiled output. – Yawar Feb 22 '18 at 16:27
  • Ok, by-name Scala parameters talk to me. So basically you say that the initialState function is there, but will actually be created only when it will be called? So multiple lazy variables might be created but a single one will lead to a function creation? – Sebastien Lorber Feb 22 '18 at 16:40
  • Correct, I don't see a way around that with the current design... – Yawar Feb 22 '18 at 18:36

1 Answers1

6

As far as I understand, make will be called every time the <Greeting> component is used in a parent component render method, so it will be called multiple times.

Yes, make is called for every render.

In this example, make Inside is printed in the console every time the button is pressed –which causes a new render of the Inside component.

I was curious to understand better why this was happening, so sharing it here in case someone else finds it interesting. The way the implementation works today is approximately as follows:

  • <Greeting name="John" /> is transformed into ReasonReact.element(Greeting.make(~name="John", [||])) by the Reason ppx. You can find the implementation in the main Reason repo.
  • In that statement, Greeting.make is the make function you are making reference to, the same function that each ReasonReact component has to define.
  • ReasonReact.element is where the magic happens. This function will call createElement the following way (source code):
createElement(
  component.reactClassInternal,
  ~props={"key": key, "ref": ref, "reasonProps": element},
  [||]
)
  • The element passed as the prop reasonProps is actually the full component "template" that is returned by make (see the assignment a few lines above).
  • component.reactClassInternal points to a React component that I will be calling WiringComponent for simplicity. What this WiringComponent does in essence is to declare all React lifecycle methods and implement them by just delegating the actual behavior to the functions that come from the "template" declared with make. These functions are picked from the reasonProps prop that is passed to this WiringComponent. You can see the implementation of this component here.

So, even if make is called once for every render as you mention, in reality what is being rendered is something like <WiringComponent reasonProps=Greeting.make(...) />. Then, when WiringComponent.getInitialState is called (just once per component instance, as usual), it will delegate that call to Greeting.initialState.

But this also means the component record will create multiple times the initialState function right?

That's what the code seems to be doing. Solving this doesn't seem trivial to me. Given that labelled arguments are being used to model the components props, there is no way to memoize make without giving up the type safety, as there can be many "flavors" of this function (one for each component).

Javier Chávarri
  • 1,605
  • 11
  • 21
  • thanks for the explainations. Not sure what you mean by the "flavors". What I'd like to understand is the design decisions behind this make function. Couldn't we declare the functions like `initialState` in another place so that it would only be created once per component instance? – Sebastien Lorber Feb 22 '18 at 10:32
  • Again, not a contributor so take this with a grain of salt (i'm also curious to know what the creators of ReasonReact would have to say). What I meant with "flavors" is that each component has different prop types. If we combine that with the decision of implementing props as labelled arguments, then it becomes really hard (impossible?) to have a "ReactComponent" record that holds all that information. How would the type of the `render` function look like? – Javier Chávarri Feb 22 '18 at 13:52
  • Unfortunately I might not know enough Ocaml yet to understand what you mean :) will try to see if someone can help me understand in Discord – Sebastien Lorber Feb 22 '18 at 16:40
  • @SebastienLorber you can define the function elsewhere and reference it inside the component. – thangngoc89 Feb 22 '18 at 17:27
  • that's what Yawar said but it won't work if the initialState need to read props – Sebastien Lorber Feb 22 '18 at 17:45