본문 바로가기

TECH

React-Table을 이용하여 자유자재 Custom 컴포넌트 만들기

 사실 자유자재는 아니고.. 리액트 테이블로 여러가지는 만들어야하는 상황이 생겼었다 ! 짜여진 리액트 테이블에 커스텀을 더한 테이블 컴포넌트로 분리하고자 하니까 꽤 많은 시간이 걸렸고, 공식 문서를 많이 찾아봐야했다 .. ! ⏤ React-table v7을 이용했습니다 ⏤ 공식 독스에는 문제를 해결할 수 있는 제안이 나와있지 않아서 여기 저기 코드를 붙여보며, 해결했습니다 !

먼저, 여러 페이지에서 사용되는 테이블을 [테이블 인터랙션/테이블] 2 개의 컴포넌트로 분리하고자 했습니다.

( 동일한 템플릿의 인터랙션을 사용한다면 인터랙션+테이블 1개의 컴포넌트로 분리해도 되지만, 동일한 테이블 디자인과 기능에 interaction 의 기능과 ui가 달라지는 경우가 있어서 2개의 컴포넌트 분리 후, 더 큰 단위(page)에서 하나로 묶어주었습니다 )

TableInteraction 컴포넌트

테이블 인터랙션 컴포넌트 같은 경우에는 다른 영향 받지 않게끔 퓨어하게 작성하고자 노력했습니다.

[ 새로고침 / 검색 / select / pagination ]

[ 검색 / 업로드 ]

위와 같은 interaction이 있을 때 클릭하면, 어떤 행동이 일어나야하는지에 대한 onClick 이벤트만 props로 전달 받았습니다. ( interaction 컴포넌트 자체적으로는 독립적임 )

  • 코드 예제
const TableInteraction=({ 
   onRefereshClick,
   onSearchClick,
   ....
})=>{
  return (
    ...
    {onRefreshClick && <button onClick={onRefreshClick}>새로고침</button>}
    {onSearchClick && <button onClick={onSearchClick}>검색</button>}
     ... 
  )

}

그래서 interaction 이 필요한 부분은 다른 컴포넌트 개발하는 것 처럼 고민없이 개발할 수 있었다.

Table 개발

테이블 개발 또한 react-table 독스에 나와 있는 방식과 동일하게 개발했다.

달랐던 점은, 기존에는 한 컴포넌트에서 useTable을 선언하고 table 요소에 내려줬다면 테이블은 interaction과 동일하게 자체적으로 useTable을 선언하지 않고, 퓨어하게 작성해주었다.

