React Hook Form - Simple Todo List 
Tags: javascript, forms, reacthookform, react
Published: 2021-08-01 22:54:04


 
 
Today we'll play around with [React Hook Form](https://react-hook-form.com/) library and build a simple to-do list project.

Creating a form using React is straightforward. But things start to get more tricky when the form requires multiple inputs/validations, responsive UI, validation, and external data. Luckily React Hook Form is one of many libraries that improve the developer experience when creating web forms. The library promises to make it easier for developers to add form validation and build performant forms. 

So let us test out the React Hook Form library by building a simple to-do list project. This quick guide will not go over styling/CSS but instead focus on building out the components. Feel free to clone and play around with the finished project [here](https://github.com/chrislemus/react-hook-form-task-form). 

##File structure
![file-structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90msmswz29dcyckur4qa.png)
The image above illustrates how our file structure will look like, so feel free to remove any additional files that come included after creating a [new react app](https://reactjs.org/docs/create-a-new-react-app.html). 

##Styling
![react-hook-project-styling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ddpnqmlcispp9noyh2g.png)
The styling is quite long and will take too much space on this page. So feel free to copy/past the styling from the [project's repo](https://github.com/chrislemus/react-hook-form-task-form/blob/main/src/styles/app.css) into the `app.css` file. 

And make sure to import the stylesheet by adding the code below into `index.js`. 
```javascript
import React from 'react';
import './styles/app.css';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```

##Building our Components
![react-hook-form-project-structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6gv8htjuz4oony3iu8c.png)
 
For the structure of our project, we will have our parent component, `app.js`. And two child components, `TaskList.js` and `NewTask.js`. So let's get started with the `app.js`.

###Parent Component - App.js
```javascript
import { useState } from 'react';
import NewTaskForm from './components/NewTaskForm';
import TaskList from './components/TaskList';

const defaultTasks = [
  { id: 1, completed: false, label: 'buy pickle' },
  { id: 2, completed: true, label: 'buy ketchup' },
];

const uniqueId = () => Math.floor(Math.random() * Date.now());

export default function App() {
  const [tasks, setTasks] = useState(defaultTasks);

  const completeTaskHandler = (taskId) => {
    const updatedTasks = tasks.map((task) => {
      const completed = !task.completed;
      return task.id === taskId ? { ...task, completed } : task;
    });
    setTasks(updatedTasks);
  };

  const deleteTaskHandler = (taskId) => {
    setTasks(tasks.filter(({ id }) => taskId !== id));
  };

  const newTaskHandler = (label) => {
    const newTask = {
      id: uniqueId(),
      completed: false,
      label,
    };
    setTasks([...tasks, newTask]);
  };

  return (
    <div className="container">
      <NewTaskForm newTaskHandler={newTaskHandler} />
      <TaskList
        tasks={tasks}
        completeTaskHandler={completeTaskHandler}
        deleteTaskHandler={deleteTaskHandler}
      />
    </div>
  );
}
```
First, we will import our `child components` and the `useState` hook. Then as the name implies, our `defaultTasks` variable will store our default tasks. Each task will require an id, completed, and label property. Since we need a unique id for each task, we will create a helper function called `uniqueId` to generate an id.

Now let's use the `useState` hook to store all of our tasks. And create three separate functions for creating, deleting, and marking a task as complete. Lastly, we will return our JSX containing our child components. While making sure we provide the required properties for each component

###Child Component #1 - TaskList.js
```javascript
export default function TaskList({
  tasks,
  completeTaskHandler,
  deleteTaskHandler,
}) {
  tasks.sort((a, b) => a.completed - b.completed);
  return (
    <div>
      {tasks.map(({ label, completed, id }) => (
        <div key={id} className={`task ${completed && 'task--completed'}`}>
          <button
            className="task__complete-button"
            onClick={() => completeTaskHandler(id)}
          />
          <p className="task__label">{label}</p>
          <button
            className="task__delete-button"
            onClick={() => deleteTaskHandler(id)}
          >
            🗑
          </button>
        </div>
      ))}
    </div>
  );
}
```
The `TaskList` component will use object destructuring to use the props provided by the parent component. And the 'sort' method will be called on our tasks array to display the uncompleted tasks at the top and the completed tasks at the bottom. Finally, we will iterate through each task to create our HTML elements.
###Child Component #2 - NewTaskForm.js
```javascript
import { useForm } from 'react-hook-form';
export default function NewTaskForm({ newTaskHandler }) {
  const { register, handleSubmit, reset, formState, clearErrors } = useForm({
    shouldUnregister: true,
    defaultValues: { label: '' },
  });

  const onSubmit = (data) => {
    newTaskHandler(data.label);
    reset();
    clearErrors();
  };

  const errors = Object.values(formState.errors);
}
```
We will now import the `useForm` hook from the React Hook Form library, which takes optional arguments. The `shouldUnregister` will be set to `true` to unregister input during unmount. And for the `defaultValues` property, we will set the default value for the task label input.

The `useForm` hook returns an object containing information about our form and helper functions to manipulate our form. Therefore destructuring assignment is used to access the `register`, `handleSubmit`, `reset`, 	`formState`, and `clearErrors` property.

Next, an `onSubmit` function is created to handle the form submission. First, the function will trigger the `newTaskHandler` while passing down the new task label from our form data. Then `reset` will reset the input values in our form. And finally, `clearErrors` as the name states will clear out all the form errors.

```javascript
  return (
    <form className="new-task-form" onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="task">New Task</label>
      <input
        id="task"
        {...register('label', {
          required: 'task cannot be blank',
          validate: {
            lessThanTwenty: (v) =>
              v.length <= 20 || 'Task cannot be longer than 20 characters.',
          },
        })}
      />
      <ul className="error-messages">
        {errors.map((error) => (
          <li>{error.message}</li>
        ))}
      </ul>
      <button type="submit">add</button>
    </form>
  );
```
The last step will be to return the JSX. The React Hook Form's `handleSubmit` function is passed down to the form's `onSubmit` property; notice we also provide the `onSubmit` callback function to `hadleSubmit` as well.

For the input element, we will use the React Hook Form's `register` function. The first argument will be the name of the input, `label`. And the second argument is a configuration object. In our case, we will only set the validation settings, the field cannot be blank, and the field length cannot be longer than twenty. The last step is to use the spread operator to give the `input` access to all the properties provided by React Hook Form.

Here is how the final code for the `NewTaskForm` should look.
```javascript
import { useForm } from 'react-hook-form';
export default function NewTaskForm({ newTaskHandler }) {
  const { register, handleSubmit, reset, formState, clearErrors } = useForm({
    shouldUnregister: true,
    defaultValues: { label: '' },
  });

  const onSubmit = (data) => {
    newTaskHandler(data.label);
    reset();
    clearErrors();
  };

  const errors = Object.values(formState.errors);

  return (
    <form className="new-task-form" onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="task">New Task</label>
      <input
        id="task"
        {...register('label', {
          required: 'task cannot be blank',
          validate: {
            lessThanTwenty: (v) =>
              v.length <= 20 || 'Task cannot be longer than 20 characters.',
          },
        })}
      />
      {errors.length > 0 && (
        <ul className="error-messages">
          {errors.map((error) => (
            <li>{error.message}</li>
          ))}
        </ul>
      )}
      <button type="submit">add</button>
    </form>
  );
}

```