API Driven React-Hook Form (Dynamic Forms)
Tags: react, api, javascript, frontend
Published: 2023-08-24 10:04:45

Salut,

# Table of content

1. [Introduction](#introduction)
2. [Dynamic Form Data](#dynamic-form-data)
3. [React Hook Form](#react-hook-form)
4. [Text Input Field](#text-input-field)
5. [Text Area Field](#text-area-field)
6. [Radio Field](#radio-field)
7. [Dropdown Field](#dropdown-field)
8. [Checkbox Field](#checkbox-field)
9. [Error Component](#error-component)
10. [Complete Input Component](#complete-input-component)

### Introduction
API-driven forms refer to the practice of using APIs (Application Programming Interfaces) to integrate and manage forms within web applications. This approach offers several benefits that can enhance the functionality and user experience of forms on websites or applications. 

- Dynamic Data Loading
- Real-time Validation
- Flexibility and Customization
- Secure Data Handling
- Seamless Integration with Backends
- Consistency Across Platforms
- Reduced Development Time
- Scalability
- Easier Updates and Maintenance

Prerequisite - React Hook Form. As this article is related to API-driven forms, that's why I'll now elaborate on the working of React Hook Form. However, if you need an understanding of this library, just leave a comment and I'll explain it in another article.

### Dynamic Form Data
When we call an API for form fields, it should have the following payload. I am going to write my code accordingly to this payload if you guys want a different payload. You will be required to make changes in the code accordingly. I am not calling any API in this article, just creating a variable "dynamicForm" and assigning payload to it.

```
const dynamicForm = {
  firstName: {
    label: "First Name",
    type: "text",
    placeholder: "",
    defaultValue: "Yes",
    rules: {
      required: true,
    },
  },
  lastName: {
    label: "Last Name",
    type: "text",
    placeholder: "",
    defaultValue: "",
    rules: {
      required: true,
    },
  },
  email: {
    label: "Email",
    type: "email",
    placeholder: "",
    defaultValue: "",
    rules: {
      required: true,
    },
  },
  subject: {
    label: "Subject",
    type: "dropdown",
    options: ["Chemistry", "Physics", "Arts"],
    defaultValue: "",
    rules: {
      required: true,
    },
  },
  message: {
    label: "Message",
    type: "textarea",
    placeholder: "",
    defaultValue: "",
    rules: {
      required: false,
    },
  },
}
```

### React Hook Form
I will make a component named "Input" but for now let's suppose there is a component. Now, let's call the form and this Input component.

```
const {
    handleSubmit,
    control,
    formState: { errors },
  } = useForm();
```

Let's convert dynamicForm object to array and map through it the Input component.

```
const formInputs = Object.keys(dynamicForm).map((e, i) => {
    const { rules, defaultValue, label } = dynamicForm[e];

    return (
      <section key={i}>
        <label className="grey">
          {label} {rules?.required ? <span className="red">*</span> : ""}
        </label>
        <Controller
          name={e}
          control={control}
          rules={rules}
          defaultValue={defaultValue}
          render={({ field }) => (
            <div>
              <Input
                cClass="input-1"
                value={field.value}
                onChange={field.onChange}
                {...dynamicForm[e]}
              />
            </div>
          )}
        />
        {errors[e] && <Error>This field is required</Error>}
      </section>
    );
  });
```

Now, add a submit handler and form to our main component.

```
const onSubmit = (data) => console.log(data);

  return (
    <div className="wrapper">
      <form onSubmit={handleSubmit(onSubmit)}>
        {formInputs}
        <div>
          <button type="submit" className="btn-1">
            Submit
          </button>
        </div>
      </form>
    </div>
  );
```

### Text Input Field

```
const Input = ({ cClass, value, onChange, type, ...rest }) => {
 return (
                <input
                    type='text'
                    placeholder={rest?.placeholder}
                    onChange={(e) => onChange(e.target.value)}
                    value={value}
                    className={cClass}
                />
            );
}
```

### Text Area Field

```
const Input = ({ cClass, value, onChange, type, ...rest }) => {
 return (
                <textarea name="" id="" cols="30" rows="10" className={cClass} onChange={(e) => onChange(e.target.value)}>{value}</textarea>
            );
}
```

### Radio Field

```
const Input = ({ cClass, value, onChange, type, ...rest }) => {
 return rest?.options.map((i) => (
                <label>
                    <input
                        type='radio'
                        key={i}
                        value={i}
                        onChange={(e) => onChange(e.target.value)}
                        checked={value === i}
                    />
                    {i}
                </label>
            ));
}
```

### Dropdown Field

```
const Input = ({ cClass, value, onChange, type, ...rest }) => {
 return (
                <select value={value} onChange={(e) => onChange(e.target.value)} className={cClass}>
                    <option value="">Select an option</option>
                    {rest?.options.map((option) => (
                        <option key={option} value={option}>
                            {option}
                        </option>
                    ))}
                </select>
            );
}
```

### CheckBox Field

```
const Input = ({ cClass, value, onChange, type, ...rest }) => {
 return (
               <label>
                    <input
                        type="checkbox"
                        onChange={(e) => onChange(e.target.checked)}
                        checked={value}
                    />
                    {rest?.checkboxLabel}
                </label>
            );
}
```

I used a switch statement to check the type.

### Error Component

```
export const Error = ({ children }) => <p style={{ color: "red" }}>{children}</p>;
```

### Complete Input Component

```
const Input = ({ cClass, value, onChange, type, ...rest }) => {
    switch (type) {
        case "text":
            return (
                <input
                    type='text'
                    placeholder={rest?.placeholder}
                    onChange={(e) => onChange(e.target.value)}
                    value={value}
                    className={cClass}
                />
            );
        case "email":
            return (
                <input
                    type='email'
                    placeholder={rest?.placeholder}
                    onChange={(e) => onChange(e.target.value)}
                    value={value}
                    className={cClass}
                />
            );
        case "textarea":
            return (
                <textarea name="" id="" cols="30" rows="10" className={cClass} onChange={(e) => onChange(e.target.value)}>{value}</textarea>
            );
        case "radio":
            return rest?.options.map((i) => (
                <label>
                    <input
                        type='radio'
                        key={i}
                        value={i}
                        onChange={(e) => onChange(e.target.value)}
                        checked={value === i}
                    />
                    {i}
                </label>
            ));
        case "dropdown":
            return (
                <select value={value} onChange={(e) => onChange(e.target.value)} className={cClass}>
                    <option value="">Select an option</option>
                    {rest?.options.map((option) => (
                        <option key={option} value={option}>
                            {option}
                        </option>
                    ))}
                </select>
            );

        case "checkbox":
            return (
                <label>
                    <input
                        type="checkbox"
                        onChange={(e) => onChange(e.target.checked)}
                        checked={value}
                    />
                    {rest?.checkboxLabel}
                </label>
            );
        default:
            return null;
    }
};

export default Input
```

Frontend would render like this.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3l99j66fa1b96olk3as.png)

Thank you for reading this far. This is a brief introduction of **API Driven forms**
If you find this article useful, please like ❤ and share this article. Someone could find it useful too. If you find anything technically inaccurate please feel free to leave a comment.
Hope it's a nice and informative read for you. Follow me on [LinkedIn](https://www.linkedin.com/in/muhammad-azfar-aslam) for more helpful resources or reach out for any mutual opportunity.

Few more articles:
- [Highlight on the new features of Next JS 13 config 2022](https://dev.to/muhammadazfaraslam/highlight-on-the-new-features-of-next-js-13-config-2022-1a3i)
- [How to configure React JS app on WSL2 Windows 10](https://dev.to/muhammadazfaraslam/how-to-configure-react-js-app-on-wsl2-windows-10-3d1n)
- [Custom 'Tooltip' component using Tailwind and React Js](https://dev.to/muhammadazfaraslam/custom-tooltip-component-using-tailwind-and-react-js-186g)
- [JavaScript and React Interview questions (Junior/Mid-level) 2023](https://dev.to/muhammadazfaraslam/javascript-and-react-interview-questions-juniormid-level-2023-hd1)