# Writing data to Database | Next.js Supabase Lite SaaS Kit

> Learn how to write, update and delete data using the Supabase Database in Makerkit (Lite)

*Canonical: https://makerkit.dev/docs/next-supabase-lite/data-fetching/writing-data-to-supabase*

---

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.

1. First, we create a new hook at `lib/tasks/hooks/use-create-task.ts`
2. We add the `tasks` table name to `lib/db-tables.ts`
3. We create a `mutations.ts` file within `lib/tasks`

```ts
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.

```ts
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '../../database.types';
import type Task from '~/lib/tasks/types/task';
import { TASKS_TABLE } from '~/lib/db-tables';

type Client = SupabaseClient<Database>;

export function createTask(client: Client, task: Omit<Task, 'id'>) {
  return client.from(TASKS_TABLE).insert({
    name: task.name,
    organization_id: task.organizationId,
    due_date: task.dueDate,
    done: task.done,
  });
}
```

### Calling mutation using a Server Action

The best way to call a mutation is to use a Next.js Server Action. This way, we can use the `revalidatePath` function to revalidate the page that we want to revalidate after the mutation is completed.

```tsx
'use server';

import { revalidatePath } from 'next/cache';

import { createTask } from '~/lib/tasks/mutations';
import type Task from '~/lib/tasks/types/task';
import { withSession } from '~/core/generic/actions-utils';
import getSupabaseServerActionClient from '~/core/supabase/action-client';

type CreateTaskParams = {
  task: Omit<Task, 'id'>;
  csrfToken: string;
};

export const createTaskAction = withSession(
  async (params: CreateTaskParams) => {
    const client = getSupabaseServerActionClient();

    await createTask(client, params.task);

    revalidatePath('/dashboard/[organization]/tasks');
  }
);
```

Then, we call the action from a form:

```tsx
'use client';

import type { FormEventHandler } from 'react';
import { useCallback, useTransition } from 'react';
import { toast } from 'sonner';
import { useRouter } from 'next/navigation';

import TextField from '~/core/ui/TextField';
import Button from '~/core/ui/Button';
import If from '~/core/ui/If';
import useCurrentOrganization from '~/lib/organizations/hooks/use-current-organization';
import { createTaskAction } from '~/lib/tasks/actions';
import useCsrfToken from '~/core/hooks/use-csrf-token';

const CreateTaskForm = () => {
  const [isMutating, startTransition] = useTransition();
  const organization = useCurrentOrganization();
  const organizationId = organization?.id as number;
  const csrfToken = useCsrfToken();
  const router = useRouter();

  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,
      };

      startTransition(async () => {
        await createTaskAction({ task, csrfToken });

        router.push('../tasks');
      });
    },
    [csrfToken, organizationId, router]
  );

  return (
    <form onSubmit={onCreateTask}>
      <div className={'flex flex-col space-y-2'}>
        <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
          className={
            'flex flex-col space-y-2 md:flex-row md:space-x-2 md:space-y-0'
          }
        >
          <Button loading={isMutating}>
            <If condition={isMutating} fallback={<>Create Task</>}>
              Creating Task...
            </If>
          </Button>

          <Button color={'transparent'} href={'../tasks'}>
            Go back
          </Button>
        </div>
      </div>
    </form>
  );
};

function getDefaultDueDate() {
  const date = new Date();
  date.setDate(date.getDate() + 1);
  date.setHours(23, 59, 59);

  return date.toDateString();
}

export default CreateTaskForm;
```

### Calling mutation using SWR

Alternatively, we can use the `createTask` mutation in our `useCreateTask` hook.

We will be using the `useSWRMutation` hook from `swr` to create our hook.

 ```tsx {% title="lib/tasks/hooks/use-create-task.ts" %}
import useSWRMutation from 'swr/mutation';
import { useRouter } from 'next/navigation';

import useSupabase from '~/core/hooks/use-supabase';
import { createTask } from '~/lib/tasks/mutations';
import type Task from '~/lib/tasks/types/task';

function useCreateTaskMutation() {
  const client = useSupabase();
  const router = useRouter();
  const key = 'tasks';

  return useSWRMutation(key, async (_, { arg: task }: { arg: Omit<Task, 'id'> }) => {
    return createTask(client, task);
  }, {
    onSuccess: () => router.refresh()
  });
}

export default useCreateTaskMutation;
```

Let's take a look at a complete example of a form that makes a request using
the hook above:

 ```tsx {% title="components/tasks/CreateTaskForm.tsx" %}
import { useRouter } from 'next/router';
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 router = useRouter();
  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.trigger(task);

      // redirect to /tasks
      return router.push(`/tasks`);
    },
    [router, 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();
}
```
