简体   繁体   中英

Generate large dynamic forms with React, which do not re-generate on each input

I'm rewriting a form generator from a class based to a functional based approach. However, in both approaches I'm running into the same problem. The form receives a field template, and values, loops the field specifications, and renders the appropriate inputs. Each is given it's value and a handler to carry the value in a state object of the form (which later can be submitted).

This works fine while the form is small, but of course those forms are not small and can grow to be quite large and many types of elaborate fields in them. When the form field specification grows, the form slows down to the point where there is a delay between key press and visible input. Interestingly, that delay is very noticable while in development but is much better when compiled to a production build.

I would like to render the form elements as few times as possible and prevent the whole building of the form every time a key is pressed. However, if i pre-generate the fields, the event handlers don't retain the modified values. If I rebuild it on every render - it just slows things down.

A simplified example of this is here:

https://codesandbox.io/s/black-meadow-wqmzt

Note that this example starts by pre-rendering the form content into state and rendering it later. However, you can change the render s return line (in main.js ) from :

return <div>{formContent}</div>;

to

return <div>{build()}</div>;

to have the form re-build on each render. You will notice in this case that the build process runs a lot.

Is it possible to pre-render a set of inputs with event handlers attached and retain the event handler's behaviour?

Edit: The slowness of a large form rendering is manifested in the input - typing some text into a text field for example sees a delay between keypress to rendering of the input because each key press triggers a rebuild of the form.

You should defer eventHandlers and all the behavior to React. I've simplified your code a bit here: https://codesandbox.io/s/solitary-tree-1hxd2 . All the changes are in main.js file. Below I explain what I changed and why.

  • Removed useEffect hook and trigger of build() in there. Your hook was called only once on the first render and wasn't called on re-renders. That caused values don't update when you changed state.
  • Added unique key to each field. This is important for performance. It let's React internally figure out what field has updated and trigger DOM update only for that input. Your build() function is super fast and don't have side-effects. You shouldn't worry that it is being called more than once. React may call render multiple times and you have no control over it. For heavy functions you can use useMemo ( https://usehooks.com/useMemo/ ) hook, but it isn't the case here, even if you have 50 fields on a form.
  • Inlined calls to handleChange and fields . That's minor and personal preference.

I don't see any delay in the code now and render called once or twice on each field update. You can't avoid render because it is controlled component: https://reactjs.org/docs/forms.html#controlled-components . Uncontrolled components isn't recommended when using React.

Final code for form component:

export default function Main({ template, data }) {
  const [values, setValues] = useState(data);

  console.log("render");

  return (
    <div className="form">
      {template.fields.map((spec, index) => {
        const FormElement = Fields[spec.type];
        const fieldName = spec.name;
        return (
          <div>
            <FormElement
              spec={spec}
              value={values[fieldName] || ""}
              key={spec.name}
              onChange={e => {
                setValues({
                  ...values,
                  [fieldName]: e.target.value
                });
              }}
            />
          </div>
        );
      })}
    </div>
  );
}

You can just use local state [and handler] to force item update/rerenderings. This of course duplicates data but can be helpful in this case.

export default function Text({ spec, value, onChange }) {
  const [val, setVal] = useState(value);
  const handleChange = ev => {
    onChange(ev);
    setVal(ev.target.value);
  };

  return (
    <React.Fragment>
      <label>{spec.label}</label>
      <input type="text" name={spec.name} value={val} onChange={handleChange} />
    </React.Fragment>
  );
}

working example

BTW - use key (and not just index value) on outer element of item rendered from an array:

  return (
    <div key={spec.name}>
      <FormElement
        spec={spec}
        value={values[spec.name] || ""}
        onChange={handleChange}
      />
    </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM