Handling nullable for parameter of @external make binding - HeadlessUI Combobox

Hi,

I am trying to bind HeadlessUI’s Combobox but am having an issue when the nullable prop is set to true as the onChange function will have a null passed in and crash the client.

My current binding is

  @module("@headlessui/react") @react.component
  external make: (
    ~\"as": React.element=?,
    ~disabled: bool=?,
    ~value: 'a=?,
    ~defaultValue: 'a=?,
    ~by: string=?,
    ~onChange: _ => unit=?,
    ~name: string=?,
    ~nullable: bool=?,
    ~multiple: bool=?,
    ~className: string=?,
    ~children: comboboxState => React.element=?,
  ) => React.element = "Combobox"

I’ve tried

@return(nullable)
~onChange: _ => unit=?

Currently my workaround in the calling module is

  let onChange = (selected: Js.Nullable.t<ComboBox.opt>) => {
    if Js.Nullable.isNullable(selected) {
      setSelected(_ => "")
    } else {
      Js.Nullable.iter(selected, (. selected) => setSelected(_ => selected.value))
    }
  } 

This logic would have to be applied to the displayValue function in Combobox.Input as well. I’m trying to avoid handling null | undefined at every calling component by seeing if there is a way to bind the parameter onChange to respect @return(null_to_opt) or @return(nullable). I’ve seen this done in external function bindings but never in a prop. Thanks for any input!

AFAIK you can’t use @return annotations inside props or types, you have to use the Nullable.t type here, but there’s a more idiomatic way to handle them starting from v11:

let onChange = (selected: Nullable.t<ComboBox.opt>) =>
  setSelected(_ =>
    switch selected {
    | Null | Undefined => ""
    | Value({value}) => value
    }
  )

PS. In theory, if you don’t want to handle the null or undefined case everywhere, the easiest solution would be to define two bindings, one where nullable is false and the other one where it’s set to true and change the types of other props accordingly, but for some reason, defining fixed default don’t seem to work with JSX v4 right now, it might get fixed in the future.

2 Likes