Skip to content

React Forms

Overview

Since React is using one way data binding - you cannot update anything on form inputs. React immediately sets the value back to initial state. So form fields need to be binded against state. And state updated on form field events

https://reactjs.org/docs/forms.html

Components

  • View component
  • Possible input values
  • Callback function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';

interface IProps {
    values: {
    input: string;
    checkbox: boolean;
    select: string;
    radio1: boolean;
    radio2: boolean;
    textarea: string;
}

handleChange: (target: 
    EventTarget & HTMLInputElement | 
    EventTarget & HTMLSelectElement | 
    EventTarget & HTMLTextAreaElement) => void;
}

const FormView = (props: IProps) => {
    return (
        <form>
            // Code omitted

Example of react form

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<form>
    <div className="form-group">
        <label htmlFor="exampleInputEmail1">Email</label>
        <input value={props.values.input} onChange={(e) => props.handleChange(e.target)} name="input" type="text" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" />
    </div>
    <div className="form-group form-check">
        <input checked={props.values.checkbox} onChange={(e) => props.handleChange(e.target)} name="checkbox" type="checkbox" className="form-check-input" id="exampleCheck1" />
        <label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
    </div>
    <div className="form-group">
        <label htmlFor="exampleFormControlSelect1">Example select</label>
        <select value={props.values.select} onChange={(e) => props.handleChange(e.target)} name="select" className="form-control" id="exampleFormControlSelect1">
            <option value="1">1</option>
            <option value="2">2</option>
        </select>
    </div>
    <div className="form-check form-check-inline">
        <input checked={props.values.radio1} onChange={(e) => props.handleChange(e.target)} name="radio" value="radio1" className="form-check-input" type="radio" id="inlineRadio1" />
        <label className="form-check-label" htmlFor="inlineRadio1">1</label>
    </div>
    <div className="form-check form-check-inline">
        <input checked={props.values.radio2} onChange={(e) => props.handleChange(e.target)} name="radio" value="radio2" className="form-check-input" type="radio" id="inlineRadio2" />
        <label className="form-check-label" htmlFor="inlineRadio2">2</label>
    </div>
    <div className="form-group">
        <label htmlFor="exampleFormControlTextarea1">Example textarea</label>
        <textarea value={props.values.textarea} onChange={(e) => props.handleChange(e.target)} name="textarea" className="form-control" id="exampleFormControlTextarea1" rows={3} />
    </div>
</form>

Control component - state

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import React, { useState, useEffect } from 'react';
import FormView from './FormView';

const Form = () => {
    const [values, setInput] = useState({
        input: "foo",
        checkbox: true,
        select: "2",
        radio1: false,
        radio2: true,
        textarea: "foo\nbar",
    });

    // Code omitted
}

Control component - callback function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const handleChange = (target:
    EventTarget & HTMLInputElement |
    EventTarget & HTMLSelectElement |
    EventTarget & HTMLTextAreaElement) => {
    //debugger;
    console.log(target.name, target.value, target.type, target)

    if (target.type === "text") {
        setInput({ ...values, [target.name]: target.value });
        return;
    }
    if (target.type === "checkbox") {
        setInput({ ...values, [target.name]: (target as EventTarget & HTMLInputElement).checked });
        return;
    }
    if (target.type === "radio" && target.name === "radio") {

    if (target.value === "radio1") {
        setInput({ ...values, radio1: true, radio2: false });
        return;
    }
        setInput({ ...values, radio1: false, radio2: true });
        return;
    }
    setInput({ ...values, [target.name]: target.value });
};

Control component - passing callback

Pass the state and value update callback function to form view.

1
2
3
4
5
    // Code omitted

    return <FormView values={values} handleChange={handleChange} />
}
export default Form;

Libraries

  • React Hook Form
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import { useForm } from 'react-hook-form';

function App() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="email">Email:</label>
      <input
        id="email"
        type="email"
        {...register('email', { required: 'Email is required', pattern: { value: /^\S+@\S+$/i, message: 'Invalid email' } })}
      />
      {errors.email && <span>{errors.email.message}</span>}
      <button type="submit">Submit</button>
    </form>
  );
}

export default App;
  • Formik
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';

function App() {
  const validateEmail = (value) => {
    let error;
    if (!value) {
      error = 'Email is required';
    } else if (!/^\S+@\S+$/i.test(value)) {
      error = 'Invalid email';
    }
    return error;
  };

  const onSubmit = (values, { setSubmitting }) => {
    console.log(values);
    setSubmitting(false);
  };

  return (
    <Formik initialValues={{ email: '' }} onSubmit={onSubmit}>
      {({ isSubmitting }) => (
        <Form>
          <label htmlFor="email">Email:</label>
          <Field id="email" type="email" name="email" validate={validateEmail} />
          <ErrorMessage name="email" component="span" />
          <button type="submit" disabled={isSubmitting}>Submit</button>
        </Form>
      )}
    </Formik>
  );
}

export default App;

React Hook Form offers better performance and a simpler API, making it a great option for developers who prioritize performance and are familiar with hooks. Formik, on the other hand, provides a more declarative approach with higher-level abstractions, making it a good choice for developers who prefer a more structured way of working with forms.

  • https://github.com/rjsf-team/react-jsonschema-form
  • https://modularforms.dev/
  • https://houseform.dev/
  • https://tanstack.com/form/latest
  • https://github.com/mnieber/react-form-state-context#readme
  • https://letsform.dev/