Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

React Router V6 - Form & Action

上一篇介紹了 React Router V6 的 Loader,本文會介紹另一個重要的新功能 Action 與表單處理方式。

Use action() to Send Form Data

在 React 中,如果要提交表單,我們通常不會採取原生的提交方式,而是利用 State 去管理表單資料,並建立 Function 去串接 API 以送出表單,可能還會加上 Loading 效果、處理 Error 與 Redirect 等等。

React Router V6 提供的 <Form> 元件會阻止原生的表單行為,路由會在表單送出後執行 action 的內容,以下是一個簡單的範例。

1import { Form } from 'react-router-dom';
2
3const EventForm = () => {
4  return (
5    <Form method="post">
6      <label htmlFor="title">Title</label>
7      <input id="title" type="text" name="title" />
8    </Form>
9  );
10};
11
12export default EventForm;

在使用 React Router 的 <Form> 時,記得要為 Form Elements 加上 name 這個屬性,方便後續取得該欄位的值。

接著,我們建立 action 來做送出表單的行為,Action 與 Loader 一樣都有 requestparams 這兩個參數。

我們可以使用 request 提供的 formData() 方法,取得使用者提交的表單資料,再透過原生 FormData 提供的 get(name) 方法,就能取得對應 name 屬性的 value 囉。

1export const action = async ({ request, params }) => {
2  const data = await request.formData();
3
4  const eventData = {
5    title: data.get('title'), // get("title") 對應到 name="title" 的 input
6    image: data.get('image'),
7    date: data.get('date'),
8    description: data.get('description'),
9  };
10
11  const response = await fetch('http://localhost:8080/events', {
12    method: 'POST',
13    headers: {
14      'Content-Type': 'application/json',
15    },
16    body: JSON.stringify(eventData),
17  });
18
19  if (!response.ok) {
20    throw json({ message: 'Could not send event' }, { status: 500 });
21  }
22
23  return redirect('/events');
24};

最後我們使用了 React Router 提供的 redirect() 函式來進行重新導頁。

當判斷已成功取得回應時,我們傳入目標路由作為該函式的參數,例如:執行 return redirect("/events") 讓 Router 導回活動列表頁面。

使用 useSubmit 程式化提交表單

結合 react-router-dom 提供的 useSubmit Hook 以及 action() 函式,我們也可以不使用 <Form> 元件,直接以程式化的方式去執行表單的送出與後續處理。

以下範例中,我們希望用程式化的方式執行刪除功能,而非透過送出表單的方式執行。

首先,將刪除按鈕綁上 startDeleteHandler 函式,這個函式會在執行後出現 Confirm 提示,一旦確認後就會執行 useSubmit Hook 的功能。

接著執行的 submit() 函式可以放入兩個參數,第一個參數是 FormData 的物件,第二個參數是 Form 表單元素的 Properties。

1import { useSubmit } from 'react-router-dom';
2
3const EventItem = ({ event }) => {
4  const submit = useSubmit();
5
6  const startDeleteHandler = () => {
7    const proceed = window.confirm('Are you sure you want to delete');
8
9    if (proceed) {
10      // params: FormData Object, Form Properties
11      submit(null, { method: 'delete' });
12    }
13  };
14
15  return (
16    <article>
17      {/* ... */}
18      <button onClick={startDeleteHandler}>Delete</button>
19    </article>
20  );
21};
22
23export default EventItem;

使用 useSubmit 就跟送出表單一樣,都會去觸發路由裡面定義的 action(),所以我們要在負責執行刪除行為的元件中定義 action() 的內容。

useSubmit 觸發時,路由會執行 action() 去呼叫執行刪除動作的 API,並且 useSubmit 所定義的參數也會傳入 request 當中。

例如:撰寫 fetch()method 時,可以使用傳入的 request.method

1export const action = async ({ request, params }) => {
2  const eventId = params.eventId;
3
4  const response = await fetch(`http://localhost:8080/events/${eventId}`, {
5    method: request.method, // submit(null, { method: "delete" });
6  });
7
8  if (!response.ok) {
9    throw json({ message: 'Could not delete event' }, { status: 500 });
10  }
11
12  return redirect('/events');
13};

這樣就能順利以程式化的方式執行刪除動作囉!

回顧

看完這篇文章,我們認識了 React Router V6 新增的 Action 與 Form 元件。

除此之外還有許多實用的功能,建議可以看看他們的官方文件,這次更新後文件整理得很不錯。

References