본문 바로가기
💻 개발 이야기/Front-end

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

by Jinseong Hwang 2023. 11. 17.

 
안녕하세요. 황진성입니다.
 
이번 글에서는 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