<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" /> 아래 예제에선 Render Props 패턴이 크게 필요하지 않음. 어떻게 사용하는지 정도만 참고

</aside>

children Prop을 이용해 컴포넌트 합성(component composition) 형태로 작성하면 부모 컴포넌트를 수정하지 않고도 자식 컴포넌트를 자유롭게 추가해서 사용할 수 있다.

일반적으로 데이터(상태) 공유는 상태 끌어올리기 방법이 가장 간단하지만 최상위 컴포넌트가 데이터(상태)와 모든 하위 컴포넌트를 갖고 있으면 해당 상태와 관련 없는 모든 하위 컴포넌트도 렌더링에 영향을 주는 단점이 있다(상태 끌어올리기로 부모 컴포넌트 상태가 변경돼서 업데이트하면 하위 컴포넌트도 다시 렌더링 하므로).

이땐 Render Props 패턴을 활용할 수 있다. 어떤 컴포넌트를 렌더링할지에 대한 로직을 상위 컴포넌트가 결정하고, 하위 컴포넌트는 자신이 관리하는 상태를 인자로 받은 render(혹은 children) Prop을 호출하여 데이터나 로직을 다른 컴포넌트에게 공유하는 방식이다.



방법 1 — Children Prop


children Props 형태로 사용

export default function CheckoutMobile({ orders, initialOrderIds }): Props {
  const {
		// ...
    getTableProps, // Native Props 노출대신 Props Getters 목록을 제공하는 Props Getters 패턴
  } = useCheckout({ initialOrderIds, orders });

  const { PaymentInfoModal, handlePaymentInfoModal } = usePaymentInfoModal();

  return (
    <div className="flex flex-col gap-2">
      {renderList.map(([date, orders]) => (
        <CheckoutTableContainer
          key={date}
          {...getTableProps(date, orders)} // useCheckout 훅이 반환한 Props 전달
        >
          {(tableBodyProps) => (
            <>
              <CheckoutTHeader />
              <CheckoutTBody
                {...tableBodyProps}
                handlePaymentInfoModal={handlePaymentInfoModal}
              />
            </>
          )}
        </CheckoutTableContainer>
      ))}
		</div>
  );
}
interface CheckoutTableContainerProps extends CheckoutTableData {
  tableClasses?: string;
  children: <T extends CheckoutTBodyData>(tableBodyProps: T) => JSX.Element;
}

export default function CheckoutTableContainer({
  // ...getCheckoutTableContainerProps 함수를 호출에 전달받은 Props 생략
  children,
  ...tableBodyProps // 나머지 Props는 테이블 Body에 사용
}: CheckoutTableContainerProps) {
  return (
    <section className="bg-white py-4 max-w-[767px]">
      <div className="flex gap-3 py-4 px-4">
        {/* ... */}
      </div>
      <table className={tableClasses}>{children(tableBodyProps)}</table>
    </section>
  );
}

방법 2 — Render Prop


render Prop 형태로 사용

<aside> <img src="/icons/light-bulb_gray.svg" alt="/icons/light-bulb_gray.svg" width="40px" /> 꼭 render Prop 이름을 사용하지 않아도 됨. renderFirstComponent 등의 이름도 가능

</aside>

export default function CheckoutMobile({ orders, initialOrderIds }: Props ) {
  const {
		// ...
    getTableProps, // Native Props 노출대신 Props Getters 목록을 제공하는 Props Getters 패턴
  } = useCheckout({ initialOrderIds, orders });

  const { PaymentInfoModal, handlePaymentInfoModal } = usePaymentInfoModal();

  return (
    <div className="flex flex-col gap-2">
      {renderList.map(([date, orders]) => (
        <CheckoutTableContainer
					{...getTableProps(date, orders)} // useCheckout 훅이 반환한 Props 전달
					key={date}
          render={(tableBodyProps) => (
            <>
              <CheckoutTHeader />
              <CheckoutTBody
                {...tableBodyProps}
                handlePaymentInfoModal={handlePaymentInfoModal}
              />
            </>
          )}
        />
      ))}
    </div>
  );
}
interface CheckoutTableContainerProps extends CheckoutTableData {
  tableClasses?: string;
  render: <T extends CheckoutTBodyData>(tableBodyProps: T) => JSX.Element;
}

export default function CheckoutTableContainer({
  // ...getCheckoutTableContainerProps 함수를 호출에 전달받은 Props 생략
  render,
  ...tableBodyProps // 나머지 Props는 테이블 Body에 사용
}: CheckoutTableContainerProps) {
  return (
    <section className="bg-white py-4 max-w-[767px]">
      <div className="flex gap-3 py-4 px-4">
        {/* ... */}
      </div>
      <table className={tableClasses}>{render(tableBodyProps)}</table>
    </section>
  );
}