ReScript, React and spread props - it's now possible!
Tags: rescript, react, jsx
Published: 2023-02-19 10:21:03

[ReScript 10.1](https://rescript-lang.org/blog/release-10-1) was recently released, bringing a new version of the built in JSX transform in ReScript. 

The new JSX transform brings a bunch of new capabilities, and one of those are that you can now do "props spreading". 

This post will quickly outline how you can use the new JSX version to bind to APIs that utilize props spreading. We'll assume you know what props spreading is, but if you don't, quickly read through the [official React documentation](https://reactjs.org/docs/jsx-in-depth.html#spread-attributes).

> More information about the new release, and JSX in ReScript:
- [The section in JSX v4 in the official blog post.
](https://rescript-lang.org/blog/release-10-1#new-jsx-v4-syntax)
- [The official documentation on JSX in ReScript.](https://rescript-lang.org/docs/react/latest/elements-and-jsx)

## Example of using props spreading in bindings - binding `useFocus` from `react-aria`
We'll show how to utilize props spreading in ReScript and React by creating bindings to an actual API that uses props spreading, and porting the official JavaScript example code to ReScript from the documentation for that API.

We're going to bind to [`useFocus`](https://react-spectrum.adobe.com/react-aria/useFocus.html) from [`react-aria`](https://react-spectrum.adobe.com/react-aria).

You're encouraged to skim the [`useFocus` documentation page](https://react-spectrum.adobe.com/react-aria/useFocus.html) before continuing.

## Setting up the bindings
First, let's set up our bindings for `useFocus` from `react-aria`.

Reading the documentation, we see both what the function returns, and the config it takes: [https://react-spectrum.adobe.com/react-aria/useFocus.html#usage](https://react-spectrum.adobe.com/react-aria/useFocus.html#usage
)

Writing the return and input types in ReScript from that documentation page looks like this:
```rescript
type useFocusConfig = {
  onFocus?: JsxEvent.Focus.t => unit,
  onBlur?: JsxEvent.Focus.t => unit,
  onFocusChange?: bool => unit,
}

type useFocusReturn = {focusProps: JsxDOM.domProps}
```
Notice 2 things here:

- JsxEvent.Focus.t is the equivalent of FocusEvent in TypeScript.
- The return type holds one field only, focusProps. That's typed as a JsxDOM.domProps because it's a bag of props that you should apply on the JSX DOM element where you want the focus handlers from useFocus. This is important and is what enables the props spread. More about this further down.

Let's tie together the types with a binding to the hook:
```rescript
@module("react-aria") external useFocus: useFocusConfig => useFocusReturn = "useFocus"
```

The finished binding looks like this:
```rescript
// We're saving this in the file ReactAria.res
type useFocusConfig = {
  onFocus?: JsxEvent.Focus.t => unit,
  onBlur?: JsxEvent.Focus.t => unit,
  onFocusChange?: bool => unit,
}

type useFocusReturn = {focusProps: JsxDOM.domProps}

@module("react-aria") external useFocus: useFocusConfig => useFocusReturn = "useFocus"
```

## Porting the example JavaScript code to ReScript
There! We have the types and the binding to the hook. Let's see if we can implement the same example that the react-aria documentation has. Here's the example, in regular JavaScript, as it's shown in the documentation:
```javascript
import {useFocus} from 'react-aria';

function Example() {
  let [events, setEvents] = React.useState([]);
  let { focusProps } = useFocus({
    onFocus: (e) =>
      setEvents(
        (events) => [...events, 'focus']
      ),
    onBlur: (e) =>
      setEvents(
        (events) => [...events, 'blur']
      ),
    onFocusChange: (isFocused) =>
      setEvents(
        (events) => [
          ...events,
          `focus change: ${isFocused}`
        ]
      )
  });

  return (
    <>
      <label htmlFor="example">Example</label>
      <input
        {...focusProps}
        id="example"
      />
      <ul
        style={{
          maxHeight: '200px',
          overflow: 'auto'
        }}
      >
        {events.map((e, i) => <li key={i}>{e}</li>)}
      </ul>
    </>
  );
}
```
Let's implement it in ReScript using our new binding:
```rescript
module Example = {
  @react.component
  let make = () => {
    let (events, setEvents) = React.useState(() => [])
    // We put the `useFocus` binding code in a `ReactAria.res` file.
    let {focusProps} = ReactAria.useFocus({
      onFocus: _ => setEvents(events => events->Array.copy->Array.concat(["focus"])),
      onBlur: _ => setEvents(events => events->Array.copy->Array.concat(["blur"])),
      onFocusChange: isFocused =>
        setEvents(events =>
          events->Array.copy->Array.concat([`focus change: ${isFocused ? "true" : "false"}`])
        ),
    })

    <>
      <label htmlFor="example"> {"Example"->React.string} </label>
      <input {...focusProps} id="example" />
      <ul style={ReactDOM.Style.make(~maxHeight="200px", ~overflow="auto", ())}>
        {events
        ->Array.mapWithIndex((e, i) => <li key={i->Int.toString}> {e->React.string} </li>)
        ->React.array}
      </ul>
    </>
  }
}
```
A few things to notice about the ReScript code above:

- We moved the binding code to a separate file `ReactAria.res`, and access our binding via `ReactAria.useFocus`. This is common practice, grouping your bindings in a separate file when it makes sense.
- It uses [ReScript Core](https://github.com/rescript-association/rescript-core) for the `Array` functions.
- Also notice that there's not a single type annotation in here, but the code is 100% sound/type safe thanks to ReScript's great inference.

As you can see, just like in the JavaScript code, we spread `{...focusProps}` on the `<input />`. This works because focusProps has the type `JsxDOM.domProps`, and that's the same type that `<input />` expects its props to have.

This is key to why this works - it's essentially the equivalent of spreading props on a record:
```rescript
let inputProps: JsxDOM.domProps = {...focusProps, id: "example"}
```

## Wrapping up
Summing up this quick post - whenever you're binding to something that wants you to use spread props on JSX DOM elements, type that as `JsxDom.domProps` so it matches what JSX DOM elements expect for props.

That's it! Here's a few additional links if you're interested in diving deeper:

- [Spreading in records](https://rescript-lang.org/docs/manual/latest/record#immutable-update)
- [Main docs about JSX in ReScript](https://rescript-lang.org/docs/react/latest/elements-and-jsx)
- [Migration guide from JSX v3 to JSX v4](https://rescript-lang.org/docs/react/latest/migrate-react)
- [React documentation on spread props](https://reactjs.org/docs/jsx-in-depth.html#spread-attributes)