할 일 목록
JSONPlaceholder의 할 일 200개를 필터, DataGrid, Pagination으로 보여 주고, 하네스 규칙대로 레이어를 나눈 사례예요.
화면
필터와 페이지는 URL에 남아서 새로고침하거나 링크를 보내도 같은 화면이 나와요.
할 일
사용자, 상태, 검색어로 골라 봐요.| 사용자 | 상태 | ||
|---|---|---|---|
4가지 상태
정상은 위 화면이에요. 필터 줄은 그대로 두고 표 자리만 바꾸고, 빈 상태와 에러에서는 Pagination을 숨겨요.
로딩 · 처음 불러올 때 같은 배치에 자리만 표시
| 사용자 | 상태 | ||
|---|---|---|---|
빈 상태 · 조건에 맞는 할 일이 없을 때
| 사용자 | 상태 | ||
|---|---|---|---|
![]() 조건에 맞는 할 일이 없어요 검색어를 지우거나 다른 사용자를 골라 보세요. | |||
에러 · 불러오지 못했을 때
| 사용자 | 상태 | ||
|---|---|---|---|
![]() 할 일을 불러오지 못했어요 네트워크 연결을 확인하고 다시 시도해 주세요. | |||
코드 구조
조회는 entities, 둘을 합치는 건 widgets, URL 상태와 Suspense·ErrorBoundary는 pages가 맡아요. 필터 값은 URL에서 API 쿼리로 그대로 바뀌어요.
text
src/
├─ entities/todo/ model · api(todo.api · todo.queries · todo.mock) · hooks
├─ entities/user/ api(user.queries · user.mock) · hooks
├─ widgets/todo-grid/ ui/todo-grid.tsx ← todo와 user를 합쳐 DataGrid에 넘겨요
├─ pages/case-studies/ model/use-todo-list-search-params.ts (nuqs)
│ ui/todos-page.tsx (prefetch · Suspense · ErrorBoundary)
└─ app/mocks/handlers.ts todoHandlers · userHandlers
todoQueries.list(params) → useSuspenseQuery → <DataGrid rowCount={total} />| URL | 필터 | API 쿼리 |
|---|---|---|
| q=quis | 검색 Input | q=quis |
| userId=3 | 사용자 Select | userId=3 |
| status=done | 상태 ToggleGroup | completed=true |
| sort=title.desc | 머리글 정렬 | _sort=title&_order=desc |
| page=2&size=10 | Pagination | _page=2&_limit=10 |
결정
- 정렬과 페이지는 서버가 해요DataGrid에 rowCount를 넘겨 서버 모드로 써요. 받은 한 페이지만 정렬하면 전체 순서와 달라져요.
- 필터를 바꾸면 1페이지로검색어, 사용자, 상태가 바뀌면 page를 1로 되돌려요. 없는 페이지에 머무르지 않아요.
- 페이지를 넘겨도 이전 화면을 유지해요새 조건은 useDeferredValue로 넘겨서, 불러오는 동안 이전 결과를 보여 줘요. Skeleton은 처음 불러올 때만 보여요.
- 검색은 입력을 멈추고 300ms 뒤글자마다 요청하지 않도록 URL 갱신을 debounce해요.
- 사용자 이름은 widget에서 합쳐요todo와 user는 서로 다른 entity라 서로 가져오지 않아요. 둘을 합치는 건 widgets/todo-grid예요.
쓴 컴포넌트
- DataGrid
- Pagination
- Input
- Select
- ToggleGroup
- Badge
- Skeleton
- EmptyState
- ErrorState

