NextJS13으로 새 포트폴리오를 만들자. 2 - third party 라이브러리 적용하기

Sal Jeong·2023년 6월 20일
1

저번 주 React Essentials에서 보았듯, 클라이언트 컴포넌트가 아닐 때, 리액트 라이프사이클을 적용하면 에러가 난다.

만들어 보고 싶은것은 위의 애니메이션을 framer motion으로 적용하는 레이아웃인데,

가장 간단한 방법은 위 모든 컴포넌트들을(상단 navBar, 왼쪽 오른쪽 fixed 메뉴 모두) use client로 만들어버리면 아무 문제가 없을 것 같다.

하지만, nextJS의 기본 개념과는 맞지 않다고 생각하므로:
client components는 반응성이 있거나, 리액트 라이프사이클을 사용하는 경우
server components는 반응성이 없고 고정된 밸류만을 표시하는 static한 경우

위 상술한 튜토리얼을 바탕으로 먼저 적용해 보았다.

// @/components/common/motion/index.ts

'use client';

import { motion as Motion } from 'framer-motion';

export default Motion;
// app/navbar/index.tsx

import React from 'react';
import Image from 'next/image';
import Link from 'next/link';

import { logo } from '@/components/common/icons';
import { montserrat } from '@/components/common/fonts';
import Motion from '@/components/common/framer/motion';

const Navbar = () => {
  return (
    <div className="w-full shadow-navbar-shadow h-20 lg:h-[12vh] sticky top-0 z-50 bg-body-color px-4">
      <div
        className={`max-w-screen h-full mx-auto py-1 flex items-center justify-between ${montserrat.className}`}
      >
        <Motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 0.5 }}
        >
          <Image className="w-14 bg-white" src={logo} alt="logo" />
        </Motion.div>
      </div>
    </div>
  );
};

export default Navbar;

왜인지는 모르겠지만 에러가 난다.

잠깐 고민을 한 뒤에 문제점을 깨달았다.

해당 라이브러리 framer-motion에서는 Compound Components Pattern 형식으로 컴포넌트를 임포트하기 때문에, 위 패턴에서 필요한 context는 모듈인 *.ts에서 프로바이더를 마운트 할 수 없기 떄문이다.

예전에 만들었던 아코디언 메뉴의 기억을 떠올려 본다면

// src/AccordionCompount.tsx
// 이렇게 위 패턴을 구현하려면 wrapper 자체가 tsx가 되어야 할 것이다. provider를 마운트해야 하기 때문에..


const AccordionCompound = ({ children, ...rest }: {children: React.ReactNode}) => {
    return <AccordionProvider {...rest}>{children}</AccordionProvider>;
};

const AccordionItem = ({ children, ...rest }: {children: React.ReactNode}) => {
    return <li className="w-full" {...rest}>{children}</li>;
};

AccordionCompound.Item = memo(AccordionItem);
AccordionCompound.Toggle = memo(AccordionToggle);
AccordionCompound.Collapse = memo(AccordionCollapse);

export default AccordionCompound;

// 그리고 사용해 줄 때는 

<ul className="space-y-2">
  <AccordionCompound>
    <AccordionConsumer>
      {({menus}) => {
   		return (
          <AccordionCompound.Item>
          </AccordionCompound.Item>
        )   
      }}
    </AccordionConsumer>
  </AccordionProvider>

따라서 이 부분은 wrapper를 모듈이 아닌 컴포넌트의 형태로 만들어 줄 필요가 있다.

// 해서 바뀐 부분은 다음과 같다.

'use client';

import { motion, MotionProps } from 'framer-motion';
import React from 'react';

type MotionedDivProps = {
  children: React.ReactNode;
  className?: string;
} & MotionProps;

function MotionedDiv({ children, ...rest }: MotionedDivProps) {
  return <motion.div {...rest}>{children}</motion.div>;
}

export default MotionedDiv;
const Navbar = () => {
  return (
    <div className="w-full shadow-navbar-shadow h-20 lg:h-[12vh] sticky top-0 z-50 bg-body-color px-4">
      <div
        className={`max-w-screen h-full mx-auto py-1 flex items-center justify-between ${montserrat.className}`}
      >
        <MotionedDiv
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 0.5 }}
        >
          <Image className="w-14 bg-white" src={logo} alt="logo" />
        </MotionedDiv>
        </div>
      </div>
    )
}

와 같은 방법으로 wrapping햇을 때, server 컴포넌트에서 framer-motion을 사용하는 client component를 임포트해 사용할 수 있었다.

profile
Can an old dog learn new tricks?

0개의 댓글