프로젝트 목록 UI
프로젝트 목록을 조회하고 생성·삭제 동작을 백엔드 API와 연결합니다.
프로젝트 및 파일 관리 UI 구현
프론트엔드 통합은 API 계약, 인증 토큰 전달, CORS, 로딩/오류 상태 기준으로 읽습니다.
이제 로그인 후 접근할 수 있는 프로젝트 목록 페이지와 에디터 페이지를 구현합니다.
프로젝트 목록
API 성공 뒤 목록을 갱신하고 실패·대기 상태를 별도로 보여줍니다
초기 []·GET 대기·성공 목록 적용·실패 목록 유지에서 실제 원 코드의 상태와 미구현 UI 표시를 구별한다.
Nest 프로젝트 UI · 요청과 화면 상태
API 성공 뒤 목록을 갱신하고 실패·대기 상태를 별도로 보여줍니다
현재 간략화 코드는 GET·POST·DELETE 성공 뒤 목록을 변경합니다. 아래 성공 흐름은 구현되어 있으며 로딩·화면 오류·중복 제출 억제는 추가할 UI 상태입니다.
조회 · GET /projects
페이지 진입
useEffect가 조회를 시작합니다.
성공 응답
setProjects(response.data)로 서버 목록을 표시합니다.실패 응답
현재는 console.error만 남깁니다. 초기 빈 배열이 “프로젝트 없음”으로 보일 수 있으므로 화면 오류 상태를 추가합니다.
생성 · POST /projects
제출 조건
기본 submit을 막고 trim한 이름이 비면 요청하지 않습니다. 서버 DTO의 최소 3자 검증과는 별도입니다.
API 성공 이후
응답 항목을 목록에 추가하고 입력을 비운 뒤 새 프로젝트 editor로 이동합니다.
API 실패 이후
현재는 로그만 남기며 성공 갱신·이동을 하지 않습니다. 오류 안내와 재제출 상태를 추가합니다.
삭제 · DELETE /projects/:id
사용자 확인
confirm을 취소하면 요청을 보내지 않습니다.
API 성공 이후
삭제 응답을 기다린 뒤 해당 ID를 목록에서 제외합니다.
API 실패 이후
목록을 그대로 두고 현재는 console.error만 남깁니다. 화면 오류와 다시 시도할 동작을 추가합니다.
추가할 상태 · pending / error / empty
조회 중인 화면, 실패한 화면, 성공했지만 0건인 화면을 구분합니다. 생성·삭제는 진행 중 표시와 해당 action의 중복 제출 억제를 추가합니다.
겹친 응답이 최신 목록을 덮어쓰지 않도록 갱신 순서·재조회·함수형 state 갱신을 설계합니다. 이 동시성 제어는 현재 간략화 코드에 구현되어 있지 않습니다.
확인 순서: 요청 시작 → 서버 성공/실패 → state 갱신 또는 오류 안내 → 필요한 페이지 이동. 버튼 클릭과 서버 성공을 같은 사건으로 취급하지 않습니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- 현재 GET 성공은 서버 목록을 적용하고 실패는 로그만 남겨 목록을 바꾸지 않는다. 처음의 빈 배열은 대기나 실패에도 남을 수 있으며 화면에 표시할 요청 상태는 별도 추가해야 한다.
관계도의 보충 설명
| 순번 | 원문 표시 |
|---|---|
| 3 | 현재 간략화한 코드는 GET·POST·DELETE 성공 뒤 목록을 변경합니다. 아래 성공 흐름은 구현되어 있으며 로딩·화면 오류·중복 제출 억제는 추가할 UI 상태입니다. |
| 48 | 겹친 응답이 최신 목록을 덮어쓰지 않도록 갱신 순서·재조회·함수형 state 갱신을 설계합니다. 이 동시성 제어는 현재 간략화한 코드에 구현되어 있지 않습니다. |
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| 빈 배열로 시작 현재 목록 예제는 초기 빈 배열로 렌더합니다. | 목록: [] · 요청 상태 UI: 별도 추가 필요 | 목록 출발 상태가 설정됩니다. |
| GET 대기 GET이 진행되어도 현재 목록 배열은 아직 바뀌지 않습니다. | 목록: [] · 대기: 배열만으로 구분 불가 | 네트워크 작업과 표시 배열이 분리됩니다. |
| 성공 적용 또는 실패 유지 성공은 서버 목록을 적용하고 실패는 로그만 남기므로 처음의 빈 배열도 유지될 수 있습니다. | 성공: 서버 목록 · 실패: 기존 목록 유지 · 오류 UI: 현재 없음 | 성공 분기에서만 표시 자료가 교체됩니다. |
API 성공 뒤 목록을 갱신하고 실패·대기 상태를 별도로 보여줍니다
초기 []·GET 대기·성공 목록 적용·실패 목록 유지에서 실제 원 코드의 상태와 미구현 UI 표시를 구별한다.
Nest 프로젝트 UI · 요청과 화면 상태
API 성공 뒤 목록을 갱신하고 실패·대기 상태를 별도로 보여줍니다
현재 간략화 코드는 GET·POST·DELETE 성공 뒤 목록을 변경합니다. 아래 성공 흐름은 구현되어 있으며 로딩·화면 오류·중복 제출 억제는 추가할 UI 상태입니다.
조회 · GET /projects
페이지 진입
useEffect가 조회를 시작합니다.
성공 응답
setProjects(response.data)로 서버 목록을 표시합니다.실패 응답
현재는 console.error만 남깁니다. 초기 빈 배열이 “프로젝트 없음”으로 보일 수 있으므로 화면 오류 상태를 추가합니다.
생성 · POST /projects
제출 조건
기본 submit을 막고 trim한 이름이 비면 요청하지 않습니다. 서버 DTO의 최소 3자 검증과는 별도입니다.
API 성공 이후
응답 항목을 목록에 추가하고 입력을 비운 뒤 새 프로젝트 editor로 이동합니다.
API 실패 이후
현재는 로그만 남기며 성공 갱신·이동을 하지 않습니다. 오류 안내와 재제출 상태를 추가합니다.
삭제 · DELETE /projects/:id
사용자 확인
confirm을 취소하면 요청을 보내지 않습니다.
API 성공 이후
삭제 응답을 기다린 뒤 해당 ID를 목록에서 제외합니다.
API 실패 이후
목록을 그대로 두고 현재는 console.error만 남깁니다. 화면 오류와 다시 시도할 동작을 추가합니다.
추가할 상태 · pending / error / empty
조회 중인 화면, 실패한 화면, 성공했지만 0건인 화면을 구분합니다. 생성·삭제는 진행 중 표시와 해당 action의 중복 제출 억제를 추가합니다.
겹친 응답이 최신 목록을 덮어쓰지 않도록 갱신 순서·재조회·함수형 state 갱신을 설계합니다. 이 동시성 제어는 현재 간략화 코드에 구현되어 있지 않습니다.
확인 순서: 요청 시작 → 서버 성공/실패 → state 갱신 또는 오류 안내 → 필요한 페이지 이동. 버튼 클릭과 서버 성공을 같은 사건으로 취급하지 않습니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- 현재 GET 성공은 서버 목록을 적용하고 실패는 로그만 남겨 목록을 바꾸지 않는다. 처음의 빈 배열은 대기나 실패에도 남을 수 있으며 화면에 표시할 요청 상태는 별도 추가해야 한다.
관계도의 보충 설명
| 순번 | 원문 표시 |
|---|---|
| 3 | 현재 간략화한 코드는 GET·POST·DELETE 성공 뒤 목록을 변경합니다. 아래 성공 흐름은 구현되어 있으며 로딩·화면 오류·중복 제출 억제는 추가할 UI 상태입니다. |
| 48 | 겹친 응답이 최신 목록을 덮어쓰지 않도록 갱신 순서·재조회·함수형 state 갱신을 설계합니다. 이 동시성 제어는 현재 간략화한 코드에 구현되어 있지 않습니다. |
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| 빈 배열로 시작 현재 목록 예제는 초기 빈 배열로 렌더합니다. | 목록: [] · 요청 상태 UI: 별도 추가 필요 | 목록 출발 상태가 설정됩니다. |
| GET 대기 GET이 진행되어도 현재 목록 배열은 아직 바뀌지 않습니다. | 목록: [] · 대기: 배열만으로 구분 불가 | 네트워크 작업과 표시 배열이 분리됩니다. |
| 성공 적용 또는 실패 유지 성공은 서버 목록을 적용하고 실패는 로그만 남기므로 처음의 빈 배열도 유지될 수 있습니다. | 성공: 서버 목록 · 실패: 기존 목록 유지 · 오류 UI: 현재 없음 | 성공 분기에서만 표시 자료가 교체됩니다. |
import React, { useEffect, useState } from 'react';
import api from '../api/axiosInstance';
import { useAuth } from '../context/AuthContext';
import { Link, useNavigate } from 'react-router-dom';
interface Project {
id: string;
name: string;
createdAt: string;
updatedAt: string;
}
const ProjectsPage: React.FC = () => {
const { user, logout } = useAuth();
const [projects, setProjects] = useState<Project[]>([]);
const [newProjectName, setNewProjectName] = useState('');
const navigate = useNavigate();
useEffect(() => {
const fetchProjects = async () => {
try {
const response = await api.get('/projects');
setProjects(response.data);
} catch (error) {
console.error('Failed to fetch projects:', error);
}
};
fetchProjects();
}, []);
const handleCreateProject = async (e: React.FormEvent) => {
e.preventDefault();
if (!newProjectName.trim()) return;
try {
const response = await api.post('/projects', { name: newProjectName });
setProjects([...projects, response.data]);
setNewProjectName('');
navigate(`/project/${response.data.id}`); // 생성 후 바로 에디터 페이지로 이동
} catch (error) {
console.error('Failed to create project:', error);
}
};
const handleDeleteProject = async (projectId: string) => {
if (!window.confirm('Are you sure you want to delete this project?')) return;
try {
await api.delete(`/projects/${projectId}`);
setProjects(projects.filter(p => p.id !== projectId));
} catch (error) {
console.error('Failed to delete project:', error);
}
};
return (
<div>
<h2>Welcome, {user?.nickname || user?.email}!</h2>
<button onClick={logout}>Logout</button>
<h3>Create New Project</h3>
<form onSubmit={handleCreateProject}>
<input
type="text"
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="Project Name"
required
/>
<button type="submit">Create</button>
</form>
<h3>Your Projects</h3>
{projects.length === 0 ? (
<p>No projects yet. Create one!</p>
) : (
<ul>
{projects.map((project) => (
<li key={project.id}>
<Link to={`/project/${project.id}`}>{project.name}</Link> ({new Date(project.updatedAt).toLocaleDateString()})
<button onClick={() => handleDeleteProject(project.id)} style={{ marginLeft: '10px' }}>Delete</button>
</li>
))}
</ul>
)}
</div>
);
};
export default ProjectsPage;코드와 화면 결과 연결하기
projects의 초기값은 빈 배열입니다. 첫 GET을 기다리는 동안에도, GET이 실패해 console.error만 실행된 뒤에도 같은 빈 배열이 남습니다. 따라서 현재 화면의 “No projects yet”는 서버가 성공적으로 0건을 반환했다는 증거가 아닙니다. 조회 중·조회 실패·성공한 빈 목록을 구분하려면 목록 데이터와 별도로 그 요청의 상태를 추가해야 합니다.
생성·삭제는 성공 응답을 기다린 뒤 목록을 바꿉니다. 생성 요청의 trim() 검사는 빈 입력을 막지만 서버 DTO의 최소 3자 검사를 대신하지 않습니다. 서버 검증이 준비된 환경에서 두 글자 이름이 거부되면 성공 경로의 목록 추가·이동이 실행되지 않는지 따라가 보세요. 목록에 추가된 순서도 서버의 updatedAt DESC 정렬을 다시 계산한 결과는 아닙니다.
독립 확인 문제
기존 프로젝트 한 개를 표시한 상태에서 삭제 확인을 취소한 경우와, 확인 뒤 DELETE가 실패한 경우를 비교해 보세요. HTTP 요청 유무와 현재 코드의 목록 결과는 각각 무엇인가요? 실패를 화면에서 구분하려면 무엇을 추가해야 할까요?
핸들러의 중단 위치 확인
확인을 취소하면 return하여 DELETE를 보내지 않고 기존 항목도 유지합니다. 확인했지만 요청이 실패하면 catch로 이동하므로 filter()에 의한 목록 갱신이 실행되지 않고 로그만 남습니다. 둘 다 항목이 남더라도 원인은 다릅니다. 요청 실패를 사용자에게 알려 주는 오류 상태와 재시도 동작이 필요합니다. 요청을 보낸 뒤 응답을 기다리는 상태와 중복 제출 억제도 현재 예제에는 없습니다.