저번 포스팅에는 recoil을 설치하고, nextjs에서 세팅하는 방법과 기본적인 recoil의 사용법에 대해 포스팅을 진행했다. 해당 포스팅을 하면서 recoil을 통해 애플리케이션의 모달 상태를 전역으로 관리하는 방법에 대해서 포스팅을 진행할 것이라고 언급했는데, 이 포스팅에서 한 번 소개해보도록 하겠다.
현재 진행중인 프로젝트는, 로그인 유저와 비로그인 유저에 따라서 처리해야할 로직이 존재한다. 로그인 유저는 애플리케이션의 모든 기능에 접근이 가능하지만, 비로그인 유저에 한해서는 특정 기능에서, 로그인이 필요하다는 모달을 띄워줘야만 했다. recoil을 통해 모달의 상태를 전역관리 하기 이전에는, 각 페이지에서 작성한 모달 컴포넌트와 모달의 상태 함수인 setState 함수를 관련 컴포넌트에 props로 넘겨줘야했기 때문에, recoil을 사용하여 불필요한 props drilling을 제거하고 유지보수를 편리하게 수정했다.
모달 atom 생성
import { atom } from 'recoil';
export const modalState = atom<boolean>({
key: 'modalState',
default: false,
});
우선적으로, 애플리케이션에서 전역으로 관리할 모달의 atom을 선언한다.
상위 컴포넌트에 모달 import
자주 언급했다싶이 nextjs는 _app.tsx라는 파일은 모든 페이지의 최상위 컴포넌트로, 애플리케이션의 전역 상태관리와 공통된 레이아웃을 작성할 수 있는 파일이다. 추가적으로 지난 포스팅에서 이야기했던 것처럼 recoil을 사용하기 위해서는 recoilRoot를 최상위 컴포넌트, 일반적으로 _app.tsx파일에서 선언하도록하는데 처음에는 recoilRoot를 간과하고 _app.tsx 파일에 모달 컴포넌트와 관련 recoilState를 임포트하니까 다음과 같은 에러를 마주쳤다.
당연하게도 해당 상태는 recoilRoot의 하위에 존재하는 상태가 아니기 때문에 다음과 같은 에러가 발생하는 것이다. 따라서 공통적으로 사용되는 페이지를 보니 애플리케이션의 레이아웃 컴포넌트가 공통적으로 사용되는 것을 알 수 있었고, 다음과 같이 레이아웃 컴포넌트에서 recoilState를 사용하도록 했다.
// _app.tsx
import '@styles/globals.css';
import tw from 'tailwind-styled-components';
import Layout from '@components/layout/Layout';
import type { AppProps } from 'next/app';
import { NextPage } from 'next';
import { ReactElement, ReactNode } from 'react';
import { Hydrate, QueryClient, QueryClientProvider } from 'react-query';
import { RecoilRoot } from 'recoil';
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
const queryClient = new QueryClient();
export default function App({ Component, pageProps }: AppPropsWithLayout) {
return (
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<Layout>
<Component {...pageProps} />
</Layout>
</Hydrate>
</QueryClientProvider>
</RecoilRoot>
);
}
export { queryClient };
// Layout.tsx
import React from 'react';
import tw from 'tailwind-styled-components';
import Modal from '@components/common/Modal';
import { useRecoilState } from 'recoil';
import { modalState } from '@recoil/modalAtom';
type Props = {
children: React.ReactNode;
};
const LayoutBox = tw.div`
flex justify-center w-screen h-full font-Pretendard overflow-x-hidden overflow-y-hidden
`;
const Layout = ({ children }: Props) => {
const [isModal, setIsModal] = useRecoilState(modalState);
return (
<LayoutBox>
{isModal && (
<Modal
isModal={true}
isKakao={true}
title="로그인 상태가 아니에요!"
message="해당 페이지는 카카오톡 로그인을 하셔야 이용가능한 페이지에요. 로그인 하시겠어요?"
left="아니요"
leftEvent={() => setIsModal(false)}
/>
)}
{children}
</LayoutBox>
);
};
export default Layout;
위처럼 레이아웃 컴포넌트에서 useRecoilState의 인자로 modal atom을 전달하고, Layout 컴포넌트는 모든 페이지에 대한 공통적인 Layout이기 때문에 boolean 값인 해당 상태에 따라 모달이 열리고 닫히게끔 구현하여 반복적인 모달 컴포넌트 작성을 피하게 했다.
특정 컴포넌트에서 모달 state 변경
이제 특정 컴포넌트에서 모달을 열리게끔 하기위해 useSetRecoilState를 사용해보도록 하겠다. useSetRecoilState는 recoil에서 제공하는 훅으로 인자로 받는 atom의 상태를 변경하는 함수를 반환한다. 따라서 해당 코드 처럼 모달의 상태를 변경하는 함수를 통해 모달을 조작하는 것이 가능하다.
...
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { modalState } from '@recoil/modalAtom';
const ShopDetail = ({ shopId, userLat, userLng }) => {
...
const setIsModal = useSetRecoilState(modalState);
return (
<ShopLayout>
...
<Button
handleButton={() => {
if (!isLogin) setIsModal(() => true);
}}
/>
</ShopLayout>
);
};
export default ShopDetail;
결과적으로 다음과 같이 모달을 전역으로 관리할 수 있다.
이렇게 오늘은 recoil을 통해 모달을 애플리케이션 전역에서 관리하는 방법에 대해서 알아보았다. 모달이외에도 recoil을 통해 로그인 상태나 애플리케이션에서 전역으로 관리해야할 상태가 존재한다면 이 포스팅이 도움이 되었으면 좋겠다.
'개발' 카테고리의 다른 글
[개발] Nextjs에서 SEO 최적화하기 (0) | 2023.06.02 |
---|---|
[개발] Nextjs Link 컴포넌트와 <a> 태그 (feat. 이메일 링크걸기) (0) | 2023.05.28 |
[개발] 페이지 접근 제한 구현 (0) | 2023.04.17 |
[React] Debounce 훅 구현하기 (0) | 2023.04.13 |
[개발] Nextjs Script 컴포넌트 사용 방법 (feat. 카카오맵) (0) | 2023.04.05 |