💻 개발 이야기/Front-end

[React/TS] MUI DataGrid로 수정 가능한 테이블 만들기

Jinseong Hwang 2023. 11. 17. 04:10

 
안녕하세요. 황진성입니다.
 
이번 글에서는 React.js와 TypeScript를 활용해서 수정 가능한 테이블을 만들어보려 합니다. 구글의 Material UI Design을 구현한 라이브러리인 MUI를 활용해 볼 예정이며, MUI의 DataGrid 컴포넌트로 테이블을 생성할 수 있습니다.
 
이번에 다룰 내용이 간단하지만 블로깅 하는 이유는 공식 문서에 제가 원하는 방식이 설명되어 있지 않아서 많이 애먹었기 때문입니다... MUI DataGrid를 사용하시는 분들 중 저와 비슷한 기능을 만드시는 분들은 시간 낭비를 하시지 않았으면 좋겠습니다.
 

목표


React 프로젝트 생성부터 DataGrid로 테이블을 생성하고 변경된 값으로 상태를 변경하는 것까지 해보겠습니다.
 
 

프로젝트 생성 스크립트


$ npx create-react-app mui-datagrid --template typescript

 
적당한 위치에 프로젝트를 생성합니다. 저는 프로젝트의 이름을 `mui-datagrid` 로 설정했습니다.
 
 
 

의존성 추가


"dependencies": {
  "@emotion/react": "^11.11.1",
  "@emotion/styled": "^11.11.0",
  "@mui/x-data-grid": "^6.18.1",
  ...
},

 
package.json 파일에 MUI DataGrid, Styled compoent 관련 의존성을 추가해 줍니다. 작성일 기준 DataGrid 최신 버전은 6.18.1 입니다.
 
 

DataGrid 컴포넌트 생성


const App = () => {
  return (
    <div className="App">
      <DataGrid columns={} rows={}/>
    </div>
  );
}

 
간단한 실습 용도이니 App.tsx의 App component에 DataGrid를 만들어 보겠습니다.
 
 

아무 값이나 넣어보자


const initialData = [
  {"name": "jinseong", "age": 20},
  {"name": "gildong", "age": 30},
  {"name": "cheolsu", "age": 40},
]

const App = () => {
  const columns: GridColDef[] = [
    {field: 'name', headerName: 'Name', width: 150},
    {field: 'age', headerName: 'Age', editable: true},
  ];

  const rows: GridRowsProp = initialData.map((data, idx) => ({
    id: idx, ...data
  }));

  return (
    <div className="App">
      <DataGrid
        sx={{"width": "500px", "margin": "auto"}}
        columns={columns}
        rows={rows}
      />
    </div>
  );
}

렌더링 결과

 
 
필수적으로 columns와 rows에 값을 설정해 줘야 합니다.
 

columns

  • 컬럼에 대한 설정을 합니다.
  • `field`는 데이터 접근 시 이름이고, `headerName`은 테이블에 노출되는 컬럼명입니다.
  • `editable`의 기본값은 false인데, true로 설정해 주면 테이블 셀을 더블클릭해서 수정할 수 있습니다.

 

rows

  • 표에 채워지는 데이터입니다.
  • `id` 필드가 꼭 필요해서 map index 값을 넣어줬습니다.

 
 

State 연결 및 수정하기


type TableRow = {
  "name": string,
  "age": number,
}

const initialData: TableRow[] = [
  {"name": "jinseong", "age": 20},
  {"name": "gildong", "age": 30},
  {"name": "cheolsu", "age": 40},
]

const App = () => {
  const [data, setData] = useState<TableRow[]>(initialData)
  
  const rows: GridRowsProp = data.map((row, idx) => ({
    id: idx, ...row
  })); // rows를 기반으로 테이블이 채워짐
  // ...
}

 
 

State를 관리 편의를 위해 TableRow라는 type을 하나 만들어 줬습니다.

 
Data라는 state를 하나 만들었습니다. 그리고 data를 기반으로 테이블이 채워집니다. 아직 렌더링 결과는 동일합니다.
 
 

return (
  <div className="App">
    <DataGrid
      sx={{"width": "500px", "margin": "auto"}}
      columns={columns}
      rows={rows}
      onStateChange={e => console.log(e)}
    />
  </div>
);

 
그리고 DataGrid 컴포넌트에 onStateChange 프롭 값을 채워줍니다. 일단 console logging을 해봅시다.
 

 
셀을 선택하고 조금 수정을 해봤는데 뭔가 촤르륵 많이 나옵니다! Click, Cell edit start, Cell edit end, Cell focus out 등 여러 이벤트들이 모두 로깅되는 것 같았습니다. 이제 변경된 값으로 Data State 변경을 해봅시다.
 
 

 
로깅된 이벤트 메시지를 잘 살펴보니 이벤트마다 editRows에 들어가는 값이 다르다는 것을 알게 됐습니다.
 

  • editRows가 {} 라면,
    • 셀이 수정되지 않았다. 즉, 값의 수정과 관련이 없는 클릭 이벤트 등이 발생한 것이다.
  • editRows가 { ... } 라면,
    • 셀이 수정됐다.

 
 

type CellEditProps = {
  "rowId": string,
  "colName": string,
  "value": string,
}

const App = () => {
  // ...
  const parseCellEditProps = (e: any): CellEditProps | null => {
    const editingRowId = Object.keys(e.editRows)[0]
    if (editingRowId === undefined) return null;
    const editingColName = Object.keys(e.editRows[editingRowId])[0]
    const editingValue = e.editRows[editingRowId][editingColName].value
    return {
      "rowId": editingRowId,
      "colName": editingColName,
      "value": editingValue,
    }
  }

  const handleEditEvent = (e: any) => {
    const editProps = parseCellEditProps(e);
    if (editProps === null) return;

    setData(prevState => {
      return prevState.map((row, idx) => {
        if (idx.toString() === editProps.rowId) {
          return {...row, [editProps.colName]: editProps.value};
        }
        return row;
      });
    });
  };
  
  return (
    <div className="App">
      <DataGrid
        sx={{"width": "500px", "margin": "auto"}}
        columns={columns}
        rows={rows}
        onStateChange={handleEditEvent} // 여기 추가!
      />
    </div>
  );
}

 

Cell 수정에 필요한 값 관리 편의를 위해 CellEditProps라는 type을 하나 만들어 줬습니다.

 
이벤트 객체에서 수정에 필요한 데이터를 뽑는 parseCellEditProps 함수를 만들고, 핸들러 함수를 만들었습니다.
 

 
이제 정상적으로 값이 수정되며, state 값도 변경되는 것을 확인할 수 있습니다!
 

 

2023-11-20 추가

Maximum update depth exceeded 에러가 발생한다면 다음 글을 참고해 주세요!

[React] Maximum update depth exceeded 에러를 해결한 방법

 

 

맺음말

제가 프론트엔드 개발 경험이 적어서 삽질을 많이 했는데 아직도 많이 부족합니다. 피드백 언제나 환영합니다! 감사합니다.
 
프로젝트에 사용된 전체 코드는 아래에서 확인하실 수 있습니다.
https://github.com/JinseongHwang/react-playground/tree/main/mui-datagrid