Skip to content

Conversation

@dev-learning1
Copy link

@dev-learning1 dev-learning1 commented Dec 12, 2025

과제 체크포인트

기본과제

목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기

  • 전역상태관리를 사용해서 상태를 분리하고 관리하는 방법에 대한 이해
  • Context API, Jotai, Zustand 등 상태관리 라이브러리 사용하기
  • FSD(Feature-Sliced Design)에 대한 이해
  • FSD를 통한 관심사의 분리에 대한 이해
  • 단일책임과 역할이란 무엇인가?
  • 관심사를 하나만 가지고 있는가?
  • 어디에 무엇을 넣어야 하는가?

체크포인트

  • 전역상태관리를 사용해서 상태를 분리하고 관리했나요?
  • Props Drilling을 최소화했나요?
  • shared 공통 컴포넌트를 분리했나요?
  • shared 공통 로직을 분리했나요?
  • entities를 중심으로 type을 정의하고 model을 분리했나요?
  • entities를 중심으로 ui를 분리했나요?
  • entities를 중심으로 api를 분리했나요?
  • feature를 중심으로 사용자행동(이벤트 처리)를 분리했나요?
  • feature를 중심으로 ui를 분리했나요?
  • feature를 중심으로 api를 분리했나요?
  • widget을 중심으로 데이터를 재사용가능한 형태로 분리했나요?

심화과제

목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기

  • TanstackQuery의 사용법에 대한 이해
  • TanstackQuery를 이용한 비동기 코드 작성에 대한 이해
  • 비동기 코드를 선언적인 함수형 프로그래밍으로 작성하는 방법에 대한 이해

체크포인트

  • 모든 API 호출이 TanStack Query의 useQuery와 useMutation으로 대체되었는가?
  • 쿼리 키가 적절히 설정되었는가?
  • fetch와 useState가 아닌 선언적인 함수형 프로그래밍이 적절히 적용되었는가?
  • 캐싱과 리프레시 전략이 올바르게 구현되었는가?
  • 낙관적인 업데이트가 적용되었는가?
  • 에러 핸들링이 적절히 구현되었는가?
  • 서버 상태와 클라이언트 상태가 명확히 분리되었는가?
  • 코드가 간결하고 유지보수가 용이한 구조로 작성되었는가?
  • TanStack Query의 Devtools가 정상적으로 작동하는가?

최종과제

  • 폴더구조와 나의 멘탈모데일이 일치하나요?
  • 다른 사람이 봐도 이해하기 쉬운 구조인가요?

과제 셀프회고

이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.

  • FSD 아키텍처를 통한 체계적인 폴더 구조의 중요성
  • TanStack Query의 캐시 전략과 낙관적 업데이트의 효과
  • Mock API 환경에서의 캐시 관리 주의점
  • 일관된 명명 규칙 적용 (useQuery*, useMutation*)

본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?

  • 각 레이어의 책임을 명확히 분리하고 단일 책임 원칙 준수
  • TanStack Query 캐시를 직접 갱신하여 즉각적인 UI 반응 구현

핵심 성과:

  • PostsManagerPage: 700+ 줄 → 230 줄 (67% 감소)
  • 컴포넌트 재사용성 향상
  • 단일 책임 원칙 준수
  • Props Drilling 최소화
  • 서버 상태와 클라이언트 상태 명확히 분리

📂 프로젝트 폴더 구조

src/
├── app/                      # 애플리케이션 진입점 및 전역 설정
│   └── providers/
│       └── QueryProvider.tsx # TanStack Query 설정
│
├── pages/                    # 페이지 레벨 컴포넌트
│   └── posts-manager/
│       └── ui/
│           └── PostsManagerPage.tsx
│
├── widgets/                  # 복합 UI 블록 (재사용 가능한 컴포넌트 조합)
│   ├── post-table/
│   │   └── ui/
│   │       └── PostTable.tsx
│   ├── post-detail/
│   │   └── ui/
│   │       └── PostDetail.tsx
│   └── user-modal/
│       └── ui/
│           └── UserModal.tsx
│
├── features/                 # 사용자 행동 및 비즈니스 로직
│   ├── post-add/
│   │   └── ui/
│   │       └── PostAddDialog.tsx
│   ├── post-edit/
│   │   └── ui/
│   │       └── PostEditDialog.tsx
│   ├── post-search/
│   │   ├── model/
│   │   │   └── store.ts      # 검색/필터 상태 (Jotai atoms)
│   │   └── ui/
│   │       └── PostSearchBar.tsx
│   ├── comment-add/
│   │   └── ui/
│   │       └── CommentAddDialog.tsx
│   └── comment-edit/
│       └── ui/
│           └── CommentEditDialog.tsx
│
├── entities/                 # 비즈니스 엔티티 (도메인 모델)
│   ├── post/
│   │   ├── api/
│   │   │   └── index.ts      # Post API 함수
│   │   ├── model/
│   │   │   └── usePost.ts    # Post Query/Mutation 훅
│   │   └── ui/
│   │       └── PostCard.tsx
│   ├── comment/
│   │   ├── api/
│   │   │   └── index.ts
│   │   ├── model/
│   │   │   └── useComment.ts
│   │   └── ui/
│   │       └── CommentCard.tsx
│   ├── user/
│   │   ├── api/
│   │   │   └── index.ts
│   │   └── model/
│   │       └── useUser.ts
│   └── tag/
│       └── model/
│           └── useTag.ts
│
└── shared/                   # 공통 유틸리티 및 인프라
    ├── api/
    │   └── client.ts         # REST API 클라이언트
    ├── types/
    │   └── index.ts          # 공통 타입 정의
    ├── ui/                   # 기본 UI 컴포넌트
    │   ├── Button.tsx
    │   ├── Input.tsx
    │   ├── Dialog.tsx
    │   ├── Table.tsx
    │   └── ...
    └── lib/
        └── highlight.tsx     # 유틸리티 함수

🏗️ 레이어별 설계 원칙과 의도

1️⃣ app - 애플리케이션 레이어

역할: 애플리케이션 진입점 및 전역 설정 관리

구현 내용:

  • QueryProvider.tsx: TanStack Query 클라이언트 설정
    • staleTime: 60초 - 1분간 데이터를 신선하게 유지
    • retry: 1 - 실패 시 1번만 재시도
    • refetchOnWindowFocus: false - 윈도우 포커스 시 자동 갱신 비활성화

설계 의도:

  • 애플리케이션 전역에서 사용되는 Provider를 한 곳에서 관리
  • 서버 상태 관리 정책을 중앙화하여 일관성 확보
  • 향후 ErrorBoundary, Router 등 다른 Provider 추가 시 확장 용이

2️⃣ pages - 페이지 레이어

역할: 라우팅 단위의 최상위 컴포넌트 (데이터 조합 및 레이아웃 구성)

구현 내용:

  • PostsManagerPage.tsx (230줄)
    • widgets(PostTable, PostDetail, UserModal) 조합
    • features(PostSearchBar, PostAddDialog) 배치
    • 페이지네이션, 검색, 태그 필터 상태 관리 (Jotai)
    • URL 쿼리 파라미터와 상태 동기화

설계 의도:

  • 조합(Composition)에만 집중: 직접적인 비즈니스 로직이나 API 호출 없음
  • 데이터 흐름 제어: 어떤 위젯과 기능을 어떻게 배치할지 결정
  • 단일 책임: "이 페이지가 무엇을 보여주는가"에만 집중
  • 기존 700줄 코드가 여러 레이어로 분리되면서 페이지는 230줄로 축소

3️⃣ widgets - 위젯 레이어

역할: 재사용 가능한 복합 UI 블록 (여러 entities와 features 조합)

