-1

I'm writing a form in ReasonReact. I used reducerComponent and a record as state. Let's say I have something like this:

type state = {
  field1: string,
  field2: int,
};

type action =
  | SetField1(string)
  | SetField2(int);

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

let make = ( _children) => {
  ...component,
  initialState: () => {field1: "", field2: 0},
  reducer: (action, state) => switch(action) {
    | SetField1(value) => ReasonReact.Update({...state, field1: value}) 
    | SetField2(value) => ReasonReact.Update({...state, field2: value})
  },
  render: ({state, send}) => 
    <div>
      <input value={state.field1} onChange={e => send(SetField1(getValue(e)))} />
      <input value={state.field2 |> string_of_int} onChange={e => send(SetField2(e |> getValue |> int_of_string))} />
    </div>,
}

In this example there are only 2 fields, but how to handle if there are for example 30 fields? Does that mean that I have to create 30 different actions and handle this 30 times in reducer? It's a lot of insignificant code. It there any way to modify the record more dynamically, or maybe i should use another structure for state (object, Js.t)?

To clarify i use this kind of forms in two cases:

  1. To convert state to Js.Json.t (using bs-json) and send to server (using bs-fetch)
  2. To send it to server using reason-apollo (graphql) as a mutation.
user3139560
  • 19
  • 1
  • 3

1 Answers1

0

This will necessarily depend a lot on your specific needs. What you're going to do with the form data will inform what shape you'll want it to have, and what else you need to do in this component will inform the internal logic of the component. But here's a few ideas at least:

One approach is to combine all the SetField variants into just one which takes a state update function. That way you can specify which field to update just in the render function:

  type action =
    | SetField(state => state);

  let make = _children => {
    ...component,
    initialState: () => {field1: "", field2: 0},
    reducer: (action, state) =>
      switch (action) {
      | SetField(updater) => ReasonReact.Update(updater(state))
      },
    render: ({state, send}) =>
      <div>
        <input
          value={state.field1}
          onChange={
            e => {
              let value = getValue(e);
              send(SetField(state => {...state, field1: value}));
            }
          }
        />
        <input
          value={string_of_int(state.field2)}
          onChange={
            e => {
              let value = e |> getValue |> int_of_string;
              send(SetField(state => {...state, field2: value}));
            }
          }
        />
      </div>,
  };

But because React events aren't immutable, and are actually reused by React itself, this is a bit error-prone. And the workaround adds verbosity. So unless you can factor this verbosity out, there might not be much point to this approach.

If all you do in this component is to update the state, you can remove the action type entirely. This removes some of the verbosity above, but otherwise has the same issues and is also much less flexible.

  let make = _children => {
    ...component,
    initialState: () => {field1: "", field2: 0},
    reducer: (updater, state) =>
        ReasonReact.Update(updater(state)),
    render: ({state, send}) =>
      <div>
        <input
          value={state.field1}
          onChange={
            e => {
              let value = getValue(e);
              send(state => {...state, field1: value});
            }
          }
        />
        <input
          value={string_of_int(state.field2)}
          onChange={
            e => {
              let value = e |> getValue |> int_of_string;
              send(state => {...state, field2: value});
            }
          }
        />
      </div>,
  };
glennsl
  • 28,186
  • 12
  • 57
  • 75
  • Thanks. It's some idea. To clarify i use this kind of forms in two cases: - To convert state to Js.Json.t (using bs-json) and send to server (using bs-fetch) - To send it to server using reason-apollo (graphql) as a mutation. What is the best way of this cases? – user3139560 Apr 07 '19 at 13:23
  • That really doesn't say all that much since you shouldn't model your application state directly on the shape of server data, as that might change independently of the application and cause bugs which will be very hard to fix. – glennsl Apr 07 '19 at 17:15
  • Application-specific concerns are much more important, like whether all your form fields are just text inputs, like your example here, or different kinds of fields. And whether you're going to do any kind of validation. And whether you're going to filter one menu based on the selection of another, for example. Once you've added all those features, a form field isn't just another form field anymore. And then having a separate action for each field makes the state transitions very clear and easy to maintain. – glennsl Apr 07 '19 at 17:19