Learn Remix: clearing inputs in forms

The problem we’re going to solve in this episode of the series is about forms. Clearing inputs and returning focus to the first logical input. This way the user can fill in the form, hit enter, and then return to the first input field to fill in the next instance.

This is easy if you only have one form. It gets tricky if you have many separate forms that need individual treatment. It’s easier if each form is in a separate component, even if you use that component in many instances. That’s exactly what we have in this application. You can use the same solution we use whenever you have an app with many forms, each in their own component.

We’ll use a React hook, useRef to create unique references to two things. The form itself, and the input that we want to return focus to when the page reloads after submission.

We’re going to need another hook later, useEffect, so let’s import that too before we go further:

import { useEffect, useRef } from "react";

Here’s the code to create the references:

let formRef = useRef<HTMLFormElement>(null);
let taskInputRef = useRef<HTMLInputElement>(null);

You can add that after the isAdding that you created in the last post.

The next step is to add the references to the places where we need them:

<Form ref={formRef} method="post">
	<input
		type="text"
		placeholder="Task..."
		name="task"
		ref={taskInputRef}
	/>
	<input
		type="text"
		placeholder="Deadline (in Days)..."
		name="deadline"
	/>
	<button type="submit" name="_action" value="create" disabled={isAdding}>
		{isAdding ? "Adding your task": "Add Task"}
	</button>
</Form>

That takes care of the markup, now we need to add the functionality.

We want this to happen on first load of the component, and on reload after a submission. So we need to make sure that it doesn’t happen at any other occasion. This is easy. We use the useEffect hook, and check that we’re not in the middle of an adding operation.

useEffect(() => {
    if(!isAdding){
      formRef.current?.reset();
      taskInputRef.current?.focus();
    }
}, [isAdding]);

Notice that we add isAdding to the dependency array here. This ensures that the effect runs every time isAdding changes value.

You can read more about that here: useEffect, optimising performance by skipping effects

That takes care of clearing the form and focusing on the first input. Here’s all the code in our index.tsx right now:

import { Form, useLoaderData, useTransition } from "remix";
import { createTask, deleteTask, getTasks } from "~/utilities/db";
import { useEffect, useRef } from "react";

import { ITask } from "~/utilities/interfaces";
import TodoTask from "~/components/TodoTask";

export async function loader() {
  let tasks = await getTasks();
  return tasks ;
}

export async function action({ request }:any) {
  let formData = await request.formData();
  const {_action, ...values} = Object.fromEntries(formData);
  if(_action === "create"){
    createTask(values as ITask);
  }
  if(_action=== "delete") {
    try {
      // throw new Error("Boom");
      let ID = parseInt(values.ID);
      await deleteTask(ID);
      } catch (e) {
        return {error: true};
      }
  }
return {};
}


export default function Index() {
  let todoList = useLoaderData();
  let transition = useTransition();
  let isAdding = transition.state === "submitting" &&
  transition.submission.formData.get("_action")==="create";
  let formRef = useRef<HTMLFormElement>(null);
  let taskInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if(!isAdding){
      formRef.current?.reset();
      taskInputRef.current?.focus();
    }
  }, [isAdding]);

  return (
    <div>
      <div>
        <div>
          <Form ref={formRef} method="post">
          <input
            type="text"
            placeholder="Task..."
            name="task"
            ref={taskInputRef}
          />
          <input
            type="text"
            placeholder="Deadline (in Days)..."
            name="deadline"
          />
        <button type="submit" name="_action" value="create" disabled={isAdding}>
        {isAdding ? "Adding your task": "Add Task"}
          </button>
        </Form>
        </div>
      </div>
      <div>
        {todoList.map((task: ITask, key: number) => {
          return <TodoTask key={key} task={task} />;
        })}
      </div>
    </div>
  );
}

In the next part of the series we’ll switch to an optimistic UI. With that we change what the user sees, before we commit the changes to the database.

About the author

For the last three decades, I've worked with a variety of technologies. I'm currently focused on fullstack development. On my day to day job, I'm a senior teacher and course developer at a higher vocational school in Malmoe, Sweden. I'm also an occasional tech speaker and a mentor. Do you want to know more? Visit my website!