구현 내용:

  • PostTable.tsx: 게시물 목록 테이블

    • Post 엔티티 목록 렌더링
    • Like/Dislike 버튼 (useMutationPostLike/Dislike)
    • Edit/Delete 액션 버튼
    • 검색어 하이라이팅
    • 태그 필터링
  • PostDetail.tsx: 게시물 상세 및 댓글

    • 선택된 Post 표시
    • Comment 목록 렌더링
    • 댓글 CRUD 기능 통합
  • UserModal.tsx: 사용자 정보 모달

    • User 정보 표시
    • 해당 사용자의 게시물 목록

설계 의도:

  • 재사용성: 다른 페이지에서도 동일한 위젯 사용 가능
  • 독립성: props만으로 동작하며 외부 상태에 의존하지 않음
  • 데이터 조합: 여러 entities의 데이터를 의미 있는 형태로 결합
  • 단일 책임: 특정 UI 영역의 완전한 기능 제공

widgets vs features 구분 기준:

  • widgets: 여러 엔티티를 조합하여 "보여주는" 것에 집중 (PostTable, PostDetail)
  • features: 사용자의 "행동"을 처리 (PostAdd, PostEdit, CommentAdd)

4️⃣ features - 피처 레이어

역할: 사용자 행동 및 인터랙션 처리 (비즈니스 로직)

구현 내용:

A. Post 관련 기능

  • PostAddDialog.tsx: 게시물 생성 다이얼로그

    • useMutationPostAdd() 사용
    • 폼 검증 및 제출 처리
  • PostEditDialog.tsx: 게시물 수정 다이얼로그

    • useMutationPostUpdate() 사용
    • 기존 데이터 로드 및 수정

B. Comment 관련 기능

  • CommentAddDialog.tsx: 댓글 작성
  • CommentEditDialog.tsx: 댓글 수정

C. 검색 및 필터링

  • PostSearchBar.tsx: 검색 UI
  • store.ts (Jotai atoms):
    searchQueryAtom      // 검색어
    selectedTagAtom      // 선택된 태그
    paginationAtom       // 페이지네이션 상태
    sortOrderAtom        // 정렬 순서

설계 의도:

  • 사용자 중심 설계: 각 기능이 하나의 사용자 행동을 담당
  • 상태 관리 분리:
    • 전역 상태(Jotai): UI 상태 (검색어, 필터, 페이지)
    • 서버 상태(TanStack Query): API 데이터 (게시물, 댓글)
  • 재사용성: 다른 페이지에서 동일한 기능 재사용 가능
  • 단일 책임: 하나의 기능만 담당하여 테스트와 유지보수 용이

5️⃣ entities - 엔티티 레이어

역할: 비즈니스 엔티티의 데이터 모델, API, UI 표현

구조: api / model / ui 3단 분리

구현 내용:

A. Post 엔티티

entities/post/
├── api/index.ts          # POST API 함수 (getPosts, createPost, updatePost...)
├── model/usePost.ts      # TanStack Query 훅 (useQueryPosts, useMutationPostAdd...)
└── ui/PostCard.tsx       # Post 단일 UI 컴포넌트
  • api: REST API 호출 함수

    • getPosts(), searchPosts(), getPostsByTag()
    • createPost(), updatePost(), deletePost()
    • likePost(), dislikePost()
  • model: TanStack Query 훅 (src/entities/post/model/usePost.ts:7-189)

    • useQueryPosts() - 게시물 목록 조회
    • useQueryPostsSearch() - 검색
    • useQueryPostsByTag() - 태그 필터링
    • useMutationPostAdd() - 생성 (캐시 직접 업데이트)
    • useMutationPostUpdate() - 수정 (캐시 직접 업데이트)
    • useMutationPostDelete() - 삭제 (캐시 직접 업데이트)
    • useMutationPostLike() - 좋아요 (낙관적 업데이트)
    • useMutationPostDislike() - 싫어요 (낙관적 업데이트)
  • ui: Post 표현 컴포넌트