이렇게 해야 테이블 / 인터랙션 컴포넌트들을 독립적으로 운영하고, 따로따로 커스텀을 하여 더 큰 컴포넌트에서의 병합이 가능해진다.

 return (
   <table>
     <thead>
       <tr>
       {
       headerGroups.map(headerGroup => (
           headerGroup.headers.map(column => (
             <th {...column}>
               {column.render('Header')}
             </th>
           )
          )
       }
      </tr>
     </thead>
     <tbody >
       {page.map(row=> { <tr>
       // 기존에 prepare(row)가 필요하지만, 해주지 않음
       {rows.cell.map(row=> <td>{row.render('Cell')}</td>}
       </tr>}}
     </tbody>
   </table>
 )

row 맵을 돌면서 prepareRow(row) 를 해주라고 독스에 명시되어있었다.

상위 레벨에서 useTable() 선언 후, useTable의 산출물인 prepareRow() => table 컴포넌트 props에 전달, 테이블 컴포넌트 내에서 prepareRow()를 선언

초기에는 위와 같은 방법으로 했었는데 오류가 계속 발생했다. (아직도 자세한 이유는 잘 모르겠다. props로 잘 전달되는 것을 확인했기 때문에 당연히 될 것 이라 생각했다. )

useTable 관련 커스텀 훅 생성

위에서 prepareRow 에 대한 문제는 useTable 커스텀 훅을 생성하며 해결했다.

커스텀 훅을 제작한 이유는 table / tableInteraction 을 분리된 컴포넌트로 사용하고 있었기 때문에 useTable() 을 상위에서 사용했어야했다. useTable로 그냥 쓸 수도 있었음에도 그렇게 하지 않은 이유는 체크박스☑️에 대한 이유 때문이었다. ⏤ useTable에서 체크박스 유무에 대한 부분을 해결할 수 있다. useTable Toggle

 const {
    headerGroups,
    page,
    rows,
    prepareRow,
  } = useTable(
    {
      columns,
      data,
    (hooks) => {
      hooks.visibleColumns.push((columns) => [
        {
          id: 'selection',
          Header: ({
            getToggleAllRowsSelectedProps,
            rows,
            toggleAllRowsSelected,
            toggleRowSelected,
          }) => {
            const { indeterminate } = getToggleAllRowsSelectedProps();

            const onChangeHeaderCheckbox = (e: any) => {
              if (!indeterminate) {
                toggleAllRowsSelected(e.target.value.checked);
              }

              if (indeterminate) {
                rows.forEach((row) => {
                  toggleRowSelected(row.id, false);
                });
              }
            };

            return (
              <>
                  <CheckboxCell
                    {...getToggleAllRowsSelectedProps()}
                    onChange={onChangeHeaderCheckbox}
                  />
              </>
            );
          },
          Cell: ({ row }: { row: Row<T> }) => {
            return (
              <>
                  <CheckboxCell {...row.getToggleRowSelectedProps()} />
              </>
            );
          },
        },
        ...columns,
      ]);
    },
  );

커스텀 훅을 작성하지 않았다면, 체크박스를 이용하는 페이지에서 매번 이정도의 길이를 작성해주어야 하기 때문이다 ! 그래서 훅으로 따로 빼서 checkbox 유무에 대한 것을을 선언해주었고,

useTableInstance({
    columns,
    data,
    enableCheckbox: true,
  });

페이지 개발 시에는 위와 같은 훅만 선언해준다면 간편하게 테이블에 필요한 props를 정리해줄 수 있도록 했다.

useTable 와 테이블 컴포넌트가 분리되어 있을 때 prepareRow 오류 해결

useTable 선언부와 테이블 컴포넌트가 분리되어있을 때, prepareRow가 발생한다고 위에 작성해두었었다 ! 그에 대한 해결 방법은 useTable 선언한 곳에서 prepareRow를 해주는 것이다.

  // useTable Custom hook.ts

  rows.map((row) => prepareRow(row));

map을 돌린다음에 props로 전달해주면 된다.

너무 간단한 해결방안 였는데 🤔 오류가 어디에서 발생하는지 문구가 안나와있어서 이게 문제일거라고 생각도 못했었다... 그냥 useTableInstance 하는 곳에서 필요한 것들은 다 해주고, props로 전달하기 ..~

 

React-Table 활용

단순히 view를 보여주는 테이블 형식이 아니라, 리액트 테이블을 이용해서 위와 같은 파일브라우저(예시)도 이용이 가능했다. 

파일 / 디렉토리인지 확인해서, onClick 이벤트를 통해 path 를 체크해주고 그 path 에 해당하는 백엔드 요청을 보내서, 새로운 table의 data를 갈아끼우는 식으로 하면 된다 🤧  말은 단순하게 말했는데 개발하느라 오래 걸렸었던 것 중에 하나였다.. 😅

 


 

react-table을 이용해서 테이블과 관련된 UI를 편하게 구현할 수 있었다. 근데 공식 문서를 읽는 시간도 꽤 걸렸고, Custom하기에 납득되지 않는 오류가 생겨서 많은 시도가 필요했던 것 같다.. 독스가 조금 더 친절했으면 좋겠어요... 그래도 React-table은 useFilters, useSortBy, usePagination 등 테이블과 관련된 많은 동작들을 지원해주기 때문에 그래도, 꽤 괜찮은 라이브러리다.