Migrate from JSX v3
JSX v4 introduces a new idiomatic record-based representation of components which is incompatible with v3. Because of this, either the entire project or dependencies need to be compiled in V4 mode, or some compatibility features need to be used to mix V3 and V4 in the same project. This page describes how to migrate from v3 to v4.
Configuration
Remove the existing JSX configuration from bsconfig.json
:
JSON{
"reason": { "react-jsx": 3 }
}
Then add the new JSX configuration:
JSON{
"jsx": { "version": 4 }
}
Note JSX v4 requires the rescript compiler 10.1 or higher, and rescript-react version 0.11 or higher. In addition, react version 18.0 is required.
Classic and Automatic Mode
Classic mode is the default and generates calls to React.createElement
just as with V3.
JSON{
"jsx": { "version": 4, "mode": "classic" }
}
Automatic mode is an experimental mode that generate calls to _jsx
functions (similar to TypeScript's react-jsx
mode)
JSON{
"jsx": { "version": 4, "mode": "automatic" }
}
File-level config
The top-level attribute @@jsxConfig
is used to update the jsx
config for the rest of the file (or until the next config update). Only the values mentioned are updated, the others are left unchanged.
RES@@jsxConfig({ version: 4, mode: "automatic" })
module Wrapper = {
module R1 = {
@react.component // V4 and new _jsx transform
let make = () => body
}
@@jsxConfig({ version: 4, mode: "classic" })
module R2 = {
@react.component // V4 with `React.createElement`
let make = () => body
}
}
@@jsxConfig({ version: 3 })
@react.component // V3
let make = () => body
v3 compatible mode
JSX v3 is still available with the latest version of compiler and rescript-react.
JSON{
"jsx": { "version": 3, "v3-dependencies": ["rescript-relay"] },
"bsc-flags": ["-open ReactV3"]
}
To build certain dependencies in V3 compatibility mode, whatever the version used in the root project, use "v3-dependencies"
. The listed dependencies will be built-in V3 mode, and in addition -open ReactV3
is added to the compiler options.
Migration of V3 components
Some components in existing projects are written in a way that is dependent on the v3 internal representation. Here are a few examples of how to convert them to v4.
makeProps
does not exist in v4
Rewrite this:
RES// V3
module M = {
@obj external makeProps: (~msg: 'msg, ~key: string=?, unit) => {"msg": 'msg} = ""
let make = (~msg) => <div> {React.string(msg)} </div>
}
To this:
RES// V4
module M = {
type props<'msg> = {msg: 'msg}
let make = props => <div> {React.string(props.msg)} </div>
}
React.Context
Rewrite this:
RESmodule Context = {
let context = React.createContext(() => ())
module Provider = {
let provider = React.Context.provider(context)
@react.component
let make = (~value, ~children) => {
React.createElement(provider, {"value": value, "children": children}) // Error
}
}
}
To this:
RESmodule Context = {
let context = React.createContext(() => ())
module Provider = {
let make = React.Context.provider(context)
}
}
React.forwardRef (Discouraged)
Rewrite this:
RESmodule FancyInput = {
@react.component
let make = React.forwardRef((
~className=?,
~children,
ref_, // argument
) =>
<div>
<input
type_="text"
?className
ref=?{ref_->Js.Nullable.toOption->Belt.Option.map(ReactDOM.Ref.domRef)}
/>
children
</div>
)
}
@react.component
let make = () => {
let input = React.useRef(Js.Nullable.null)
<div>
<FancyInput ref=input> // prop
<button onClick> {React.string("Click to focus")} </button>
</FancyInput>
</div>
}
To this: In v3, there is an inconsistency between ref
as prop and ref_
as argument. With JSX V4, ref
is only allowed as an argument.
RESmodule FancyInput = {
@react.component
let make = React.forwardRef((
~className=?,
~children,
ref, // only `ref` is allowed
) =>
<div>
<input
type_="text"
?className
ref=?{ref->Js.Nullable.toOption->Belt.Option.map(ReactDOM.Ref.domRef)}
/>
children
</div>
)
}
@react.component
let make = () => {
let input = React.useRef(Js.Nullable.null)
<div>
<FancyInput ref=input>
<button onClick> {React.string("Click to focus")} </button>
</FancyInput>
</div>
}
Mangling the prop name
The prop name was mangled automatically in v3, such as _open
becomes open
in the generated js code. This is no longer the case in v4 because the internal representation is changed to the record instead object. If you need to mangle the prop name, you can use the @as
annotation.
Rewrite this:
RESmodule Comp = {
@react.component
let make = (~_open, ~_type) =>
<Modal _open _type>
<Description />
</Modal>
}
To this:
RESmodule Comp = {
@react.component
let make =
(@as("open") ~_open, @as("type") ~_type) =>
<Modal _open _type>
<Description />
</Modal>
}
Bindings to JS components with optional props
Previously, you could wrap optional props with an explicit option
when writing bindings to JS components. This approach functioned only due to an implementation detail of the ppx in JSX 3; it's not how to correctly write bindings to a function with optional arguments.
Rewrite this:
RESmodule Button = {
@module("./Button") @react.component
external make: (~text: option<string>=?) => React.element = "default"
}
To this:
RESmodule Button = {
@module("./Button") @react.component
external make: (~text: string=?) => React.element = "default"
}