form icon indicating copy to clipboard operation
form copied to clipboard

Every input field in form rerenders when just one changed

Open apo91 opened this issue 5 years ago • 8 comments

Ideally, only one input field component should render when its value is changed. Currently, all other input in the same form rerender, sometimes like 4 times each (when it comes to validation). Any suggestions?

import React, { useState } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

import { useForm, useField, splitFormProps } from "react-form";

async function sendToFakeServer(values) {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return values;
}

function validateAddressStreet(value) {
  if (!value) {
    return "A street is required";
  }
  return false;
}

async function fakeCheckValidName(name, instance) {
  if (!name) {
    return "A name is required";
  }

  return instance.debounce(async () => {
    console.log("checking name");
    await new Promise(resolve => setTimeout(resolve, 1000));
    // All names are valid, so return a false error
    return false;
  }, 500);
}

let counter = 0;
const InputField = React.forwardRef((props, ref) => {
  const [id] = useState(() => counter++);
  console.log(`InputField#${id}.render()`);
  // Let's use splitFormProps to get form-specific props
  const [field, fieldOptions, rest] = splitFormProps(props);

  // Use the useField hook with a field and field options
  // to access field state
  const {
    meta: { error, isTouched, isValidating },
    getInputProps
  } = useField(field, fieldOptions);

  // Build the field
  return (
    <>
      <input {...getInputProps({ ref, ...rest })} />{" "}
      {isValidating ? (
        <em>Validating...</em>
      ) : isTouched && error ? (
        <em>{error}</em>
      ) : null}
    </>
  );
});

function MyForm() {
  // Use the useForm hook to create a form instance
  const {
    Form,
    meta: { isSubmitting, canSubmit }
  } = useForm({
    onSubmit: async (values, instance) => {
      // onSubmit (and everything else in React Form)
      // has async support out-of-the-box
      await sendToFakeServer(values);
      console.log("Huzzah!");
    },
    debugForm: true
  });

  return (
    <Form>
      <div>
        <label>
          Name: <InputField field="name" validate={fakeCheckValidName} />
        </label>
      </div>
      <div>
        <label>
          Address Street:{" "}
          <InputField field="address.street" validate={validateAddressStreet} />
        </label>
      </div>

      <div>
        <button type="submit" disabled={!canSubmit}>
          Submit
        </button>
      </div>

      <div>
        <em>{isSubmitting ? "Submitting..." : null}</em>
      </div>
    </Form>
  );
}

function App() {
  return <MyForm />;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

PS: This seems to be a classic unsolved problem for every react forms library: https://github.com/jaredpalmer/formik/issues/342 https://github.com/redux-form/redux-form/issues/1405

apo91 avatar Feb 03 '20 21:02 apo91

Are they slow renders? Renders are not bad, but slow renders are. Profile if you can and post your findings. 👍 On Feb 3, 2020, 2:03 PM -0700, Andrii Polishchuk [email protected], wrote:

Ideally, only one input field component should render on it's value change. Currently, all other input in the same form rerender, sometimes like 4 times each (when it comes to validation). Any suggestions? import React, { useState } from "react"; import ReactDOM from "react-dom";

import "./styles.css";

import { useForm, useField, splitFormProps } from "react-form";

async function sendToFakeServer(values) { await new Promise(resolve => setTimeout(resolve, 1000)); return values; }

function validateAddressStreet(value) { if (!value) { return "A street is required"; } return false; }

async function fakeCheckValidName(name, instance) { if (!name) { return "A name is required"; }

return instance.debounce(async () => { console.log("checking name"); await new Promise(resolve => setTimeout(resolve, 1000)); // All names are valid, so return a false error return false; }, 500); }

let counter = 0; const InputField = React.forwardRef((props, ref) => { const [id] = useState(() => counter++); console.log(InputField#${id}.render()); // Let's use splitFormProps to get form-specific props const [field, fieldOptions, rest] = splitFormProps(props);

// Use the useField hook with a field and field options // to access field state const { meta: { error, isTouched, isValidating }, getInputProps } = useField(field, fieldOptions);

// Build the field return ( <> <input {...getInputProps({ ref, ...rest })} />{" "} {isValidating ? ( Validating... ) : isTouched && error ? ( {error} ) : null} </> ); });

function MyForm() { // Use the useForm hook to create a form instance const { Form, meta: { isSubmitting, canSubmit } } = useForm({ onSubmit: async (values, instance) => { // onSubmit (and everything else in React Form) // has async support out-of-the-box await sendToFakeServer(values); console.log("Huzzah!"); }, debugForm: true });

return (

 <div>
   <button type="submit" disabled={!canSubmit}>
     Submit
   </button>
 </div>

 <div>
   <em>{isSubmitting ? "Submitting..." : null}</em>
 </div>
); }

function App() { return <MyForm />; }

const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement); — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

tannerlinsley avatar Feb 04 '20 00:02 tannerlinsley

@tannerlinsley Renders are slow or not depending on the implementation of components and the number of components in form. You may be interested to check the landing page of this alternative library, it describes the general idea how to fix this issue: https://react-hook-form.com/

Do you ever wonder how many component re-renders have been triggered by the user? React Hook Form embraces uncontrolled form validation to reduce unnecessary performance impact.

apo91 avatar Feb 06 '20 20:02 apo91

@apo91 yes, but for which price? they use MutationObservers under the hood.

tommmyy avatar Feb 14 '20 12:02 tommmyy

This one has zero extra rerenders while being a controlled kind of form. I tested this. It uses event emitter. It is fast. https://github.com/JoviDeCroock/Hooked-Form

lishine avatar May 08 '20 22:05 lishine

Just to bump this, I'm getting 6 rerenders on a simple form with two fields. This causes an input delay of around 40ms, which sounds small but feels very noticeable, especially when typing fast.

ForestBeaver avatar Jun 20 '20 00:06 ForestBeaver

https://github.com/jaredpalmer/formik/issues/342#issuecomment-646155810

Censwin avatar Jan 19 '22 06:01 Censwin

I have the same problem

NairiAreg avatar Mar 06 '24 13:03 NairiAreg

@NairiAreg what version are you running into this with?

crutchcorn avatar Mar 06 '24 15:03 crutchcorn