B. Comment, User, Tag 엔티티

  • 동일한 api/model/ui 구조
  • 각 엔티티의 고유한 비즈니스 로직 캡슐화

설계 의도:

  • 도메인 중심 설계: 비즈니스 엔티티를 중심으로 코드 조직화
  • 응집도: 관련된 api, model, ui가 한 곳에 모여 있어 찾기 쉬움
  • 재사용성: 어디서든 동일한 방식으로 엔티티 사용
  • 타입 안정성: 각 엔티티의 타입이 명확히 정의됨
  • 독립성: 엔티티 간 의존성 최소화

중요한 설계 결정: api/model 분리 이유

  • api: 순수한 HTTP 통신 (fetch, axios 등)
  • model: 데이터 관리 로직 (캐싱, 상태, 동기화)
  • 이렇게 분리하면 나중에 API 클라이언트를 교체하거나 캐싱 전략을 변경해도 서로 영향을 주지 않음

6️⃣ shared - 공유 레이어

역할: 프로젝트 전역에서 사용되는 공통 코드

구현 내용:

A. types - 타입 정의

// 모든 엔티티 타입과 DTO 정의
User, Post, Comment, Tag
CreatePostDto, UpdatePostDto, CreateCommentDto, UpdateCommentDto
PostsResponse, CommentsResponse, UsersResponse

B. api/client - REST API 클라이언트

export const apiClient = {
  get<T>(url: string): Promise<T>
  post<T>(url: string, data?: unknown): Promise<T>
  put<T>(url: string, data?: unknown): Promise<T>
  patch<T>(url: string, data?: unknown): Promise<T>
  delete(url: string): Promise<void>
}

C. ui - 기본 UI 컴포넌트

  • Button, Input, Textarea
  • Dialog, Card, Table
  • Select, Pagination
  • 모두 Headless UI 스타일 (비즈니스 로직 없음)

D. lib - 유틸리티 함수

  • highlightText(): 검색어 하이라이팅

설계 의도:

  • 재사용성: 프로젝트 어디서든 import 가능
  • 의존성 방향: shared는 다른 레이어에 의존하지 않음 (의존성 최하위)
  • 변경 최소화: 한 번 작성하면 거의 수정하지 않음
  • 도메인 독립성: 비즈니스 로직 없이 순수 기술적 유틸리티만 포함

🔄 상태 관리 전략

핵심 원칙: 서버 상태와 클라이언트 상태 명확히 분리

1. 서버 상태 (TanStack Query)

대상: API로부터 가져오는 모든 데이터

  • Posts, Comments, Users, Tags
  • CRUD 작업 결과

전략:

  • useQuery: 데이터 조회 및 캐싱
  • useMutation: 데이터 변경
  • 쿼리 키 설계:
    ["posts", limit, skip]           // 페이지네이션
    ["posts", "search", query]       // 검색
    ["posts", "tag", tag]            // 태그 필터
    ["comments", postId]             // 특정 게시물의 댓글
    ["users"]                        // 사용자 목록

2. 클라이언트 상태 (Jotai)

대상: UI 상태 및 사용자 인터랙션 상태

  • 검색어 (searchQueryAtom)
  • 선택된 태그 (selectedTagAtom)
  • 페이지네이션 (paginationAtom)
  • 정렬 순서 (sortOrderAtom)

장점:

  • Props Drilling 완전히 제거
  • 전역 상태를 필요한 컴포넌트에서만 구독
  • Atom 단위로 세밀한 리렌더링 제어

3. URL 상태 (선택적)

  • URL 쿼리 파라미터로 검색/필터 상태 동기화
  • 새로고침 시에도 상태 유지
  • 공유 가능한 링크 생성

왜 이렇게 분리했는가?

  1. 책임 분리: 각 도구가 가장 잘하는 일만 담당
  2. 성능 최적화: TanStack Query의 자동 캐싱/동기화 활용
  3. 개발자 경험: 서버 데이터는 선언적으로, UI 상태는 간단하게
  4. 유지보수성: 상태의 출처가 명확하여 디버깅 용이

