Next js API 데이터 가져오기 3편

nab5m·2023년 11월 4일
1

Next js 공홈 뽀개기

목록 보기
4/7

서버 액션

form을 submit 했을 때 동작을 서버 액션으로 정의할 수 있습니다. 서버 액션은 서버 컴포넌트에 정의할 수 있고 클라이언트 컴포넌트에서 호출할 수 있습니다. 서버 액션은 Next의 캐싱, revalidation 아키텍처와 연동하여 form이 submit 되었을 때 캐시된 데이터를 업데이트 할 수 있습니다.

주의사항

서버 액션은 form을 submit 했을 때 서버에서 실행할 동작을 정의하는 것입니다. 보안상의 이유로 API 키와 같은 민감한 데이터를 숨겨야한다든지, next js를 api 서버로도 함께 쓴다든지, 이러한 특별한 요구사항이 있는 것이 아니라면 클라이언트 사이드에서 처리하면 되는 작업이라고 생각합니다.

예시

서버 액션의 정의

export default function Page() {
  async function create(formData: FormData) {
    'use server'
 
    // mutate data
    // revalidate cache
  }
 
  return <form action={create}>...</form>
}

"use server" 디렉티브를 함수나 파일의 맨 위에 명시하여 서버 액션을 정의할 수 있습니다.

revalidatePath

'use server'
 
import { revalidatePath } from 'next/cache'
 
export default async function submit() {
  await submitForm()
  revalidatePath('/')
}

위와 같이 revalidatePath를 사용하면 해당 라우트에서 사용하는 데이터를 revalidate할 수 있습니다.

revalidateTag

'use server'
 
import { revalidateTag } from 'next/cache'
 
export default async function submit() {
  await addPost()
  revalidateTag('posts')
}

또는 위와 같이 특정 tag를 사용한 데이터 fetch를 revlidate할 수 있습니다.

redirect

'use server'
 
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
 
export default async function submit() {
  const id = await addPost()
  revalidateTag('posts') // Update cached posts
  redirect(`/post/${id}`) // Navigate to new route
}

사용자를 다른 경로로 리다이렉트 시키는 것도 가능합니다.

form validation

import { z } from 'zod'
 
const schema = z.object({
  // ...
})
 
export default async function submit(formData: FormData) {
  const parsed = schema.parse({
    id: formData.get('id'),
  })
  // ...
}

input 태그에 required, type="email"과 같은 속성을 추가해서 간단한 검증을 한 후에 서버 사이드에서 zod와 같은 스키마 검증 라이브러리를 사용해서 요청 데이터를 검증할 수 있습니다.

로딩 상태 표시

'use client'
 
import { useFormStatus } from 'react-dom'
 
export function SubmitButton() {
  const { pending } = useFormStatus()
 
  return (
    <button type="submit" aria-disabled={pending}>
      Add
    </button>
  )
}

클라이언트 컴포넌트에서 useFormStatus()로 둘러싸고 있는 서버 액션을 사용하는 폼의 상태를 알아낼 수 있습니다.

결과 반환

'use server'
 
export async function createTodo(prevState: any, formData: FormData) {
  try {
    await createItem(formData.get('todo'))
    return revalidatePath('/')
  } catch (e) {
    return { message: 'Failed to create' }
  }
}

위와 같이 서버 액션에서 serializable 객체를 return하면

'use client'
 
import { useFormState } from 'react-dom'
import { createTodo } from '@/app/actions'
 
const initialState = {
  message: null,
}
 
export function AddForm() {
  const [state, formAction] = useFormState(createTodo, initialState)
 
  return (
    <form action={formAction}>
      <label htmlFor="todo">Enter Task</label>
      <input type="text" id="todo" name="todo" required />
      <p aria-live="polite" className="sr-only">
        {state?.message}
      </p>
    </form>
  )
}

클라이언트 사이드에서 서버 액션의 결과 값을 state로 받아볼 수 있습니다.

낙관적 업데이트

'use client'
 
import { useOptimistic } from 'react'
import { send } from './actions'
 
type Message = {
  message: string
}
 
export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
    messages,
    (state: Message[], newMessage: string) => [
      ...state,
      { message: newMessage },
    ]
  )
 
  return (
    <div>
      {optimisticMessages.map((m, k) => (
        <div key={k}>{m.message}</div>
      ))}
      <form
        action={async (formData: FormData) => {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      >
        <input type="text" name="message" />
        <button type="submit">Send</button>
      </form>
    </div>
  )
}

서버 액션을 완료하기 전에 서버 작업이 성공할 것으로 보고 낙관적으로 클라이언트 업데이트 작업을 하는 것입니다. 위의 예제는 채팅에서 메시지를 보냈을 때 성공할 것으로 예상하고 메시지 목록에 추가하는 모습처럼 보입니다.

이외에도 쿠키를 다루는 작업을 할 수 있습니다.

0개의 댓글