How to make a component which is the same of another comp with a few props injected?

Hello! I’m using Material UI with bs-material-ui and scratching my head because of a simple issue I cannot solve.

There’s a MaterialUi.Slide React component that makes a thing appear by sliding from an edge of the screen. It has a direction property:

"direction": option<[#Down | #Left | #Right | #Up]>

If not set, the content slides from the top.

There’s another component MaterialUi.Dialog which might be provided with a transition component. It defines how the dialog would appear. And if I pass MaterialUi.Slide.make as the dialog prop value, the dialog works fine, and slides from the top on appear.

But not I want to make it appear from the bottom. That is, I need a React component which “curries” the direction prop to the #Up value. Here’s example JS snippet from the Material UI docs:

const Transition = React.forwardRef(function Transition(props, ref) {
  return <Slide direction="up" ref={ref} {...props} />;
});

Can I make the same with ReScript? I’m trying the following:

let slideUp = React.forwardRef((props, ref_) => {
  /*let props' = Js.Obj.assign({ "direction": #Up, "ref": ref_ }, props)*/
  React.createElement(Slide.make, props)
})

No luck:

[rescript]   This expression's type contains type variables that can't be generalized:
[rescript]   React.component<
[rescript]   {
[rescript]     "_in": option<bool>,
[rescript]     "children": option<'_weak1>,
[rescript]     "direction": option<[#Down | #Left | #Right | #Up]>,
[rescript]     "id": option<string>,
[rescript]     "key": option<string>,
[rescript]     "onEnter": option<ReactEvent.Synthetic.t => unit>,
[rescript]     "onEntered": option<ReactEvent.Synthetic.t => unit>,
[rescript]     "onEntering": option<ReactEvent.Synthetic.t => unit>,
[rescript]     "onExit": option<ReactEvent.Synthetic.t => unit>,
[rescript]     "onExited": option<ReactEvent.Synthetic.t => unit>,
[rescript]     "onExiting": option<ReactEvent.Synthetic.t => unit>,
[rescript]     "ref": option<ReactDOMRe.domRef>,
[rescript]     "style": option<ReactDOMRe.Style.t>,
[rescript]     "timeout": option<MaterialUi.Slide.Timeout.t>
[rescript]   ,},
[rescript] >
[rescript]   
[rescript]   This happens when the type system senses there's a mutation/side-effect,
[rescript]   in combination with a polymorphic value.
[rescript]   Using or annotating that value usually solves it.

I really don’t understand what it wants :slight_smile: I suspect it’s something around "children": option<'_weak1>, not sure though. How can I access the prop type of the Slide if that could be used for the annotation?

One option would be creating a 100% wrapper around Slide but it would require replicating all its props and I’m trying to avoid that. Any ideas?

I know this is somewhat of a non-answer, but I’d definitely start replicating the props I need, and leave the other props.

The reason is that you start getting into a world where you have a type structure. At your defintion of slideUp this is more work, but at every callsite, you’d enjoy having the type checker figure out what to pass. As you need more and more of the component, you can extend your helper, which will break the code at every callsite, making sure you cover whatever is needed.

A more workaround-like solution is probably reachable by exploiting React.cloneElement (not tested at all):

let slideUp = React.forwardRef((props, ref) => {
  let s = Slide.make(props)
  React.cloneElement(s, {"direction": #Up, "ref": ref })
}

However, I’d much prefer working in a solution that establish a typed world personally, even with the extra work. I.e., I’d just do something along the lines of

let slide = (~direction) => <Slide direction />
let slideUp = slide(~direction=#Up)

And then extend the slide function with more optional parameters as they are needed.

Others might have more or better insights.

1 Like

Thanks for replying! Unfortunately, the workaround does not compile with the same error. I see I have to annotate the slideUp, but I can’t understand where can I “steal” the original and already generated type definition…

Perhaps you’re right, the simplest and most correct thing to do is to copy-paste the props to create a fair component wrapper. I just wanted a quick solution because I’m decorating a foreign JS component that is immediately passed to another foreign JS component and their prop-protocol is something I’m nothing to do with.