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.