How to deal with type parameter in module

Hello,
I wanted to write bindings for react-spring library. I found this thread but I stuck miserably with Rescript basic syntax.
I took the solution, rewrote it to newer bindings syntax. I found what @deriving(abstract) means in Rescript documentation but I’m a bit lost about how I should use the type parameter in a module.
It’s here:

module Props = {
  type t<'a> = Js.t<'a>;

  external fromStyle: ReactDOM.Style.t => t<'a> = "%identity";
};

/**
 The spring itself
 */
@deriving(abstract)
type t<'source, 'target> = {
  @optional
  config: Config.t,
  @optional
  from: Props.t<'source>,
  @optional @as("to")
  toValue: Props.t<'target>,

@module("react-spring/web.cjs")
    external useSpring: (unit => t<'from, 'toValue>) => ('props, (. t<'from, 'toValue>) => unit, 'stop) = "useSpring";

I thought it’s clear, but here:

let (props, setProps, stopProps) = useSpring(() => t(~config, ~from={x: 10}, ~toValue={x: 0}, ()))

I have an error because the compiler doesn’t know type {x: 10}.
If I define type from = {x: int} it still would be an error because it wants Props.t instead of from.
Props module is not module signature and to be honest, I don’t know to specialize it.
I know I refer to react-related thread, but my main goal here is to understand Rescript syntax for declaring more advanced types and bindings.
I hope you can help me :sweat_smile:

source and target are not the same type?

Hi @mishaszu

Looks like your Props.t is a JS object, so if you change your from and to values to {"x": 10} and {"x": 0} then I suspect this will compile.

I’m not familiar with React Spring, but the API looks like it might be quite complicated to model in ReScript.

Just to offer another perspective, if strict typing of the React Spring API is not critical for you, you may like to consider a simpler strategy, for example:

module ReactSpring = {
  type styles

  @module("react-spring") @scope("default")
  external useSpring: {..} => styles = "useSpring"
}

Example usage:

let styles = ReactSpring.useSpring({"opacity": 0})

Adding the callback version of useSpring

module ReactSpring = {
  type styles

  module Api = {
    type t

    @send external start: (t, {..}) => unit = "start"
    @send external stop: t => unit = "stop"
  }

  @module("react-spring") @scope("default")
  external useSpring: {..} => styles = "useSpring"

  @module("react-spring") @scope("default")
  external useSpringWithCallback: (unit => {..}) => (styles, Api.t) = "useSpring"
}

Example usage:

let (styles, api) = ReactSpring.useSpringWithCallback(() => {"opacity": 0})
api->ReactSpring.Api.start({"opacity": 1})
api->ReactSpring.Api.stop

I noticed the React Spring docs had a complex example in JS:

const styles = useSpring({
  to: async (next, cancel) => {
    await next({ opacity: 1, color: '#ffaaee' })
    await next({ opacity: 0, color: 'rgb(14,26,19)' })
  },
  from: { opacity: 0, color: 'red' },
})

For this I’d just use %raw(), for example:

let makeComplexOptions: unit => {..} = %raw(`
  function makeComplexOptions() {
    return {
      to: async (next, cancel) => {
        await next({ opacity: 1, color: '#ffaaee' })
        await next({ opacity: 0, color: 'rgb(14,26,19)' })
      },
      from: { opacity: 0, color: 'red' },
    }
  }
`)

Example usage:

let styles = ReactSpring.useSpring(makeComplexOptions())

And that function could accept some typed arguments as well.

The nice idea behind this approach is that it really only takes a few minutes to write bindings and you can be productive quite quickly, and if the React Spring API changes, then updating these bindings are likely to be straightforward.

I’m still a ReScript learner, but this approach has helped me to be productive when working with complex/dynamic JS APIs in ReScript.

1 Like

@kevanstannard that’s what I needed. I picked react-spring because indeed it has complex API (for example several useTransition definitions for different props), so it seems like good material for experiments.
I exactly wanted to achieve that productivity level. I’m preparing to convince my client to switch to Rescript and I don’t want to write exhausting bindings for every single library even when a use case is very simple.
Using JS objects and %raw seems like a great workaround.

Just a single thing to clarify.
Are {..} and Js.t<'a> signatures for the same thing (ie. not specified JS object)?

I don’t like open objects, but there isn’t currently a better solution than @deriving(abstract) (or the similar @obj) to strongly type binding objects with optional fields. And that’s quite annoying. ReScript 10 will have a solution but there isn’t a build available to try yet.

At this point, yes. The Js.t wrapper is no longer necessary and is only there for compatibility now.

2 Likes