⚡ TanStack Query 캐시 최적화 전략

문제 상황

Mock API 환경에서는 실제 데이터베이스가 없어 서버 응답을 신뢰할 수 없음.
invalidateQueries로 refetch하면 변경 사항이 반영되지 않은 기존 데이터를 다시 가져옴.

해결 방법: setQueryData를 이용한 직접 캐시 갱신

1. CRUD 작업 (Post/Comment 생성, 수정, 삭제)

// 예: Post 추가 시 (src/entities/post/model/usePost.ts:31-56)
onSuccess: (newPost) => {
  // 모든 posts 관련 쿼리 캐시를 가져옴
  const queries = queryClient.getQueriesData({ queryKey: [POST_QUERY_KEY] })

  // 각 캐시에 새 게시물 추가
  queries.forEach(([key, oldData]) => {
    if (!oldData) return
    const old = oldData as PostsResponse

    queryClient.setQueryData(key, {
      ...old,
      posts: [newPost, ...old.posts],  // 맨 앞에 추가
      total: old.total + 1,             // 총 개수 증가
    })
  })
}

장점:

  • ✅ 즉각적인 UI 업데이트 (refetch 불필요)
  • ✅ 네트워크 요청 최소화
  • ✅ 모든 관련 쿼리(검색, 태그 필터, 페이지네이션) 동시 업데이트

2. 낙관적 업데이트 (Like/Dislike)

// 예: Like 버튼 클릭 시 (src/entities/post/model/usePost.ts:106-145)
onMutate: async (variables) => {
  // 진행 중인 refetch 취소
  await queryClient.cancelQueries({ queryKey: [POST_QUERY_KEY] })

  // UI를 먼저 업데이트 (API 응답 기다리지 않음)
  const queries = queryClient.getQueriesData({ queryKey: [POST_QUERY_KEY] })

  queries.forEach(([key, oldData]) => {
    if (!oldData) return
    const old = oldData as PostsResponse

    queryClient.setQueryData(key, {
      ...old,
      posts: old.posts.map((post) =>
        post.id === variables.id
          ? {
              ...post,
              reactions: {
                ...post.reactions,
                likes: (post.reactions?.likes || 0) + 1,  // 즉시 증가
              },
            }
          : post
      ),
    })
  })
}
// onSuccess, onError 제거 - Mock 환경에서는 서버 응답 무시

장점:

  • ✅ 즉각적인 사용자 피드백 (버튼 클릭 즉시 숫자 증가)
  • ✅ 네트워크 지연 무관하게 빠른 UI 반응
  • ✅ 더 나은 사용자 경험 (UX)

3. 전체 캐시 전략 요약

작업 전략 이유
Post/Comment 조회 useQuery 자동 캐싱 및 중복 제거
Post/Comment 생성 setQueryData Mock API 환경에서 즉시 반영
Post/Comment 수정 setQueryData Mock API 환경에서 즉시 반영
Post/Comment 삭제 setQueryData Mock API 환경에서 즉시 반영
Like/Dislike 낙관적 업데이트 즉각적인 사용자 피드백
User 조회 useQuery + 5분 캐시 변경 빈도 낮아 재사용

아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.

마지막 [리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문] 에 적힌 부분

이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.

  • 신규 프로젝트에서 처음부터 FSD 구조로 설계
  • 서버 상태와 클라이언트 상태를 명확히 분리하는 습관
  • 낙관적 업데이트를 통한 UX 개선

챕터 셀프회고

클린코드와 아키테쳑 챕터 함께 하느라 고생 많으셨습니다!
지난 3주간의 여정을 돌이켜 볼 수 있도록 준비해보았습니다.
아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요.

클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기

  • 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요

결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리

  • 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요

