본문 바로가기

TIL

TIL 24.11.11 React.Children.map() 함수

공통 컴포넌트를 작성하는중에, children 으로 받아온 자식요소에 대해서 index 를 주어야 할 일이 생겼다. 물론 사용하려고 불러오는 컴포넌트에서 다음처럼 index 값이나 id 를 개별적으로 넘겨주어도 되지만 map을 돌리지 않는 이상, 너무 귀찮은 일이라고 생각이 들었다.

 

그리고 나는 context로 accordion 컴포넌트에서 전역적으로 데이터를 관리하므로, item에서 받아온 정보를 또 props 로 넘겨주거나 개별 컴포넌트마다 id 를 뿌려주는 귀찮은 짓을 해야해서 사용자가 단순히 children 만 입력해도 기능이 동작하게 끔 최대한 기본컴포넌트에서 기능을 구현하고 싶었다.

 

<Accordion>
        <AccordionItem id=1>
          <AccordionTrigger>open1</AccordionTrigger>
          <AccordionContent>
            content1
          </AccordionContent>
        </AccordionItem>
        <AccordionItem id=2>
          <AccordionTrigger>open2</AccordionTrigger>
          <AccordionContent>
            content2
          </AccordionContent>
        </AccordionItem>
        <AccordionItem id=3>
          <AccordionTrigger>open3</AccordionTrigger>
          <AccordionContent>
            content3
          </AccordionContent>
        </AccordionItem>
</Accordion>

 

 

그래서 찾아본 React.Children.map() 함수. 저렇게 위처럼 데이터를 전달해도 다음처럼 children 을 인식해서 map을 돌려 그 안에 해당하는 자식요소의 속성을 수정할 수 있다. 

child 가 validElement라면 각 child에 { index: index } 값을 전달하므로, 각자 고유의 index 값을 갖게 되는 것이다.

    <AccordionContext.Provider value={{ activeIndex, handleToggleAccordion }}>
      <div {...props}>
        {React.Children.map(children, (child, index) => {
          if (React.isValidElement(child)) {
            return cloneElement(child as ReactElement, { index });
          }
          return child;
        })}
      </div>
    </AccordionContext.Provider>

https://legacy.reactjs.org/docs/react-api.html#reactchildrenmap

 

React Top-Level API – React

A JavaScript library for building user interfaces

legacy.reactjs.org

 

따라서 Accordion 컴포넌트의 자식 컴포넌트인 AccordionItems 에는 다음과 같이 부모로부터 index 를 받아와서 공유할 수 있다.

const AccordionItem = ({ children, index, ...props }: AccordionItemProps) => {
  return (
    <div {...props} className={styles.accordionItem} data-index={index}>
      {React.Children.map(children, (child) => {
        if (React.isValidElement(child)) {
          return cloneElement(child as ReactElement, { index });
        }
        return child;
      })}
    </div>
  );
};

 

이에 따라서 다음과 같이 조건문을 걸어주면, 해당 index 일 경우 한번 더 클릭하면  아코디언이 닫히고, 다른 index일 경우 트리거 된다.

  const handleToggleAccordion = (index: number) => {
    setActiveIndex((prevIndex) => (prevIndex === index ? null : index));
  };

 

 

혹시몰라서 작성해본 객체배열에 map을 돌려서 사용해도 아주 잘 동작한다.

 const list = [
   {
      trigger: "트리거1",
      content: "내용1",
    },
    {
      trigger: "트리거2",
      content: "내용2",
    },
  ];
      
      <Accordion>
        {list.map((v, i) => (
          <AccordionItem key={i}>
            <AccordionTrigger>{v.trigger}</AccordionTrigger>
            <AccordionContent>{v.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>