React: How to create a reusable form using React Context
Tags: react, javascript, es6
Published: 2021-09-25 10:09:40

Forms are very common in web apps. We are going to be creating forms over and over again when working as a developer. What makes React fun is that we can take common patterns like this and turn them into reusable components, making our development life easier and our code shorter.

This is for those who already know:
- [React State](https://reactjs.org/docs/state-and-lifecycle.html)
- [useState()](https://reactjs.org/docs/hooks-intro.html) hook

and would like to learn about [React Context](https://reactjs.org/docs/context.html) which "provides a way to pass data through the component tree without having to pass props down manually at every level." If you think [Redux sucks](https://twitter.com/thekitze/status/1104514942152175616), then keep reading, because Context is an alternative to Redux.

If you're having trouble, you can see [the finished code here](https://github.com/trishalim/react-reusable-form-tutorial) or leave a comment below.

## Let's start by creating a React app.
You can create your own React app but I suggest cloning this repository instead. I added some CSS, since I will not be explaining that.
`git clone https://github.com/trishalim/react-reusable-form-tutorial-boilerplate.git`

Go into that directory and run `npm install` and `npm start`.

## Creating a reusable <input> component called FormInput
Create a new filed named `FormInput.js` with the following code:

```jsx
import './FormInput.css';
import { useState } from 'react';

function FormInput(props) {
  const { label } = props;

  const [value, setValue] = useState('');
  const onChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <div className="FormInput">
      <label>{label}</label>
      <input
        type="text" 
        value={value}
        onChange={onChange}
      />
    </div>
  )
}

export default FormInput;
```
This component has a custom `label` prop, and handles changing of the input value through a state.

Use this new component in `App.js` by adding the following code:
```jsx
<FormInput label="First Name" />
<FormInput label="Last Name" />
```

Don't forget to import:
`import FormInput from './FormInput';`

You should end up with this:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzxylouddg39a1xdfdvi.png)
 
It would be useful if our FormInput component can handle different types of <input> fields. So let's add a `type` prop to allow for custom types.

```jsx
function FormInput(props) {
  // Set default type to "text"
  const { label, type = 'text' } = props;

  const [value, setValue] = useState('');
  const onChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <div className="FormInput">
      <label>{label}</label>
      <input
        type={type}
        value={value}
        onChange={onChange}
      />
    </div>
  )
}
```

Let's add email and password fields to `App.js`.
```jsx
<FormInput label="Email Address" type="email" />
<FormInput label="Password" type="password" />
```

Yay! Now our FormInput can do a tiny bit more.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uavez087rfsihah5a4u6.png)
 
## Moving state to App.js.

We want to be able to retrieve the values of the form. Currently, `App` has no way of knowing the current state of the form. Let's fix that.

Add a form state in `App`.
```jsx
import { useState } from 'react';

const [form, setForm] = useState({
  firstName: '',
  lastName: '',
  emailAddress: '',
  password: ''
});
```

Add some new props to `FormInput`. Remove the state and change handlers in `FormInput`. These will be moved to the parent component `App`. You should end up with only this:
```jsx
function FormInput(props) {
  const {
    label, 
    type = 'text', 
    name, 
    value, 
    onChange
  } = props;

  return (
    <div className="FormInput">
      <label>{label}</label>
      <input
        type={type}
        name={name}
        value={value}
        onChange={onChange}
      />
    </div>
  )
}
```

Since we just removed the `value` state and change handler from `FormInput`, we have to add these from `App` and pass them on as props instead.

```jsx
<FormInput 
  label="First Name" 
  name="firstName" 
  value={form.firstName}
  onChange={handleFormChange} />
```

Do the same for Last Name, Email and Password fields.
```jsx
<FormInput 
  label="Last Name" 
  name="lastName" 
  value={form.lastName}
  onChange={handleFormChange} />
<FormInput 
  label="Email Address" 
  type="email" 
  name="emailAddress" 
  value={form.emailAddress}
  onChange={handleFormChange} />
<FormInput 
  label="Password" 
  type="password" 
  name="password" 
  value={form.password}
  onChange={handleFormChange} />
```

Time to define our change handler `handleFormChange`. Here we are modifying `form` state, but only the field that changed. For example, if you type on the First Name field, `form.firstName` will be updated.
```jsx
  const handleFormChange = (event) => {
    // Clone form because we need to modify it
    const updatedForm = {...form};

    // Get the name of the field that caused this change event
    // Get the new value of this field
    // Assign new value to the appropriate form field
    updatedForm[event.target.name] = event.target.value;

    console.log('Form changed: ', updatedForm);

    // Update state
    setForm(updatedForm);
  };
```

Now go into your browser and play around with the form. You should be able to see the changes reflected on your console as you type on any of the fields. That means our state in `App` is working!
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4qcuhfkvuvebyq72vi0.png)