응집도 높이기: 서버상태관리, 폴더 구조

  • "이 코드는 대체 어디에 둬야 하지?"라고 고민했던 시간, FSD를 적용해보면서의 느낌, 나만의 구조를 만들어가는 과정, TanStack Query로 서버 상태를 분리하면서 느낀 해방감(?)등을 공유해주세요

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문

  1. 검색했을 때 response가 나올 때까지 로딩화면이 나오는 것이 맞는 것인지 화면이 유지되었다가 response를 받을 때 갱신되는 것이 맞는지 잘 모르겠습니다.
  2. 추가/수정/삭제/좋아요 모두 캐시에 저장된 값이 돌아와서 화면 갱신이 된다라고 생각했습니다. 그런데 실질적으로 반환된 결과값으로 적용되는 것은 추가/수정/삭제 이고, 좋아요는 반환된 값이 origin mock데이터와 동일하더라고요. 그로 인해, 좋아요는 onSuccess를 사용하면 서버 응답과 불일치?가 되어서 onSuccess없이 queryClient.setQueryData()만 실행되어야하고, 추가/수정/삭제는 onSuccess안에서 사용이 되던데 그 이유를 잘 모르겠습니다.
    좋아요(낙관적 업데이트)
export const useMutationCommentLike = () => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ id, likes, postId }: { id: number; likes: number; postId: number }) =>
      commentApi.likeComment(id, likes),

    // 낙관적 업데이트: API 호출 전에 UI를 먼저 업데이트
    // Mock 데이터 환경에서는 서버 응답을 신뢰할 수 없으므로 낙관적 업데이트만 유지
    onMutate: async (variables) => {
      // 진행 중인 refetch 취소
      await queryClient.cancelQueries({ queryKey: [COMMENT_QUERY_KEY, variables.postId] })

      // 낙관적 업데이트: 즉시 UI 업데이트
      queryClient.setQueryData<CommentsResponse>(
        [COMMENT_QUERY_KEY, variables.postId],
        (old) => {
          if (!old) return old

          return {
            ...old,
            comments: old.comments.map((comment) =>
              comment.id === variables.id
                ? { ...comment, likes: comment.likes + 1 }
                : comment
            ),
          }
        }
      )
    },

    // Mock 데이터 환경에서는 서버 응답을 무시하고 낙관적 업데이트만 유지
    // onSuccess와 onError는 제거하여 롤백이나 서버 응답 업데이트가 발생하지 않도록 함
  })
}

추가/수정/삭제

export const useMutationCommentDelete = () => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ id, postId }: { id: number; postId: number }) =>
      commentApi.deleteComment(id),

    onSuccess: (_, variables) => {
      queryClient.setQueryData<CommentsResponse>(
        [COMMENT_QUERY_KEY, variables.postId],
        (old) => {
          if (!old) return undefined
          return {
            ...old,
            comments: old.comments.filter((comment) => comment.id !== variables.id),
            total: old.total - 1,
          }
        }
      )
    },
  })
}

