import GoalList from "./components/GoalList";
import GoalListProvider from "./components/GoalListProvider";
export const dynamic = "force-dynamic";
async function getAllGoalList() {
const res = await fetch(
`${process.env.NEXT_PUBLIC_NEXT_SERVER_URL}/api/goal/show-goalList`,
{
next: { revalidate: 15 },
}
);
const data = res.json();
return data;
}
export default async function Home() {
const data = await getAllGoalList();
return (
<GoalListProvider allGoalList={data}>
<GoalList />
</GoalListProvider>
);
}
NextJS를 활용한 동적 렌더링 페이지를 만들었다. 서버 api를 통해 받아온 데이터를 컴포넌트의 props로 전달할 계획이였다.
하지만 데이터를 모든 컴포넌트에게 일일히 props로 전달하기가 번거로웠다.
그래서 생각한 것이 데이터를 전달하는 provider 컴포넌트를 만들어서 해당 컴포넌트의 children 으로 들어오는 모든 컴포넌트들에게 props로 서버에서 받은 데이터를 전달하기로 계획을 변경했다.
export default function GoalListProvider({ allGoalList, children }) {
return <div>{children}</div>;
}
하지만...
어떻게 children 들에게 내가 받은 props 데이터를 전달해줄 것인가가 문제였다.
children props에는 모든 자식 컴포넌트의 정보가 있을 것이고, map 함수처럼 자식 컴포넌트들에게 props를 전달해줄순 없는걸까?
Children
은 컴포넌트의 매개변수로 가질 수 있는 children props를 효과적으로 다룰 수 있는 몇가지 API를 제공한다는 것을 알았다.
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
Call Children.map(children, fn, thisArg?) to map or transform each child in the children data structure.
공식 사이트
공식 사이트 설명에서 나와있듯이 Children map 메서드는 children props에 전달되는 각각의 child에게 콜백함수를 실행할 수 있다.
나는 모든 children 컴포넌트들에게 props로 서버에서 받아온 데이터를 넘겨주고 싶었다. 이럴 때는 cloneElement
를 사용하면 된다.
일부 리액트 요소를 재정의하기 위한 방법으로 사용되는 메서드이다.
cloneElement
(element, props, ...children)
Call cloneElement to create a React element based on the element, but with different props and children
기존의 요소를 기반으로 리액트 요소를 복제하는데, 전달하는 props와 children 요소와 함께 복제가 된다고 한다.
그렇다면 기존 요소의 props는 어떻게 되는것일까?
공식사이트에 따르면 내가 전달한 props로 재정의된 props로 얕은 병합이 이루어진다고 한다.
import React, { cloneElement } from "react";
export default function ParentComponent() {
const originalElement = <div className='original' />;
const clonedElement = cloneElement(originalElement, { className: "cloned" });
return (
<div>
{originalElement}
{clonedElement}
</div>
);
}
아래 예시에서 클론된 clonedElement의 className이 cloned으로 바뀐 것을 볼 수 있다. 즉, 클론 대상이였던 originalElement의 props를 덮어버렸다.
import React from "react";
export default function GoalListProvider({ allGoalList, children }) {
return React.Children.map(children, (child) =>
React.cloneElement(child, { allGoalList })
);
}
두 방식을 활용해 다음과 같은 코드로 변경하였다.
이렇게 되면 GoalListProvider의 안으로 들어오는 각각의 자식 컴포넌트들에게 props로 allGoalList라는 데이터를 넘겨줄 수 있게 된다.
공식 사이트에서는 Children.map과 cloneElement를 활용해서 내가 구현한 코드와 비슷한 예시를 제공했다.
export default function List({ children }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{Children.map(children, (child, index) =>
cloneElement(child, {
isHighlighted: index === selectedIndex
})
)}
하지만 기본적으로 cloneElement는 흔히 사용하는 방법이 아니고 이질적인 코드를 만들 가능성이 있으니 대안책을 제시하고 있다.