KembaliBelajar Full-stack Dengan React Router v7
Pembahasan

Actions

Data loading buat ngambil data, sedangkan action buat mutasi data. Sama kayak loader, action juga bisa dieksekusi di server lewat fungsi action atau di client lewat clientAction.

Bedanya dengan Server Actions di Next.js, actions di React Router nggak perlu ditaro di file khusus. Lo cuman perlu export fungsi action dari route module sama kayak lo export loader. Nantinya fungsi itu yang bakal dipanggil ketika ada form request.

Contohnya gini:

app/routes/admin/products.tsx
import type { Route } from './+types/products';
import { db } from '~/libs/db';
import { Form } from 'react-router';

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const name = formData.get('name') as string;
  
  // Simulasi update data
  await db.updateProduct({ name });
  
  return { name };
}

export default function Products({ actionData }: Route.ComponentProps) {
  return (
    <div>
      <h1>Product</h1>
      <Form method="POST">
        <input type="text" name="name" placeholder="Product name" />
        <button type="submit">Update</button>
      </Form>
      {actionData && <p>Updated name: {actionData.name}</p>}
    </div>
  );
}

Penjelasan kodenya gini:

  • <Form> adalah component yang disediain React Router buat nge-handle form submission.
  • Ketika user submit form, hal ini yang terjadi:
    • action dieksekusi ketika ada form request dengan method POST, PUT, PATCH, atau DELETE.
    • request.formData() dipake buat ambil data dari form yang di-submit. Karena lo pake <Form>, maka tipe datanya adalah FormData.
    • Nama produk diambil dari form data dan disimpen ke variabel name.
    • Data produk di-update pake fungsi db.updateProduct({ name }) (ini contoh aja).
    • Terus data tersebut di-return sebagai actionData, yang bisa lo akses di dalam component Products.
    • Ketika action selesai dieksekusi tanpa error, semua loader data bakal di-revalidate supaya UI dan data tetep sinkron.
    • Terakhir, lo bisa akses nilai return dari action di dalam component lewat actionData.

Ketika lo pake <Form> buat submit form, dia bakal nambahin entry ke history browser, jadi user bisa kembali ke halaman sebelumnya pake tombol back di browser. Kalo lo nggak mau behavior ini, lo bisa pake yang namanya fetcher:

app/routes/admin/products.tsx
import type { Route } from './+types/products';
import { db } from '~/libs/db';
import { useFetcher } from 'react-router';

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const name = formData.get('name') as string;
  
  // Simulasi update data
  await db.updateProduct({ name });
  
  return { name };
}

export default function Products() {
  const fetcher = useFetcher<typeof action>();
  const actionData = fetcher.data;

  return (
    <div>
      <h1>Product</h1>
      <fetcher.Form method="POST">
        <input type="text" name="name" placeholder="Product name" />
        <button type="submit">Update</button>
      </fetcher.Form>
      {actionData && <p>Updated name: {actionData.name}</p>}
    </div>
  );
}

Ketika pake fetcher, buat ngambil data return dari action, lo bisa pake fetcher.data.

Di kasus nyata, lo biasanya butuh ngevalidasi data sebelum disimpen. Nggak ada cara spesial buat ini, standar aja kayak gini:

app/routes/admin/products.tsx
import type { Route } from './+types/products';
import { db } from '~/libs/db';
import { data, useFetcher } from 'react-router';

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const name = formData.get('name') as string;
  
  const errors: { name?: string } = {};
  
  if (!name) {
    errors.name = 'Name is required';
  }
  
  if (Object.keys(errors).length > 0) {
    return data({ errors } , { status: 400 });
  }
  
  // terserah mau ngapain abis ini
}

export default function Products() {
  const fetcher = useFetcher<typeof action>();
  const actionData = fetcher.data;

  return (
    <div>
      <h1>Product</h1>
      <fetcher.Form method="POST">
        <input type="text" name="name" placeholder="Product name" />
        {actionData?.errors?.name && (
          <p className="text-red-500">{actionData.errors.name}</p>
        )}
        <button type="submit">Update</button>
      </fetcher.Form>
    </div>
  );
}

Ketika lo mau nge-return error dari action, lo bisa pake fungsi data dan ngasih status code yang sesuai. Hal ini diperluin supaya mencegah React Router nge-revalidate semua loader data yang ada di halaman tersebut.

Progres kursus

Sudah paham pelajaran ini?

Tandai selesai untuk menyimpan progresmu di kursus ini.

Diskusi

0 komentar

Belum ada komentar.

Mulai diskusi