dev-learning1 and others added 17 commits December 12, 2025 21:02
- Add jotai for global state management
- Add @tanstack/react-query for server state management
- Add @types/node for path module types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Add path aliases (@/*) in tsconfig.app.json
- Configure Vite path resolution
- Add CSS module type declaration in vite-env.d.ts
- Include vite-env.d.ts in tsconfig to fix CSS import errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Add type definitions for all entities (Post, Comment, User, Tag)
- Create API client with REST methods (get, post, put, patch, delete)
- Implement highlight text utility for search functionality
- Add shared UI components (Button, Input, Card, Table, Dialog, Select)
- Move Header and Footer to shared/ui

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Post entity:
- API functions for CRUD operations and filtering
- React Query hooks (useQueryPosts, useMutationPostAdd, etc.)
- PostCard UI component

Comment entity:
- API functions for comment CRUD and like operations
- React Query hooks (useQueryComments, useMutationCommentAdd, etc.)
- CommentItem UI component

User entity:
- API function for user details
- React Query hook (useQueryUser)

Tag entity:
- API function for fetching tags
- React Query hook (useQueryTags)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Global state management (Jotai):
- Create atoms for search, filter, sort, and pagination state

Post features:
- PostAddDialog: Add new post functionality
- PostEditDialog: Edit existing post functionality
- PostSearchBar: Search posts with highlighting

Comment features:
- CommentAddDialog: Add new comment functionality
- CommentEditDialog: Edit existing comment functionality

All features use TanStack Query hooks from entities layer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
PostTable widget:
- Display posts in table format
- Integrate PostCard, edit/delete actions
- Handle post selection for details

PostDetail widget:
- Show post details with comments
- Integrate comment CRUD features
- Support comment like functionality

UserModal widget:
- Display user information in modal
- Fetch user details on demand

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Reduce from 700+ lines to 230 lines
- Use Jotai atoms for global state (search, filter, sort, pagination)
- Use TanStack Query hooks for server state
- Compose features and widgets for clean separation
- Sync state with URL parameters
- Handle multiple query modes (normal, search, tag filter)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Create QueryProvider with TanStack Query configuration
- Configure query defaults (retry, refetchOnWindowFocus, staleTime)
- Update App.tsx to use new providers and FSD paths
- Wrap application with QueryProvider

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Remove src/components/Header.tsx (moved to shared/ui)
- Remove src/components/Footer.tsx (moved to shared/ui)
- Remove src/components/index.tsx (replaced by shared/ui)

All components have been reorganized into FSD structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Add getUsers() API to fetch all users with username and image
- Add useQueryUsers() hook with 5-minute cache
- Enable displaying author info in posts without redundant API calls

This improves performance by fetching users once and reusing the data
across multiple posts instead of embedding user data in each post response.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Add likePost() API to increment post likes
- Add dislikePost() API to increment post dislikes
- Use PATCH method to update reactions field

Enables interactive like/dislike functionality on posts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Replace invalidateQueries with setQueryData for immediate UI updates:

Post CRUD operations:
- Add: Insert new post at top of cache
- Update: Replace specific post in cache
- Delete: Remove specific post from cache

Like/Dislike operations:
- Use optimistic updates (onMutate) for instant UI feedback
- Update all cached queries containing the post
- Remove onSuccess to prevent server response overriding UI

Why this approach:
- Mock API environment doesn't persist changes reliably
- Optimistic updates provide better UX
- Eliminates unnecessary refetch delays

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Replace invalidateQueries with setQueryData for immediate UI updates:

Comment CRUD operations:
- Add: Insert new comment at top of cache
- Update: Replace specific comment in cache
- Delete: Remove specific comment from cache

Comment like operation:
- Use optimistic updates (onMutate) for instant UI feedback
- Cancel ongoing queries to prevent race conditions
- Remove onSuccess to prevent server response overriding UI

Why this approach:
- Mock API environment doesn't persist changes reliably
- Optimistic updates provide instant user feedback
- Eliminates delay between action and UI update
- Prevents UI flickering from refetch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Remove PostCard component (inline the post display logic)
- Add like/dislike buttons with click handlers
- Display post title, tags, author, and reactions in table cells
- Enable tag filtering by clicking on tag badges

Why this change:
- PostCard was only used in one place (not reusable)
- Direct rendering in table cells improves readability
- Like/dislike buttons now functional instead of read-only

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Fetch users once with useQueryUsers and merge with posts
- Prevent duplicate URL updates with comparison logic
- Add replace: true to navigate() to avoid history pollution

Changes:
1. User data handling:
   - Use useQueryUsers() to fetch all users once
   - Merge author data in getCurrentPosts() instead of in API
   - Reduces redundant API calls

2. URL synchronization:
   - Compare new URL params with current before navigate
   - Only update URL if params actually changed
   - Prevents unnecessary re-renders and navigation events

3. Search query in URL:
   - Add searchQuery to URL sync useEffect dependencies
   - Ensures search state persists in URL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant