본문 바로가기
Next.js

Next.js만 활용해서 CRUD 구현하기

by rinny01 2025. 3. 13.
반응형

Uncontrolled Components

: 폼 요소의 상태를 제어하지 않는 컴포넌트

한마디로 정의하자면 과정은 보지 않고 결과만 반영하여준다.(마지막 제출할때만)

즉 코드상으로 onChage 등으로 관리할 필요가 없다는것!

 

장점 :

  • 간단한 구현 - 상태를 따로 관리하지 않기 때문에 간단한 폼 처리에 적합하다
  • 낮은 복잡성 - 폼의 상태를 리액트 상태로 동기화할필요가 없음으로 상태를 관리하지 않아도 된다

단점 :

  • 비제어 컴포넌트이니까 당연하게도 제어되지 않는다 -> 폼의 상태 추적이나 제어가 어려울 수 있다.
  • 복잡한 폼 처리 어려움 : 실시간 유효성 검사 불가 : 판단하려면 무조건 제출해야함

 

FormData

https://ko.javascript.info/formdata

 

FormData 객체

 

ko.javascript.info

https://developer.mozilla.org/ko/docs/Web/API/FormData

 

FormData - Web API | MDN

FormData 인터페이스는 form 필드와 그 값을 나타내는 일련의 key/value 쌍을 쉽게 생성할 수 있는 방법을 제공합니다. 또한 XMLHttpRequest.send() 메서드를 사용하여 쉽게 전송할 수 있습니다. 인코딩 타입

developer.mozilla.org

 

장점 : 

  • 이름부터 FormData로, 직관적으로 코드를 작성하기 때문에 가독성이 좋다
  • reset등 메서드 지원으로 쉬운 폼 제어가 가능하다.

코드예시

value, onChange함수 없이도 가능하다.

export default function TodoForm({ fetchTodos }: TodoFormProps) {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const formData = new FormData(e.currentTarget); //FormData 생성
    const title = formData.get('title')?.toString().trim(); //인풋의 value값을 가져올수있음

    await createTodo({ title }); // Todo 추가
    e.currentTarget.reset(); // 폼 초기화
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" type="text" placeholder="할 일을 입력하세요" />
      <button type="submit">추가하기</button>
    </form>
  );
}

 

Next.js만 활용해서 CRUD 구현하기

CREATE - 생성

새로운 항목추가 : POST 요청 : 데이터를 전송 > 새 항목 생성

// api/todo-api.ts
export const createTodo = async (title: string) => {
  const res = await fetch('/api/todos', {
    method: 'POST', // 새로운 리소스를 생성할 때 사용
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, completed: false }), // 전송할 데이터를 JSON 형태로 변환
  });
  if (!res.ok) throw new Error('Todo 생성 실패');
  const data: Todo = res.json(); // 생성된 Todo 데이터 반환
  return data;
};

 

 

🤔  headers: { 'Content-Type': 'application/json' } 의 역할

body에서 JSON.stringfy로 문자열로 만들어 보내주었기 때문에, 서버는 문자열만 받기때문에 알수가 없는데, 헤더에서  { 'Content-Type': 'application/json' } json형태라고 알려주었기때문에 서버는 이 헤더를 보고 JSON형태의 데이터를 받을 준비를 할수 있다.

 

🤔 res.json()쓰는이유

응답이 ReadableStream 형태로 제공된다. 이 스트림 데이터는 바로 사용할수 없고, 텍스트나 객체로 변환해야하기때문에,

응답받은 제이슨 데이터를 .json()으로 자바스크립트 객체로 변환하여 주어야 한다.

 

DELETE - 삭제

// api/todo-api.ts
export const deleteTodo = async (id: string) => {
  const res = await fetch(`/api/todos/${id}`, {
    method: 'DELETE', // DELETE 메서드는 특정 리소스를 삭제할 때 사용
  });

  if (!res.ok) throw new Error('삭제 실패');
};

 

UPDATE - 수정

// api/todo-api.ts
export const toggleTodoCompleted = async (id: string, completed: boolean) => {
  const res = await fetch(`/api/todos/${id}`, {
    method: 'PATCH', // PATCH 메서드는 리소스의 일부 데이터를 수정할 때 사용
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ completed }), // 변경할 데이터 전달
  });

  if (!res.ok) throw new Error('업데이트 실패');

	const data: Todo = res.json(); // 업데이트된 Todo 데이터 반환

  return data;
};

 

 

하지만 이렇게 구현했을때 문제점이 있다.

추가/삭제/수정 시에 바로바로 화면에 뜨지않고 새로고침을 해야 반영이되는데

즉, 리 랜더링이 일어나지 않는 이상 브라우저에 적용이 되지 않는다. 이문제를 어떻게 해결할수 있을까?

 

서버 액션 및 캐시 revalidate 활용

서버 액션은 Next.js에서 서버에서 직접 데이터를 처리할 수 있도록 도와주는 기능으로, 기존의 라우트 핸들러를 사용하지 않고도 서버의 함수를 직접 호출하여 데이터를 변경할 수 있다.

 

서버 액션을 사용하는 이유:

  1. 코드 간소화: 라우트 핸들러를 따로 생성하지 않아도 되므로 코드가 단순해짐
  2. 직접적인 데이터베이스 작업: 서버에서 직접 데이터베이스와 상호작용하여 효율성을 높일 수 있음
  3. 보안 강화: 클라이언트에서 민감한 API 요청을 직접 노출하지 않고 서버에서 처리할 수 있음
  4. 자동 데이터 갱신: revalidatePath 또는 revalidateTag를 활용하여 변경된 데이터를 자동으로 최신 상태로 유지할 수 있음
'use server';

// 서버에서 직접 데이터 변경 작업을 처리
export const deleteTodoAction = async (id: string) => {
  await fetch(`http://localhost:3000/api/todos/${id}`, { method: 'DELETE' });

  revalidatePath('/todos'); // 특정 경로('/todos')를 자동으로 갱신
  // 또는
  revalidateTag('todos'); // 태그 기준으로 자동 갱신
};
  • 'use server'; 코드 추가
  • revalidatePath 또는 revalidateTag 로 갱신요청 > 서버에서 자동으로 갱신해줌

컴포넌트에서 호출하기

'use client';

import { deleteTodoAction } from '@/actions/deleteTodoAction';

export default function TodoItem({ todo }) {
  const handleDelete = () => {
    deleteTodoAction(todo.id);
  };

  return (
    <div>
      <span>{todo.title}</span>
      <button onClick={handleDelete}>삭제</button>
    </div>
  );
}
반응형
LIST