Skip to content

React Hooks

@teamsparta/react에서 제공하는 커스텀 훅 모음입니다.


useBoolean

boolean 상태값을 관리하기 위한 훅입니다.

API

typescript
function useBoolean(defaultValue?: boolean): {
  value: boolean;
  setTrue: () => void;
  setFalse: () => void;
  toggle: () => void;
};

사용 예시

tsx
const { value, setTrue, setFalse, toggle } = useBoolean(false);

return (
  <div>
    <p>{value.toString()}</p>
    <button onClick={setTrue}>True</button>
    <button onClick={setFalse}>False</button>
    <button onClick={toggle}>Toggle</button>
  </div>
);

useToggle

boolean 상태를 간편하게 토글하는 훅입니다. useReducer 기반으로 동작합니다.

API

typescript
function useToggle(defaultValue?: boolean): [state: boolean, toggle: () => void];

사용 예시

tsx
const [isOpen, toggle] = useToggle();

return (
  <div>
    <p>{isOpen ? '열림' : '닫힘'}</p>
    <button onClick={toggle}>토글</button>
  </div>
);

useClickAway

특정 요소 외부 클릭을 감지하는 훅입니다.

API

typescript
function useClickAway<T extends HTMLElement = HTMLElement>(
  callback: () => void
): React.RefObject<T>;

사용 예시

tsx
const ref = useClickAway<HTMLDivElement>(() => {
  console.log('외부 클릭!');
});

return <div ref={ref}>이 영역 외부 클릭 시 콜백 실행</div>;

useDebounce

입력값의 변경을 지정된 시간만큼 지연시키는 훅입니다.

API

typescript
function useDebounce<T>(value: T, delay?: number): T;
// delay 기본값: 500 (ms)

사용 예시

tsx
const [value, setValue] = useState('');
const debouncedValue = useDebounce(value, 500);

// debouncedValue는 value 변경 후 500ms 뒤에 업데이트

useThrottle

입력값을 지정된 시간 동안 제한된 속도로 반환하는 훅입니다.

API

typescript
function useThrottle<T>(value: T, delay?: number): T;
// delay 기본값: 1000 (ms)

사용 예시

tsx
const [value, setValue] = useState('');
const throttledValue = useThrottle(value, 1000);

usePreserveCallback

콜백 함수의 레퍼런스를 보존하여 불필요한 리렌더링을 방지하는 훅입니다.

API

typescript
function usePreserveCallback<T extends (...args: any[]) => any>(
  callback: T
): (...args: Parameters<T>) => void;

사용 예시

tsx
const useSomething = (someCallback: () => void) => {
  const callback = usePreserveCallback(someCallback);

  useEffect(() => {
    callback();
  }, [callback]); // 콜백 레퍼런스가 변경되지 않아 불필요한 재실행 방지
};

useTimeout

지정된 시간 후 콜백을 실행하는 훅입니다.

API

typescript
function useTimeout(props: {
  callback: () => void;
  delay?: number; // 기본값: 0 (ms)
}): void;

사용 예시

tsx
useTimeout({
  callback: () => setTitle('로딩 중...'),
  delay: 2000,
});

useInterval

주기적으로 콜백을 실행하는 타이머 훅입니다.

API

typescript
interface UseIntervalProps {
  onTick: () => void;
  delay: number;        // 밀리초, 0보다 커야 함
  disabled?: boolean;   // 기본값: false
  immediate?: boolean;  // 기본값: false, true면 즉시 첫 실행
}

function useInterval(props: UseIntervalProps): void;

사용 예시

tsx
useInterval({
  onTick: () => fetchData(),
  delay: 5000,
  immediate: true,
});

useTimer

감소하는 카운트다운 타이머 훅입니다.

API

typescript
interface UseTimerProps {
  expireTime?: number;
  onStart?: () => void;
  onReset?: () => void;
  onPause?: () => void;
  onExpire?: () => void;
  syncTime?: boolean;       // 기본값: false
  autoStart?: boolean;      // 기본값: false
  interval?: number;        // 기본값: 1000 (ms)
  decrementValue?: number;  // 기본값: 1
}

