- fetch, route 핸들러 필요 없이 api 처리하기(Next.js 13~)
- POST method로만 처리됨. but 다른 메소드 요청 작업 가능! (비표준적인 설계이긴 함)
- 클라이언트 컴포넌트에서 호출 가능
- 사용 유형
- Form (대표적)
- 이벤트 핸들러
- useEffect 내부에서
import { handleSubmit } from './actions'; export default function FormComponent() { return ( <form action={handleSubmit}> <input type="text" name="name" placeholder="Enter your name" /> <button type="submit">Submit</button> </form> ); }
'use client'; import { handleAction } from './actions'; export default function ButtonComponent() { const handleClick = async () => { await handleAction('Hello, Server Action!'); }; return <button onClick={handleClick}>Call Server Action</button>; }
'use client'; import { fetchData } from './actions'; export default function EffectComponent() { useEffect(() => { fetchData(); }, []); return <div>Fetching data...</div>; }
How To
- api 처리하는 비동기 함수(async), 즉 server action을 만듦
- 최상단에
“use server”
⇒ 서버에서만 작동되어야 함을 의미 - client component에서 : use server 함수를 쓸 수 X
- 클.컴이 아니라면 인라인으로 서버액션 함수 쓸 수 있음
⇒ 서버액션 함수를 파일로 분리
- form 속성에
action={서버액션}
속성 추가
⇒ submit 될 때 서버액션 실행
- server action에서 매개변수로 payload data를 받을 수 있음
- 서버액션의 매개변수(== data) 형식 :
FormData
- 데이터.get(”input의 name속성 값”)
- 받으려면, input의 name 속성을 꼭 적용해줘야 함
export async function handleForm(formData: FormData) { await new Promise((resolve) => { setTimeout(resolve, 3000); }); const email = formData.get("email"); console.log(formData.get("email"), formData.get("password")); return email }
- 서버와 통신 과정 비교 : API Routes vs Server Action
서버와 통신 과정 | 백엔드와 통신 | API Route | Server Action |
1. 서버에게 요청 방식 | fetch(HTTP요청) | fetch(HTTP요청) | 서버액션 함수 호출
(form은 action 속성으로) |
메소드 | fetch 할 때 명시 | fetch 할 때 명시 | 자동으로 POST 요청 생성 |
2. 서버측 코드 실행 | 백엔드 코드 실행 | api/route.ts 에 핸들러 실행 | 서버액션 함수 실행 |
- 이벤트 핸들러(on~)을 쓰면 클.컴이 되어야 하는데, 클.컴에서는 db 조작 같은 서버 작업을 할 수 없으므로 이 때 서버액션을 사용.
⇒ 그게 아니라면 db 조작을 굳이 서버액션에서 할 필요 x. 일반 함수에서 가능
- 서버액션 내부에서 다른 HTTP 요청(fetch) 시, 모든 메서드 요청 가능
서버액션과 함께 쓰면 유용한 react 훅들
1. useFormStatus
useFormStatus
⇒ form action의 작업 상태를 알려주는 훅 - 사용하려면 “use client”
action
(어떤 액션인지),data
,method
,pending
(로딩) 을 속성으로 제공
- 부모 action의 상태를 본다.
⇒ action form이 있는 곳에선 사용 X
⇒ form의 자식에서만(form 상태에 따라 변경되는 컴포넌트) 사용!
ex)
const FormButton = ({ text }: FormButtonProps) => { const { pending } = useFormStatus(); return ( <button disabled={pending} className="primary-btn h-10 disabled:bg-neutral-400 disabled:text-neutral-300 disabled:cursor-not-allowed" > {pending ? "Loading..." : text} </button> ); };
2. useFormState(~14) / useActionState(15~)
⇒ 서버액션을 실행 후 얻은 리턴값과, 서버액션을 실행할 수 있게하는 trigger를 제공
- 사용하려면 “use client”
const [state, action] = useActionState(서버액션, 초기상태값)
- state : 서버액션 실행하고 얻은 return 값
- action(trigger) : 서버액션을 실행
- 보통 초기상태 값으로 null을 주로
- 태그의 action 속성에 action을 넣기
- 실행할 때 useActionState에 이전 state, 즉 이전 return 값(혹은 초기값)이 인자에 넣어져 실행됨
⇒ 그러므로, 서버액션 함수의 매개변수에 prevState도 추가해줘야 함
ex)
"use server"; export async function handleForm(prevState: any, formData: FormData) { console.log("prevState >>>", prevState); ... return email; }
"use client"; ... import { handleForm } from "./actions"; const LoginPage = () => { const [state, action] = useActionState(handleForm, "null"); return ( .. <form action={action} className="gap-3 flex flex-col"> <FormInput type="email" name="email" placeholder="Email" required errorMessages={[state?.toString() ?? ""]} /> ...