서로 다른 컴포넌트에서 isInView 공유하기

3 minute read
2024-08-17

예시

랜딩페이지에서 CTA 버튼 으로 유저를 유도하는 장치가 아래와 같이 빈번하다.


image-20240817135630890


하지만 가격 안내 섹션에는 다른 버튼이 그 역할을 중복 되게 담당하고 있기 때문에 숨겨주고 싶을 때가 있다.


image-20240817135648523


코드

일단은 NextJS 에서 jotai 를 사용하기 위해서는 Provider 로 감싸서 싱글톤으로 관리되도록 할 수 있다.


세팅

src/hooks/jotai/index.tsx
import { PropsWithChildren } from 'react'
 
import { Provider } from 'jotai'
 
export const JotaiProvider = ({ children }: PropsWithChildren) => {
  return <Provider>{children}</Provider>
}


그리고 레이아웃에는 body 안에 넣어줘야 한다! 깜빡하면 삽질하게 된다.


app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ko">
      <body className={Pretendard.className}>
        <JotaiProvider>
          {children}
					{/* ... */}
        </JotaiProvider>
      </body>
    </html>
  )
}


전역 상태 선언

이제 컴포넌트 간 공유할 전역 상태를 선언해준다.


src/hooks/jotai/price.ts
import { useAtom } from 'jotai'
import { atomWithImmer } from 'jotai-immer'
 
const priceAtom = atomWithImmer(false)
export const useIsInPriceView = () => useAtom(priceAtom)


물론 위 예시에서는 immer 가 필요없다. primitive 값이기 때문에 차이가 없기 때문이다.


컴포넌트

주인공인 하단 CTA 버튼은 전역 상태에 따라 노출을 결정한다.


BottomCta.tsx
const variants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
}
 
const BottomCta = () => {
  const [isInPriceView] = useIsInPriceView()
 
  return (
    <motion.div
      initial="hidden"
      animate={isInPriceView ? 'hidden' : 'visible'}
      variants={variants}
    >
    	{/* ... */}
    </motion.div>
  )
}


그리고 다른 컴포넌트인 가격 섹션에서는 숨기려고 한다. 이 컴포넌트가 보일 때 값을 수정하면 된다.


PriceSection.tsx
const PriceSection = () => {
  const ref = useRef(null)
  const isInView = useInView(ref, {
    margin: '-25% 0px -25% 0px',
    // 1번째 -25% : 상단 viewport 에서 윗방향 더 커져서 `종료`
    // 2번째 -25% : 하단에서 25% 떨어진 곳을 기준으로 `시작`
  })
  const [_, setIsInPriceView] = useIsInPriceView()
 
  useEffect(() => {
    if (isInView) {
      setIsInPriceView(true)
    } else {
      setIsInPriceView(false)
    }
  }, [isInView, setIsInPriceView])
 
  return (
    <section
      ref={ref}
      id="price-section"
    	// ...
    >
    	{/* ... */}
    </section>
  )
}