Focusing an input field in a React component – getting type error trying to create a ref

489 Views Asked by At

I have a React component that contains a text <input> element. When the component is mounted, I want the text cursor to be set in the input field, i.e. I want the text input element to have the focus.

In a “traditional” JavaScript React component, I would get the input field's DOM element through a ref and then call its focus method.

I've read this documentation that explains how to use refs in Reason-React: https://github.com/reasonml/reason-react/blob/master/docs/react-ref.md

Alas, the code sample contained on this page is for refs of custom component, it only mentions that it also works on React DOM elements.

So I've tried to convert the example code to a React DOM element, here is what I've tried so far:

type state = {
  text: string,
  inputElement: ref(option(Dom.element))
};

let valueFromEvent = (evt) : string => (
  evt
  |> ReactEventRe.Form.target
  |> ReactDOMRe.domElementToObj
)##value;

let component = ReasonReact.reducerComponent("EditTodoField");

let setInputElement = (theRef, {ReasonReact.state}) =>
  state.inputElement := Js.Nullable.to_opt(theRef);

let make = (~initialText, ~onSubmit, _) => {
  ...component,
  initialState: () => {text: initialText, inputElement: ref(None)},
  reducer: (newText, state) => ReasonReact.Update({...state, text: newText}),
  render: ({state: {text}, reduce, handle}) =>
    <input
      value=text
      _type="text"
      ref=(handle(setInputElement))
      placeholder="Todo description"
      onChange=(reduce((evt) => valueFromEvent(evt)))
      onKeyDown=(
        (evt) =>
          if (ReactEventRe.Keyboard.key(evt) == "Enter") {
            onSubmit(text);
            (reduce(() => ""))()
          } else if (ReactEventRe.Keyboard.key(evt) == "Escape") {
            onSubmit(initialText);
            (reduce(() => ""))()
          }
      )
    />
};

The error message I get is this:

We've found a bug for you!
/Users/pahund/git/todo-list-reason-react/src/EditTodoField.re 21:11-35

19 ┆ value=text
20 ┆ _type="text"
21 ┆ ref=(handle(setInputElement))
22 ┆ placeholder="Todo description"
23 ┆ onChange=(reduce((evt) => valueFromEvent(evt)))

This has type:
  ReasonReact.Callback.t(Js.Nullable.t(Dom.element)) (defined as
    (Js.Nullable.t(Dom.element)) => unit)
But somewhere wanted:
  (Js.null(Dom.element)) => unit

The incompatible parts:
  Js.Nullable.t(Dom.element) (defined as Js.nullable(Dom.element))
  vs
  Js.null(Dom.element)

I know that the problem probably lies within how I define the type of the state at the beginning of the code, it's different for DOM elements than it is for custom components.

What would be the correct type definition here to fix the bug?

The full project can be found here on GitHub: https://github.com/pahund/todo-list-reason-react/tree/ref-problem

1

There are 1 best solutions below

0
On BEST ANSWER

I think your reason-react dependency is out of date. refs were changed from Js.null(Dom.element) to Js.nullable(Dom.element) in 0.3.0. See https://github.com/reasonml/reason-react/blob/master/HISTORY.md#030

If for some reason you can't or refuse to upgrade, you can just use Js.Null.to_opt instead though :)

(Also, if you do upgrade, you can use Js.toOption as a nice shortcut in place of Js.Nullable.to_opt)