그럼에도 불구하고

👨‍💻

[Router] action과 Form, useActionData 그리고 redirect 본문

React/Router

[Router] action과 Form, useActionData 그리고 redirect

zenghyun 2023. 6. 12. 14:22

 

action과 Form, useActionData 그리고 redirect에 대해 알아보겠습니다.

 

 

[ action ]

react router dom에서 action을 배울 때 주목해야 할 부분은 HTML form입니다. HTML form은 특정 url에 데이터를 전송해서 처리하는 요청 과정입니다. 그리고 그 요청을 처리할 주소 값은 보통 action에 정의합니다. 

 

클라이언트 사이드에서 form을 처리하기 위해 리액트 라우터는 Form이라는 것을 사용합니다. 그리고 이는 html form을 모방하여 클라이언트 측에다가 request를 보냅니다. 

 

<form> // html 폼
<Form> // react router의 폼. 클라이언트 사이드에서 처리합니다.

 

즉 <form>을 사용하면 서버에다가 request를 보내는 것이고, <Form>을 사용하면 클라이언트 측에다가 request를 보내는 것입니다.

 

action의 핵심 개념을 정리해보면 다음과 같습니다.

 

1. 클라이언트 측에서 form을 처리하기 위해 Form을 사용하고, 이는 클라이언트에 request를 보내는 것입니다.

2. 클라이언트 측에서 request를 받으면 action이 이를 처리하는데, POST 요청 시 호출됩니다. 

3. formData()를 사용하면 action 함수에 전달된(요청에 사용된) 데이터를 받을 수 있습니다. 

 

 

아래는 제가 실제로 사용했던 코드의 예시입니다.

 