function useTimer(props: UseTimerProps): {
  isRunning: boolean;
  time: number;
  start: () => void;
  reset: () => void;
  pause: () => void;
};

사용 예시

tsx
const { isRunning, time, start, reset, pause } = useTimer({
  expireTime: 60,
  autoStart: true,
  onExpire: () => alert('시간 종료!'),
});

return <div>{time}초 남음</div>;

useStopwatch

증가하는 스톱워치 훅입니다.

API

typescript
interface UseStopwatchProps {
  initialTime?: number;
  onStart?: () => void;
  onStop?: () => void;
  onPause?: () => void;
  onTick?: (time: number) => void;
  syncTime?: boolean;   // 기본값: false
  autoStart?: boolean;  // 기본값: false
}

function useStopwatch(props: UseStopwatchProps): {
  isRunning: boolean;
  time: number;
  start: () => void;
  stop: () => void;
  pause: () => void;
};

사용 예시

tsx
const { isRunning, time, start, stop, pause } = useStopwatch({
  initialTime: 0,
  autoStart: true,
  onTick: (t) => console.log(`${t}초 경과`),
});

useDocumentVisibility

문서의 가시성 상태(탭 전환) 변경을 감지하는 훅입니다.

API

typescript
function useDocumentVisibility(props: {
  onVisibilityChange: (visibility: 'visible' | 'hidden') => void;
}): void;

사용 예시

tsx
useDocumentVisibility({
  onVisibilityChange: (visibility) => {
    if (visibility === 'hidden') pauseVideo();
    else playVideo();
  },
});

useIntersectionObserver

Intersection Observer API를 사용하여 요소의 가시성 변화를 관찰하는 훅입니다.

API

typescript
interface UseIntersectionObserverProps {
  root?: IntersectionObserverInit['root'];
  rootMargin?: IntersectionObserverInit['rootMargin'];
  threshold?: IntersectionObserverInit['threshold'];
  onChange?: (entry: IntersectionObserverEntry) => void;
  onEnter?: () => void;
  onLeave?: () => void;
  /** @deprecated unobserveOnLeave 사용 권장 */
  unobserveOnEnter?: boolean;
  unobserveOnLeave?: boolean;
}

function useIntersectionObserver<E extends HTMLElement>(
  props: UseIntersectionObserverProps
): {
  ref: React.RefObject<E>;
  observe: () => void;
  unobserve: () => void;
};

사용 예시

tsx
const { ref } = useIntersectionObserver<HTMLDivElement>({
  onEnter: () => console.log('요소가 화면에 나타남'),
  onLeave: () => console.log('요소가 화면에서 사라짐'),
  threshold: 0.5,
});

return <div ref={ref}>관찰 대상</div>;

useResizeObserver

Resize Observer API를 사용하여 요소의 크기 변화를 관찰하는 훅입니다.

API

typescript
function useResizeObserver<E extends HTMLElement = HTMLDivElement>(props: {
  onResize?: (entry: ResizeObserverEntry) => void;
}): React.RefObject<E>;

사용 예시

tsx
const containerRef = useResizeObserver({
  onResize: (entry) => {
    console.log('너비:', entry.contentRect.width);
  },
});

return <div ref={containerRef}>크기가 변하는 요소</div>;

useHover

마우스 hover 상태를 감지하는 훅입니다.

API

typescript
interface UseHoverProps {
  onHoverStart?: () => void;
  onHoverEnd?: () => void;
}

function useHover<T extends HTMLElement>(
  props?: UseHoverProps
): {
  targetRef: React.RefObject<T>;
  isHovered: boolean;
};

사용 예시

tsx
const { targetRef, isHovered } = useHover<HTMLDivElement>({
  onHoverStart: () => console.log('hover 시작'),
  onHoverEnd: () => console.log('hover 종료'),
});

return (
  <div ref={targetRef} style={{ background: isHovered ? 'blue' : 'white' }}>
    Hover me
  </div>
);

