Receive input and add to database
We already have a loader()
that can access the database to fetch data. But we need a way to send data to the database.
Let’s add a function to app/utilities/db.ts
to address this. Before we do that, we’ll create an interface to define the contents of a task
, to make it easier and less error prone.
Create app/utilities/interfaces.ts
with this content:
export interface ITask {
id: number;
task: string;
deadline: string;
}
I named it ITask
, with a capital I to show that it is an interface and not a type alias.
With the interface in place, we turn our attention to app/utilities/db.ts
.
Import our new interface at the top, and then add a function to create tasks in the database:
import { ITask } from "./interfaces";
export async function createTask(task: ITask): Promise<void> {
await db.tasks.create({data: {task: task.task, deadline: task.deadline }});
db.$disconnect();
}
The return type will be a Promise, since it is an async function, and the contents of that Promise is void
. Once we’ve added the data, we disconnect from the db. This way we have the smallest number of simultaneous connections to the database.
Now we need to find a way to invoke this function, and feed it the correct data. This has to happen in our app/routes/index.ts
.
The easiest way is to first change the form
to the Remix Form
component. You do that by first importing it from Remix, and then capitalise the tag name in your code:
import { Form, useLoaderData } from "remix";
export default function Index() {
let todoList = useLoaderData();
return (
<div>
<div>
<div>
<Form method="post">
<input
type="text"
placeholder="Task..."
name="task"
/>
<input
type="text"
placeholder="Deadline (in Days)..."
name="deadline"
/>
<button type="submit">Add Task
</button>
</Form>
</div>
</div>
<div>
{todoList.map((task: any) => {
return (
<div key={task.id} className="task">
<div>
<span>{task.task}</span>
<span>{task.deadline}</span>
<button>X</button>
</div>
</div>
);
})
}
</div>
</div>
);
}
Next you need to receive and process the formdata. You do that in a function called action()
.
export async function action({ request }:any) {
let formData = await request.formData();
const {_action, ...values} = Object.fromEntries(formData);
if(_action === "create"){
createTask(values as ITask);
}
return {};
}
As you can see, there is a variable _action
. It needs to have the value create
in order for the function to call our createTask()
. Where does that come from?
If you look at MDN button documentation you’ll see that buttons can have values connected to their name. If we give the “Add Task…” button the name _action
and the value create
, that’ll do the trick for us.
... (stuff omitted)
<button type="submit" name="_action" value="create" >Add Task
</button>
You can use any name you want, as long as you use the same name on the variable in your action()
. I chose _action
because it describes how I intend to use the value.
If you fire up npm run dev
now you should be able to add tasks to your list. But you can’t delete them yet.
Let’s fix that now!
We need a function to delete tasks, let’s add it to app/utilities/db.ts
:
export async function deleteTask(ID: number): Promise<void> {
await db.tasks.delete({
where: { id: ID as number }
});
db.$disconnect();
}
We need the ID the task has in the database, so that has to be a parameter.
How do we get hold of that, and how can we set up the delete button to actually work? It’s not even inside a form, so what can we do?
Well, first off we need to surround it with a form, or rather a Form
. Then we need a hidden input to carry the ID value.
Finally, we need to give the button itself a value of delete
to separate the actions in our action()
.
Let’s also extract the entire task to a separate component, components/TodoTask.tsx
.
import { Form } from "remix";
import { ITask } from "../utilities/interfaces";
import React from "react";
interface Props {
task: ITask;
}
const TodoTask = ({ task }: Props) => {
return (
<div key={task.id}>
<div>
<Form method="post">
<input type="hidden" name="ID" value={task.id}></input>
<span>{task.task}</span>
<span>{task.deadline}</span>
<button
type="submit" name="_action" value="delete"
>×</button></Form>
</div>
</div>
);
};
export default TodoTask;
As you see, we wrap the button in a Form
and add an input of type “hidden”. That way we can send the tasks ID to our action()
. We give the button the same name as our previous button, ”_action”. This lets us filter out the action to take, based on the value of ”_action”.
We’ll do that in a minute, but let us first use the new TodoTask.
Let’s replace the inline version we have in routes/index.tsx
.
Remove this code:
<div>
{todoList.map((task: any) => {
return (
<div key={task.id}>
<div>
<span>{task.task}</span>
<span>{task.deadline}</span>
<button>×</button>
</div>
</div>
);
})
}
</div>
And replace it with this:
<div>
{todoList.map((task: ITask, key: number) => {
return <TodoTask key={key} task={task} />;
})}
</div>
Remember to also import the new TodoTask component at the top of routes/index.tsx
:
import { ITask } from "~/utilities/interfaces";
Everything should look the same in the browser when you save. If the site isn’t running, run npm run dev
in the terminal to check.
All that remains to make deletions work now is to add the required code inside our action()
.
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 {
let ID = parseInt(values.ID);
await deleteTask(ID);
} catch (e) {
return {error: true};
}
}
return {};
}
Remember to also import deleteTask
from ~/utilities/db
:
import { createTask, deleteTask, getTasks } from "~/utilities/db";
Notice that we need to use parseInt()
on values.id
, because we get it as string
, but we need a number
to make it work.
Go ahead and test it in the browser now, it should be working. But it isn’t all that it could be. If you throttle the browser to “slow 3G” you’ll notice it takes a while for things disappear when you delete them. As it also does when you add new tasks. But there is no feedback at all. We’ll deal with that in the next part of this series.