이 예시에서 필요한 부분만 인용해보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import {
  useNavigate,
  Form,
  useNavigation,
  useActionData,
  json,
  redirect
from "react-router-dom";
 
import classes from "./EventForm.module.css";
 
function EventForm({ method, event }) {
  const data = useActionData();
  const navigate = useNavigate();
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";
 
  function cancelHandler() {
    navigate("..");
  }
 
  return (
    <Form method={method} className={classes.form}>
      {data && data.errors && (
        <ul>
          {Object.values(data.errors).map((err) => (
            <li key={err}>{err}</li>
          ))}
        </ul>
      )}
      <p>
        <label htmlFor="title">Title</label>
        <input
          id="title"
          type="text"
          name="title"
          required
          defaultValue={event ? event.title : ""}
        />
      </p>
      <p>
        <label htmlFor="image">Image</label>
        <input
          id="image"
          type="url"
          name="image"
          required
          defaultValue={event ? event.image : ""}
        />
      </p>
      <p>
        <label htmlFor="date">Date</label>
        <input
          id="date"
          type="date"
          name="date"
          required
          defaultValue={event ? event.date : ""}
        />
      </p>
      <p>
        <label htmlFor="description">Description</label>
        <textarea
          id="description"
          name="description"
          rows="5"
          required
          defaultValue={event ? event.description : ""}
        />
      </p>
      <div className={classes.actions}>
        <button type="button" onClick={cancelHandler}>
          Cancel
        </button>
        <button disabled={isSubmitting}>
          {isSubmitting ? "Submitting..." : "Save"}
        </button>
      </div>
    </Form>
  );
}
 
export default EventForm;
 
export async function action({ request, params }) {
  const method = request.method;
  const data = await request.formData();
 
  const eventData = {
    title: data.get("title"),
    image: data.get("image"),
    date: data.get("date"),
    description: data.get("description"),
  };
 
  let url = "http://localhost:8080/events";
 
  if (method === 'PATCH') {
    const eventId = params.eventId;
    url = "http://localhost:8080/events/" + eventId; 
  }
 
  const response = await fetch(url, {
    method: method,
    headers: {
      "Content-Type""application/json",
    },
    body: JSON.stringify(eventData),
  });
 
  if (response.status === 422) {
    return response;
  }
 
  if (!response.ok) {
    throw json(
      { message: "Could not save event." },
      {
        status: 500,
      }
    );
  }
 
  return redirect("/events");
}
cs

 

 

[ useActionData ]

action에서 response를 return 했을 때, return 한 값을 받아오기 위해서 사용합니다. ( useLoaderData와 비슷합니다. )

 

https://despiteallthat.tistory.com/244  

 

[React-Router-Dom] loader와 useLoaderData

오늘은 loader와 useLoaderData에 대해 알아보겠습니다. [ loader ] 서버에 요청하지 않고 데이터를 가능한 한 빨리 가져오기 위해 react router dom 6.4v 부터는 client side browser를 제공하고 있습니다. 즉, 서버

despiteallthat.tistory.com

 

클라이언트 측에서 form을 처리하기 위해 Form을 사용하고 있습니다.  

 

그리고 useActionData를 통해 받아온 data를 통해 form에서 error 발생 시 error message를 출력하고 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
 
function EventForm({ method, event }) {
  const data = useActionData();
 
//생략 
 
 
 return (
    <Form method={method} className={classes.form}>
      {data && data.errors && ( // <= useActionData()로 받아온 data
        <ul>
          {Object.values(data.errors).map((err) => (
            <li key={err}>{err}</li>
          ))}
        </ul>
      )}
      <p>
        <label htmlFor="title">Title</label>
        <input
          id="title"
          type="text"
          name="title"
          required
          defaultValue={event ? event.title : ""}
        />
      </p>
      <p>
        <label htmlFor="image">Image</label>
        <input
          id="image"
          type="url"
          name="image"
          required
          defaultValue={event ? event.image : ""}
        />
      </p>
      <p>
        <label htmlFor="date">Date</label>
        <input
          id="date"
          type="date"
          name="date"
          required
          defaultValue={event ? event.date : ""}
        />
      </p>
      <p>
        <label htmlFor="description">Description</label>
        <textarea
          id="description"
          name="description"
          rows="5"
          required
          defaultValue={event ? event.description : ""}
        />
      </p>
      <div className={classes.actions}>
        <button type="button" onClick={cancelHandler}>
          Cancel
        </button>
        <button disabled={isSubmitting}>
          {isSubmitting ? "Submitting..." : "Save"}
        </button>
      </div>
    </Form>
  );
cs

 

[ redirect ]

 

formData()를 사용하여 action 함수에 전달된(요청에 사용된) 데이터를 받아오고 있습니다.

 

action 함수의 모든 과정이 끝난 후 redirect을 통해 해당 라우터로 이동하고 있습니다.

 

redirect는 내가 원하는 라우터로 이동할 수 있게 해주는 특수 함수입니다.  

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export async function action({ request, params }) {
  const method = request.method;
  const data = await request.formData();
 
  const eventData = {
    title: data.get("title"),
    image: data.get("image"),
    date: data.get("date"),
    description: data.get("description"),
  };
 
  let url = "http://localhost:8080/events";
 
  if (method === 'PATCH') {
    const eventId = params.eventId;
    url = "http://localhost:8080/events/" + eventId; 
  }
 
  const response = await fetch(url, {
    method: method,
    headers: {
      "Content-Type""application/json",
    },
    body: JSON.stringify(eventData),
  });
 
  if (response.status === 422) {
    return response;
  }
 
  if (!response.ok) {
    throw json(
      { message: "Could not save event." },
      {
        status: 500,
      }
    );
  }
 
  return redirect("/events");
}
cs

 

'React > Router' 카테고리의 다른 글

[Router] useNavigation과 useSubmit  (1) 2023.06.12
[Router] useFetcher  (0) 2023.06.12
[Router] loader의 error 처리 with useRouterError & json  (0) 2023.06.10
[Router] loader와 useLoaderData  (0) 2023.06.10
[Router] useParams 사용하기  (0) 2023.06.10
Comments