With some ES6 magic, we can shorten this to:
```jsx
const handleFormChange = (event) => {
  // Get the name of the field that caused this change event
  // Get the new value of this field
  const { name, value } = event.target;

  // Assign new value to the appropriate form field
  const updatedForm = {
    ...form,
    [name]: value
  };

  console.log('Form changed: ', updatedForm);

  // Update state
  setForm(updatedForm);
};
```

Now our code is still pretty long. 🙄 Great news: all this logic inside `App` for handling the form state can be reused too!

## Creating a reusable Form component

Remember all that code we just added in `App`? Let's move all that to a new `Form` component.

```jsx
import { useState } from 'react';
import './Form.css';

function Form(props) {
  const { children } = props;

  const [form, setForm] = useState({
    firstName: '',
    lastName: '',
    emailAddress: '',
    password: ''
  });

  const handleFormChange = (event) => {
    // Get the name of the field that caused this change event
    // Get the new value of this field
    const { name, value } = event.target;

    // Assign new value to the appropriate form field
    const updatedForm = {
      ...form,
      [name]: value
    };

    console.log('Form changed: ', updatedForm);

    // Update state
    setForm(updatedForm);
  };

  return (
    <form className="Form">
      {children}
    </form>
  );
}

export default Form;
```

We have the `children` props so that we can later on write something like:
```jsx
<Form>
  <FormInput />
  <FormInput />
  <FormInput />
</Form>
```

which results to:
```jsx
<form className="form">
  <FormInput />
  <FormInput />
  <FormInput />
</form>
```

`App` should NOT have any fields anymore, only the `return` statement. Remove `form`, `setForm` and `handleFormChange`. This will result into an error:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9udepqye0tgg7g5bruv.png)

`form` and `handleFormChange` are now undefined, since we moved them to `Form`. We need to be able to access these fields somehow. This is where React Context comes in.

## Use React Context to have access to form state and handleFormChange

