Skip to content

depromeet/18th-team1-FE

Repository files navigation

first-penguin-fe

책 속 문장으로 나의 하루를 기록하는 일기 앱 — 프론트엔드 모노레포

앱 실행

pnpm --filter web dev      # http://localhost:3000
pnpm --filter mobile start # Expo Go 앱으로 확인
pnpm turbo dev             # 둘 다 동시 실행

테스트

pnpm turbo test

Zustand 사용법

슬라이스 추가

store/slices/ 에 새 슬라이스 파일을 추가한다.

// store/slices/exampleSlice.ts
import { StateCreator } from 'zustand';
import type { TBoundStore, TStoreMutators } from '../types';

export interface TExampleSlice {
  count: number;
  increment: () => void;
}

export const createExampleSlice: StateCreator<TBoundStore, TStoreMutators, [], TExampleSlice> = (set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
});

store/types.ts 에 타입 추가:

import type { TExampleSlice } from './slices/exampleSlice';

export type TBoundStore = TDiarySlice & TAuthSlice & TExampleSlice;

store/useStore.ts 에 슬라이스 연결:

(...a) => ({
  ...createDiarySlice(...a),
  ...createAuthSlice(...a),
  ...createExampleSlice(...a),  // 추가
}),

컴포넌트에서 사용

'use client';

import useStore from '@/store/useStore';

export default function ExampleComponent() {
  // createSelectors로 생성된 .use.{key}() 셀렉터 사용 — 해당 값 변경 시에만 리렌더
  const count = useStore.use.count();
  const increment = useStore.use.increment();

  return <button onClick={increment}>{count}</button>;
}

TanStack Query 사용법

Query Key 추가

packages/api/src/queryKey.ts 에 도메인별 키 팩토리를 추가한다.

export const exampleKeys = {
  all: ['examples'] as const,
  list: () => ['examples', 'list'] as const,
  detail: (id: string) => ['examples', id] as const,
};

useQuery 훅 작성

'use client';

import { useQuery } from '@tanstack/react-query';
import { diaryKeys } from '@first-penguin/api';
import { apiClient } from '@first-penguin/api';
import type { Diary } from '@first-penguin/types';

export function useDiaryDetail(id: string) {
  return useQuery({
    queryKey: diaryKeys.detail(id),
    queryFn: () => apiClient.get<Diary>(`/diaries/${id}`),
  });
}

useMutation 훅 작성

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { diaryKeys } from '@first-penguin/api';
import { apiClient } from '@first-penguin/api';
import type { CreateDiaryRequest, Diary } from '@first-penguin/types';

export function useCreateDiary() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (body: CreateDiaryRequest) =>
      apiClient.post<Diary>('/diaries', body),
    onSuccess: () => {
      // 성공 시 캘린더 목록 자동 리페치
      queryClient.invalidateQueries({ queryKey: diaryKeys.list() });
    },
  });
}

컴포넌트에서 사용

'use client';

export default function DiaryDetailPage({ id }: { id: string }) {
  const { data, isLoading, isError } = useDiaryDetail(id);

  if (isLoading) return <p>로딩 중...</p>;
  if (isError) return <p>오류 발생</p>; // 전역 에러는 QueryProvider가 처리

  return <p>{data?.quote.text}</p>;
}

특정 쿼리 에러를 직접 핸들링하고 싶을 때

meta.skipGlobalError: true 를 설정하면 QueryProvider의 전역 에러 핸들러를 건너뛴다.

useQuery({
  queryKey: diaryKeys.detail(id),
  queryFn: () => apiClient.get(`/diaries/${id}`),
  meta: { skipGlobalError: true }, // 이 쿼리는 에러를 직접 처리
});

패키지 구조

apps/
  web/      Next.js 15 (App Router)
  mobile/   Expo (React Native)
packages/
  types/    공유 TypeScript 타입
  api/      TanStack Query 키 팩토리 + fetch 클라이언트
  utils/    날짜 포맷, 텍스트, HttpError 유틸
  ui/       Web 전용 컴포넌트 (shadcn/ui)

About

18기 1팀 퍼스트펭귄 팀 Frontend 레포

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors