Reason React Context types


#1

Hi @ all,

I am currently struggling a bit with the context api of reason react.
Here is my implementation of a context provider, which provides a function to all children:

let context =
  React.createContext(
    (
      ~typ: Notification.errorType,
      ~title: string,
      ~message: string,
      ~timeout: option(int),
    ) =>
    ()
  );

let makeProps =
    (
      ~value:
         (
           ~typ: Notification.errorType,
           ~title: string,
           ~message: string,
           ~timeout: option(int)
         ) =>
         unit,
      ~children,
      (),
    ) => {
  "children": children,
  "value": value,
};

let make = React.Context.provider(context);

It is working completely fine, but I find it a bit wierd, that I have to type the function two times.
I mean the final implementation of the function also is in another file (because it is setting state in another component)…

<NotificationContext
    value={(
      ~typ: Notification.errorType,
      ~title: string,
      ~message: string,
      ~timeout: option(int),
    ) => {
      setMessages(currentMessages => {
        Array.append(
          currentMessages,
          [|
            {
              "id": Js.Date.make()->Js.Date.toUTCString,
              "typ": typ,
              "title": title,
              "message": message,
              "timeout": timeout,
            },
          |],
        )
      })
    }}>
    ...

…and here I am typing the parameters again :frowning:
So if I want to, make the message optional, I would have to change the type three times.

I am just asking myself if there needs to be an initial value for the React.createContext(), if it is also not allowed, to leave out the value of the (in my case) <NotificationContext> call.
But when leaving the React.createContext() empty, it expects to recieve a unit to be passed, which then leads to the question, why I need to type it in makeProps when the value is obviously inferred.

Sorry…I am just a bit confused and wanted to share my struggles.

Is there something I am missing or is this just this cumbersome at the moment?

Thank you,
Torben


#2

You actually don’t have to type your function every time, and since BuckleScript 7 you would probably better use records instead of Js.t objects. For example you would define it like this :

module Message = {
    type t = {
      id: int,
      typ: Notification.errorType,
      title: Title.t,
      message: Content.t,
      timeout: Timeout.t,
    };
};
 

That would lead to something like that:

  type t =
    (
      ~typ: Notification.errorType,
      ~title: Title.t,
      ~message: Content.t,
      ~timeout: Timeout.t
    ) =>
    unit;

  let context: React.Context.t(t) =
    React.createContext(
      (
        ~typ as _,
        ~title as _,
        ~message as _,
        ~timeout as _,
      ) =>
      ()
    );

  [@react.component]
  let make = (~value, ~children) =>
    React.createElement(
      React.Context.provider(context),
      {"value": value, "children": children},
    );

And you would use it this way:

<NotificationContext
    value={(
      ~typ,
      ~title,
      ~message,
      ~timeout,
    ) => {
      setMessages(currentMessages => {
        Belt.Array.concat(
          currentMessages,
          [|
            {
              id: Js.Date.make()->Js.Date.toUTCString,
              typ,
              title,
              message,
              timeout,
            },
          |],
        )
      })
    }}>
    ...

This way, if you want to change the type of message, you just have to change it once.

If you happen to append messages a lot, it would probably be better to use a list which has a very cheap prepend function.
Does all this make more sense?