[Call for help #2] Test JSX v4

Since the post of the test request [Call for help] Test the React JSX v4, many feedbacks and suggestions were brought up in the thread. I appreciate it all. Thanks to your help, we’ve fixed and improved the JSX v4 now. It is published on npm, you can download it now.

  • rescript@10.1.0-alpha.2
  • @rescript/react@0.11.0.-alpha.1

Also, the example project I posted earlier is updated with dependencies, and added more examples since then.

I’d like to call for your help with the test with the latest compiler and react binding on your project. Any issues, suggestions, and questions about migration are welcome.

Additionally, we’ve updated the JSX v4 spec documentation. https://github.com/rescript-lang/syntax/blob/master/cli/JSXV4.md

5 Likes

I’ve tested with those versions and have not encountered any issues.
Which JSX configs do you want feedback on? I’ve currently tried the following:

  "jsx": {
    "version": 4,
    "mode": "classic"
  },
2 Likes

Fantastic! Thank you for your time.

Can you try "mode": "automatic" too?

Why are we requiring React 18.2 in @rescript/react / why is 18.1 or 18.0 not sufficient? (The reason I am asking is that React Native is currently still on 18.1.)

I think 18.0 would be fine.

We can set 18.x in a package json

1 Like

Just realized that the context API can now be used as follows:

let context = React.createContext("example")

module Provider = {
  type props = React.Context.props<string>
  let make = React.Context.provider(context)
}

<Provider value="something">
  <SomeComponent />
</Provider>
6 Likes

I have discovered a problem in representing APIs that take a React component with a certain props shape as an argument. (Or, to be more precise, with using @react.component for the components to pass to such an API.) For example, React Navigation screens.

So, in JSX v3, something like this works:

module SomeLib = {
  type navigation
  type route

  type screenProps = {"navigation": navigation, "route": 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 as _: SomeLib.navigation, ~route as _: SomeLib.route) =>
    React.string("Some component")
}

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

but in JSX v4, changing screenProps to a record type, this does not work anymore because of nominal typing:

module SomeLib = {
  type navigation
  type route

  type screenProps = {navigation: navigation, route: 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 as _: SomeLib.navigation, ~route as _: SomeLib.route) =>
    React.string("Some component")
}

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

It gives the error:

This has type:
  SomeComponent.props<SomeLib.navigation, SomeLib.route> => React.element
Somewhere wanted:
  SomeLib.screenComponent (defined as SomeLib.screenProps => Jsx.element)

The incompatible parts:
  SomeComponent.props<SomeLib.navigation, SomeLib.route> vs
  SomeLib.screenProps

So in order to be able to use the API, I am forced to avoid the @react.component attribute and instead define my component like this:

module SomeComponent = {
  type props = SomeLib.screenProps
  let make = ({navigation: _, route: _}: props) => React.string("Some component")
}
2 Likes

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