개발자
류준열

훅, 컴포넌트 밖에서 사용가능한 toast 유틸 만들기

React에서 useToast()를 커스텀 훅으로 만들어쓰고 있었다.

const toast = useToast();

toast({
  type: "success",
  content: "저장되었습니다!",
});

useToast 훅의 문제점

혹은 컴포넌트 내부에서만 호출 할 수 있다는 제약때문에 React 외부에서는 토스트를 띄울 수 없다.

이때의 문제점은 아래와 같이 axios intercepter 내부의 토큰만료 안내를 할 때는 브라우저 alert를 사용해야한다는 것이다.

axios.interceptors.response.use(
  (res) => res,
  async (error: AuthAxiosError) => {
    ...
    const isTokenExpired =
      status === 401 
			
    if (isTokenExpired && error.config.url === REFRESH_API_URL) {
      ...
      alert("세션이 만료되어 로그인 페이지로 이동합니다.");
      logout();
      ...
    }
  },
);

우리팀의 제품은 일관된 UI를 지향하는데, React의 제약에 체념한 상태였다.

하지만 'this는 메소드가 자신을 호출한 객체를 가리킨다' 는 성질을 이용하여 React 외부에서도 토스트를 띄울 수 있다.

전역 toast 유틸 객체 만들기

utils폴더안에 toast.ts를 다음과 같이 만들자.


// toast.ts

const toast = {
  showToast: (newToastOption: ToastOptionType) => {
    console.warn("ToastProvider가 아직 초기화되지 않았습니다.");
  },

  _setFunctions(add: (newToastOption: ToastOptionType) => void) {
    this.showToast = add;
  },
};

export default toast;

이 toast 객체는 React 바깥을 포함한 모든 전역에서 접근 가능하다. 초기에는 아무 기능도 없지만, 아래와 같이 Provider가 마운트되면서 내부 함수를 주입한다.

Provider안에서 함수 주입과 this 바인딩


export const Provider= ({...}:ToastProps) => {
  ...
	const showToast = useToast();
	
  useEffect(() => {
    toast._setFunctions(showToast);
  }, []);
	
	...
	
	return <>{children}</>
}

toast._setFunction는 this가 해당 객체(toast)를 가리킨다. 덕분에 toast.showToast에 내부 함수를 주입 할 수 있다.

사용방식

// utils/handleError.ts

import toast from "@/utils/toast";

export function handleError(message: string) {
  toast.showToast({
    type: "error",
    content: `에러 발생: ${message}`,
  });
}

정리

React 커스텀 훅은 범위에 제한이 있다. 그 한계를 벗아나기 위해서는 this 바인딩을 활용하는 것이 좋은 해결책이 될 수 있다.

이런 패턴은 toast뿐 아니라, 전역 알림, 로딩 처리등 모든 컴포넌트에 확장할 수 있다.

이 패턴을 팀원들에게 공유하여 모달, alert 등에 적용했고 컴포넌트 호출을 메서드로 처리하여 개발 생산성을 높힐 수 있었다.