[Call for help #2] Test JSX v4

I’m not used to React Navigation now. It’s been a while to use. How about using module function?

module SomeLib = (
  T: {
    type props<'navigation, 'route> = {
      navigation: 'navigation,
      route: 'route,
    }
    let make: React.component<props<_>>
  },
) => {
  type navigation
  type route

  type screenProps = T.props<navigation, route>

  type screenComponent = React.component<screenProps>

  module Screen = {
    @react.component @module("some-lib")
    external make: (~component: screenComponent) => React.element = "Screen"
  }
}

module SomeComponent = {
@react.component
  let make = (~navigation, ~route) => {
    let _ = (navigation, route)
    React.string("Some component")
}}

module SomeComp = SomeLib(SomeComponent)

let render = () => <SomeComp.Screen component=SomeComponent.make />

EDIT: surely, you can use @react.component either.

Yes, it’s definitely a nominal type effect :smiley:

I noticed createRoot is scoped inside Experimental. It does not look like React considers this API to be experimental; should we move it outside of Experimental?

1 Like

Good point! ReactDOM apis need to be cleaned up since bindings are having peer dependences>=React@18.x. It needs to be separated to react-dom/client and server too.

Cleanup of the bindings for React 18 is already done in https://github.com/rescript-lang/rescript-react/pull/46 which will be merged soon (feedback welcome).

5 Likes

Using first-class modules with locally abstract types does not seem to work.

module Select = {
  @react.component
  let make = (
    type a key,
    ~model as module(T: T with type t = a and type key = key),
    ~selected: option<key>,
    ~onChange: option<key> => unit,
    ~items: array<a>,
  ) =>
  ...
}

yields the error:

  The type of this packed module contains variables:
module(T with type key = 'type-key with type t = 'type-a)

Thank you for reporting!
This seems not compiled in v3 either. Can you give me a self-contained example?

module Select = {
  @react.component
  let make = (
    type a key,
    ~model as module(T: T with type t = a and type key = key), // Unbound module type T
    ~selected: option<key>,
    ~onChange: option<key> => unit,
    ~items: array<a>,
  ) => {
    <div />
  }
}

Sorry, it just needs some module type T with types t and key. This compiles in v3:

module Select = {
  module type T = {
    type key
    type t
  }

  @react.component
  let make = (
    type a key,
    ~model as module(T: T with type t = a and type key = key),
    ~selected: option<key>,
    ~onChange: option<key> => unit,
    ~items: array<a>,
  ) => {
    <div />
  }
}

Thank you! I found what needs to be done. Let you know once it’s fixed.

2 Likes

Thank you for the suggestion! It does work, however, the API becomes rather unwieldy this way as you would have to apply that SomeLib functor separately to every screen component you would like to use.

With React Navigation, it would become extra unwieldy as the API is already quite complex and already requires functors now.

1 Like

@glennsl I’ve fixed for the implementation. Can you give me an interface too? If you can, I’ll check the interface usage.

1 Like

The interface just uses ordinary unification variables, so i think it should be fine. It’d be something like this:

module Select: {
  module type T = {
    type key
    type t
  }

  @react.component
  let make: (
    ~model: module(T with type t = 'a and type key = 'key),
    ~selected: option<'key>,
    ~onChange: option<'key> => unit,
    ~items: array<'a>,
  ) => React.element
}
1 Like

The PR to fix is on review. https://github.com/rescript-lang/syntax/pull/666

1 Like

It looks like there may be an issue when not using the transform and using the external keyword.

type props<'msg> = {msg: 'msg}
@module("@foo/bar")
external make: props<_> => React.element = "SomeComponent"

Gets compiled to

JsxRuntime.jsx(
  (function (prim) {
    return Bar.SomeComponent(prim);
  }),
  { msg: "test" }
)

instead of

JsxRuntime.jsx(Bar.SomeComponent, {
  msg: "test"
})

Thanks for reporting.
This code seems an interface. Can you give me a sample example that is compiled?
Confused. I’ll look into it.

For more context the external component I’m binding to uses forwardRef, which returns an “exotic” component that is not callable.

interface ExoticComponent<P = {}> {
  /**
   * **NOTE**: Exotic components are not callable.
   */
  (props: P): (ReactElement|null);
  readonly $$typeof: symbol;
}

What you really end up with is an object with a render method.

Is this anothet report? Do you want to bind a component which is implemented with forwardRef?

If it is, you can bind the component which is implemented with forwardRef:

@module("componentForwardRef") @react.component
  external make: (~x: string, ~ref: ReactDOM.Ref.currentDomRef) => React.element = "component"

In order to bind to the component without JSX transformation, I think this is the proper way:

type props<'msg> = {msg: 'msg}
@module("@foo/bar")
external make: React.component<props<'msg>> => React.element = "SomeComponent"

With JSX transformation

@module("@foo/bar") @react.component
external make: (~msg:'msg) => React.element = "SomeComponent"

Thanks @moondaddi, the following worked

type props<'msg> = {msg: 'msg}
@module("@foo/bar")
external make: React.component<props<'msg>> = "SomeComponent"
1 Like