Similarly to reading data, we may want to create a custom hook
also when we write, update or delete data to our Supabase database.
Assuming we have an entity called tasks
, and we want to create a hook to
create a new Task
, we can do the following.
- First, we create a new hook at
lib/tasks/hooks/use-create-task.ts
- We add the
tasks
table name tolib/db-tables.ts
- We create a
mutations.ts
file withinlib/tasks
export const TASKS_TABLE = `tasks`;
In our mutations file, we will add all the mutations we want to perform on
the tasks
table. In this case, we will add a createTask
mutation.
export function createTask(
client: Client,
task: Task,
) {
return client.from(TASKS_TABLE).insert(task).throwOnError();
}
Now, we can use the createTask
mutation in our useCreateTask
hook.
We will be using the useMutation
hook from react-query
to create our hook.
lib/tasks/hooks/use-create-task.ts
function useCreateTaskMutation(task: Task) {
const client = useSupabase();
return useMutation(
async (task: Task) => {
return createTask(client, task);
}
);
}
export default useCreateTaskMutation;
Let's take a look at a complete example of a form that makes a request using the hook above:
components/tasks/CreateTaskForm.tsx
import { useNavigate } from '@remix-run/react';
import type { FormEventHandler } from 'react';
import { useCallback } from 'react';
import { toast } from 'sonner';
import TextField from '~/core/ui/TextField';
import Button from '~/core/ui/Button';
import useCreateTaskMutation from '~/lib/tasks/hooks/use-create-task';
import useCurrentOrganization from '~/lib/organizations/hooks/use-current-organization';
const CreateTaskForm = () => {
const createTaskMutation = useCreateTaskMutation();
const navigate = useNavigate();
const organization = useCurrentOrganization();
const organizationId = organization?.id as number;
const onCreateTask: FormEventHandler<HTMLFormElement> = useCallback(
async (event) => {
event.preventDefault();
const target = event.currentTarget;
const data = new FormData(target);
const name = data.get('name') as string;
const dueDate = (data.get('dueDate') as string) || getDefaultDueDate();
if (name.trim().length < 3) {
toast.error('Task name must be at least 3 characters long');
return;
}
const task = {
organizationId,
name,
dueDate,
done: false,
};
// create task
await createTaskMutation.mutateAsync(task);
// redirect to /tasks
return navigate(`/tasks`);
},
[navigate, createTaskMutation, organizationId]
);
return (
<form onSubmit={onCreateTask}>
<div>
<TextField.Label>
Name
<TextField.Input
required
name={'name'}
placeholder={'ex. Launch on IndieHackers'}
/>
<TextField.Hint>Hint: whatever you do, ship!</TextField.Hint>
</TextField.Label>
<TextField.Label>
Due date
<TextField.Input name={'dueDate'} type={'date'} />
</TextField.Label>
<div>
<Button>Create Task</Button>
</div>
</div>
</form>
);
};
export default CreateTaskForm;
function getDefaultDueDate() {
const date = new Date();
date.setDate(date.getDate() + 1);
date.setHours(23, 59, 59);
return date.toDateString();
}