How to handle variants in jsx <select>?

I am implementing a component with <select> and would like to use variants to toggle a switch.
I wonder which is the most practical way to do this.

Currently I have to convert text values into variants and viceversa.

  type view = ConcreteView | DurockView | WoodView

  @react.component
  let make = () => {
    let (view, setView) = React.useState(_ => WoodView)

<main>
     <select
            id="roof_material"
            name="roof_material"
            value={switch view {
            | ConcreteView => "concrete"
            | DurockView => "durock"
            | WoodView => "wood"
            }}
            onChange={e => {
              switch (e->ReactEvent.Form.target)["value"] {
              | "concrete" => setView(_ => ConcreteView)
              | "durock" => setView(_ => DurockView)
              | "wood" => setView(_ => WoodView)
              | _ => setView(_ => WoodView)
              }
            }} >
            <option value="wood"> {`Madera`->React.string} </option>
            <option value="concrete"> {`Concreto`->React.string} </option>
            <option value="durock"> {`Durock`->React.string} </option>
   </select>
  <div className="mx-auto max-w-4xl">
        {switch view {
        | ConcreteView => <Concrete items />
        | DurockView => <Durock items />
        | WoodView => <Wood items />
        }}
      </div>
</main>

I wonder if RescriptReactRouter would help here, but I haven’t found any docs on it.
I also use select for product configurations, so it’s not limited to views.

If you want to conver polyvariants to strings, check @deriving(jsConverter)

Although, in modern ReScript polyvariants are already backed by strings so maybe just Obj.magic may do the trick.

Although this doesn’t help at the moment, there is an open issue about subtyping polyvars and strings. If it makes it into the language, it will make this even easier. https://github.com/rescript-lang/rescript-compiler/issues/4591

2 Likes

Using @deriving(jsConverter) as @TomiS says, you get something like:

@deriving(jsConverter)
type view = [#ConcreteView | #DurockView | #WoodView]

// You can also use variant as you've done first but it implies convert to string anytime you use them :
// type view = ConcreteView | DurockView | WoodView
// <select value={viewToJs(view)->Belt.Int.toString}>

@react.component
let make = () => {
  let (view, setView) = React.useState(_ => #WoodView)

  <main>
    <select
      id="roof_material"
      name="roof_material"
      value={viewToJs(view)} // with variant: 
      onChange={e =>
        setView(_ =>
          viewFromJs((e->ReactEvent.Form.target)["value"])->Belt.Option.getExn
        )}>
      <option value={viewToJs(#ConcreteView)}>
        {`Madera`->React.string}
      </option>
      <option value={viewToJs(#DurockView)}>
        {`Concreto`->React.string}
      </option>
      <option value={viewToJs(#WoodView)}> {`Durock`->React.string} </option>
    </select>
    <div className="mx-auto max-w-4xl">
      {switch view {
    | #ConcreteView => <Concrete items />
    | #DurockView => <Durock items />
    | #WoodView => <Wood items />
      }}
    </div>
  </main>
}
1 Like

Thanks a lot for landing this example.
I didn’t know that @deriving(jsConverter) will make viewToJs and viewFromJs. This is very helpful!