[Context](https://reactjs.org/docs/context.html) provides another way to pass props to children, grandchildren, great grandchildren and so on - without having to pass them at every single level. 

First, let's declare and initialize a Context in `Form.js`. Make sure to export this since we'll be using it in other components.
```jsx
import React from 'react';

export const FormContext = React.createContext({
  form: {},
  handleFormChange: () => {}
});
```

These are the fields that we'd like to share to `Form`'s children.

Pass them from `Form` to `App` by wrapping `{children}` in `Form.js`'s return:
```jsx
<FormContext.Provider value={{
  form,
  handleFormChange
}}>
  {children}
</FormContext.Provider>
```

With this, the children can access `form` and `handleFormChange`. In `App`, make to sure to import:
`import Form, { FormContext } from './Form';`

Wrap all the `FormInput` components:
```jsx
<Form>
  <FormContext.Consumer>
    {({form, handleFormChange}) => (
      <>
        <FormInput 
          label="First Name" 
          name="firstName" 
          value={form.firstName}
          onChange={handleFormChange} />
        <FormInput 
          label="Last Name" 
          name="lastName" 
          value={form.lastName}
          onChange={handleFormChange} />
        <FormInput 
          label="Email Address" 
          type="email" 
          name="emailAddress" 
          value={form.emailAddress}
          onChange={handleFormChange} />
        <FormInput 
          label="Password" 
          type="password" 
          name="password" 
          value={form.password}
          onChange={handleFormChange} />
      </>
    )}
  </FormContext.Consumer>
</Form>
```

Notice that here we are using `FormContext.Consumer`. This means that we are consuming some data from FormContext. In `Form`, we were passing data, thus `FormContext.Provider`.

Check your browser and play around with the form. The state should be reflecting. You'll see this in the console just like before.

The behavior didn't change, but now our code is more reusable. And you've learned how to use Context! 🎉

## Let's make our code shorter. More reusability!
Our code is still pretty long and repetitive. For every `FormInput`, we've had to write `value={form.xxx}` and `onChange={handleFormChange}`.

We can move this logic to `FormInput`. Instead of consuming `FormContext` in `App`, we can actually do that in `FormInput`. This is the great thing about Context compared to props. The fields become accessible down several levels.

In `FormInput`, let's use `FormContext`. This is another way to use a Context:
```jsx
const formContext = useContext(FormContext);
const { form, handleFormChange } = formContext;
```

Don't forget to import:
`import { useContext } from 'react';`
`import { FormContext } from './Form';`

Now that we have access to the `form` state, we can set the input value from that:
`value={form[name]}`

And the change handler:
`onChange={handleFormChange}`

We no longer need `value` and `onChange` props here.

Your `FormInput.ts` should look like this:
```jsx
import './FormInput.css';
import { useContext } from 'react';
import { FormContext } from './Form';

function FormInput(props) {
  const {
    label, 
    type = 'text', 
    name,
  } = props;

  const formContext = useContext(FormContext);
  const { form, handleFormChange } = formContext;

  return (
    <div className="FormInput">
      <label>{label}</label>
      <input
        type={type}
        name={name}
        value={form[name]}
        onChange={handleFormChange}
      />
    </div>
  )
}

export default FormInput;
```

Since `FormInput` now handles the use of FormContext, we can remove lots of code in `App.js`:

```jsx
import './App.css';
import Form from './Form';
import FormInput from './FormInput';

function App() {
  return (
    <div className="App">
      <h1>Sign Up</h1>

      <Form>
        <FormInput 
          label="First Name" 
          name="firstName" />
        <FormInput 
          label="Last Name" 
          name="lastName" />
        <FormInput 
          label="Email Address" 
          type="email" 
          name="emailAddress" />
        <FormInput 
          label="Password" 
          type="password" 
          name="password" />
      </Form>
    </div>
  );
}

export default App;
```

Looking neat af! 🤩 Make sure it's still working as expected.

## One last thing!

Currently, `Form` always has the same fields `firstName, lastName, emailAddress, password`. We need to be able to customize this.

In `Form`, add a new prop called `formInitialValues` and use that as a default state:
`const [form, setForm] = useState(formInitialValues);`

In `App`, make sure we're passing the new prop:
```jsx
<Form formInitialValues={{
  firstName: '',
  lastName: '',
  emailAddress: '',
  password: ''
}}>
```

Great! Is it still working as expected? If so, let's proceed with adding another form.


## Create another form, and see how much easier it is now!
Here's a login form that I created:
```jsx
<Form formInitialValues={{
  username: '',
  password: ''
}}>
  <FormInput
    label="Username"
    name="username" />
  <FormInput
    label="password"
    name="Password"
    type="password" />
</Form>
```

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9guuxf5bme6hypyzknc.png)
 
And there you have it!

You can also [download the finished code here](https://github.com/trishalim/react-reusable-form-tutorial).

You can continue adding more code to improve this:
1. Add a submit button.
2. Add a `required` boolean prop to FormInput. If there is no value, display an error message.
3. Custom validations and error messages.
4. Other input fields like `<select>`.

If you're having trouble in any of the steps, let me know below. I'd love to help you out!

If you enjoyed this and want to learn more about me, [check out my website](https://trishalim.com) and [download my website template](https://trishalim.gumroad.com/l/portfolio-website-template-for-software-developers).