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.
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 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")
}
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?
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.
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 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.
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
}