React repeated onInput handlers in components

Viewed 147

I have a react component that looks like the one given below.

The form inputs are handled using the onInputChange function and form submit is handled by onFormSubmit

function RegisterForm() {
  // formData stores all the register form inputs.
  const [formData, setFormData] = useState(registerDefault);
  const [errors, posting, postData] = useDataPoster();

  function onInputChange(event: ChangeEvent<HTMLInputElement>) {
    let update = { [event.target.name]: event.target.value };

    setFormData(oldForm => Object.assign(oldForm, update));
  }

  function onFormSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    const onSuccess: AxiosResponseHandler = response => {
      setFormData(Object.assign(formData, response.data));
    };

    postData("/api/register", formData, onSuccess);
  }

  return (
    <form onSubmit={onFormSubmit}>
      <FormTextInput
        name="full_name"
        label="Name"
        errors={errors.full_name}
        onChange={onInputChange}
      />
      <FormTextInput
        name="email"
        label="Email address"
        type="email"
        errors={errors.email}
        onChange={onInputChange}
      />
      <button type="submit" className="theme-btn submit" disabled={posting}>
        {posting && <span className="fas fa-spin fa-circle-notch"></span>}
        Create
      </button>
    </form>
  );
}

My app has more than 50 similar forms and I wonder if I have to copy paste these two functions on all the other forms. onInputChange won't be changing a bit and the url is the only variable in onFormSubmit.

I am thinking of a class based approach with setFormData and postData as properties and the functions in question as class methods. But in that case, I have to bind the handlers with the class instance, so that handlers have a valid this instance.

Is there any other way to do this? How would you avoid the repetition of these two code blocks in all the form components?

Thanks

3 Answers

Create an HOC to inject input handlers to the form components with added params for url.

function RegisterForm(props) {
  // specific function
  const specific = () => {
    const formData = props.formData; // use passed state values
    // use form data
  }
}

function withInputHandlers(Component, params) {
  return function(props) {
      // states
      function onInputChange(...) {...}
      function onFormSubmit(...) {
        // use params.url when submitting
        postData(params.url, formData, onSuccess);
      }
      // inject input handlers to component and state values
      return (
        <Component {...props} formData={formData} onChange={onInputChange} onSubmit={onFormSubmit} />
      );
  }
}

// Usage
const EnhancedRegisterForm = withInputHandlers(
  RegisterForm,
  { url: 'register_url' } // params
);

const EnhancedSurveyForm = withInputHandlers(
  Survey,
  { url: 'survey_url' } // params
)

you could create a custom hook, something like this:

const [formState, setFormState] = useFormStateHandler({name: ''})

<input value={formState.name} onChange={event => setFormState(event, 'name')} />

where the definition looks like this:

export default function useFormStateHandler(initialState) {
  const [state, setState] = useState(initialState)

  const updater = (event, name) => {
    setState({...state, [name]: event.target.value})
  }

  return [state, updater]
}

This change may help you

function RegisterForm() {
  // formData stores all the register form inputs.
  const [formData, setFormData] = useState(registerDefault);
  const [errors, posting, postData] = useDataPoster();

  const onInputChange = name => event => {
    let update = { [name]: event.target.value };

    setFormData(oldForm => Object.assign(oldForm, update));
  }

  const onFormSubmit = url => event =>{
    event.preventDefault();

    const onSuccess: AxiosResponseHandler = response => {
      setFormData(Object.assign(formData, response.data));
    };

    postData(url, formData, onSuccess);
  }

  return (
    <form onSubmit={onFormSubmit("/api/register")}>
      <FormTextInput
        name="full_name"
        label="Name"
        errors={errors.full_name}
        onChange={onInputChange("full_name")}
      />
      <FormTextInput
        name="email"
        label="Email address"
        type="email"
        errors={errors.email}
        onChange={onInputChange("email")}
      />
      <button type="submit" className="theme-btn submit" disabled={posting}>
        {posting && <span className="fas fa-spin fa-circle-notch"></span>}
        Create
      </button>
    </form>
  );
}
Related