useMediaQuery

미디어 쿼리의 일치 여부를 구독하는 훅입니다. useSyncExternalStore 기반으로 SSR 안전합니다.

API

typescript
type MediaQuery = ScreenSizeQuery | DisplayOrientationQuery | DeviceInputQuery
  | DisplayQualityQuery | UserPreferenceQuery | EnvironmentQuery | (string & {});

function useMediaQuery(query: MediaQuery, defaultValue?: boolean): boolean;

참고: useMediaQuery.ts 모듈에는 media 유틸리티 객체와 편의 훅(useMinWidth, useMaxWidth, useBetweenWidth 등)이 함께 정의되어 있지만, 패키지 진입점(@teamsparta/react)에서는 useMediaQuery만 re-export됩니다. 편의 훅이 필요한 경우 useMediaQuery와 직접 미디어 쿼리 문자열을 조합하여 사용하세요.

사용 예시

tsx
const isMobile = useMediaQuery('(max-width: 768px)', false);
const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)', false);

useLockBodyScroll

페이지의 스크롤을 제한하는 훅입니다. 모달이나 팝업에서 사용합니다.

API

typescript
interface ScrollLockOptions {
  isLocked?: boolean;          // 기본값: false
  preventShift?: boolean;      // 기본값: true, 스크롤바 너비만큼 패딩 추가
  preventTouchScroll?: boolean; // 기본값: true, iOS Safari 터치 스크롤 방지
}

function useLockBodyScroll(options?: ScrollLockOptions): void;

사용 예시

tsx
function Modal({ isOpen }) {
  useLockBodyScroll({ isLocked: isOpen });
  return isOpen ? <div>Modal Content</div> : null;
}

useIsClient

현재 환경이 클라이언트(브라우저)인지 확인하는 훅입니다. useSyncExternalStore 기반으로 SSR 안전합니다.

API

typescript
function useIsClient(): boolean;
// 서버: false, 클라이언트: true

사용 예시

tsx
const isClient = useIsClient();

if (!isClient) return <div>Loading...</div>;
return <div>클라이언트에서만 보이는 컨텐츠</div>;

useIsomorphicLayoutEffect

SSR과 클라이언트 환경에서 모두 안전하게 동작하는 useLayoutEffect입니다.

API

typescript
const useIsomorphicLayoutEffect: typeof useLayoutEffect;
// 클라이언트: useLayoutEffect, 서버: useEffect

사용 예시

tsx
useIsomorphicLayoutEffect(() => {
  // DOM 측정이 필요한 코드
}, []);

useFunnel

퍼널(단계별 흐름) UI를 관리하는 훅입니다.

API

typescript
type NonEmptyArray<T> = readonly [T, ...T[]];

function useFunnel<Steps extends NonEmptyArray<string>>(props: {
  steps: Steps;
  initialStep?: Steps[number];        // 기본값: steps[0]
  tracingExternalStep?: boolean;       // 기본값: false
  externalStep?: Steps[number];
}): readonly [
  FunnelComponent & { Step: StepComponent },
  React.Dispatch<React.SetStateAction<Steps[number]>>,
  Steps[number]
];

// FunnelComponent props (step은 자동 주입)
type FunnelProps<Steps> = {
  children: ReactElement<StepProps<Steps>>[] | ReactElement<StepProps<Steps>>;
};

// Step props
type StepProps<Steps> = {
  name: Steps[number];
  onEnter?: () => void;
  children: ReactNode;
};

사용 예시

tsx
const [Funnel, setStep, currentStep] = useFunnel({
  steps: ['info', 'address', 'confirm'] as const,
  initialStep: 'info',
});

return (
  <Funnel>
    <Funnel.Step name="info">
      <InfoForm onNext={() => setStep('address')} />
    </Funnel.Step>
    <Funnel.Step name="address">
      <AddressForm onNext={() => setStep('confirm')} />
    </Funnel.Step>
    <Funnel.Step name="confirm">
      <ConfirmPage />
    </Funnel.Step>
  </Funnel>
);