버글버글

[React] mui DataGrid를 통한 API 호출 CRUD (4) Update 본문

React/react 공부기록

[React] mui DataGrid를 통한 API 호출 CRUD (4) Update

Bugle 2023. 5. 8. 14:50
반응형

 

상품수정(Update)

1. selectedIds 상태 변수 선언하기

 - id 선택을 위한 useState Hook 사용

  const [selectedIds, setSelectedIds] = useState([]);

2. Datagrid가 수정될 때, 수정된 row를 저장해주는 props 선언

 - editMode : 그리드에서 셀을 클릭하여 데이터를 편집할 때 어떤 모드로 작동할지를 설정. ('row' = 전체 행 편집 가능, 'cell' = 각 셀 편집 가능)
 - processRowUpdate : 행 데이터가 업데이트 되었을 때 호출되는 콜백 함수를 전달. 업데이트된 행 데이터를 처리하고, 상태(state)에 저장.
 - onProcessRowUpdateError : processRowUpdate 함수 실행 중에 에러가 발생했을 때 호출되는 콜백 함수를 전달.

    <DataGrid
      rows={rows}
      columns={columns}
      checkboxSelection
      editMode='row'          
      processRowUpdate={processRowUpdate}
      onProcessRowUpdateError={(error) => error}
      onRowSelectionModelChange={(newSelectedIds) => {
        setSelectedIds(newSelectedIds);
      }}
      selectedIds={selectedIds}
      slots={{
        pagination: CustomPagination,
      }}
    >
    </DataGrid>

3. Datagrid의 processRowUpdate 함수 (변수명이 updatedRowupdateRows 가 나오니 주의!)

 - DataGrid에서 행(row)이 수정될 때마다 호출되는 함수인 processRowUpdate를 정의

  (1) newRow를 매개변수로 받으며, 수정된 행(row)의 데이터를 포함.
  (2) updatedRow라는 새로운 객체 생성
  (3) updatedRow는 newRow와 동일한 속성(key)과 값을 갖지만, 별도의 메모리 공간에 저장

        (이렇게 새로운 객체를 만드는 이유는, 이전 객체와 새로운 객체가 동일한 메모리를 공유하지 않도록 하기 위함.)
  (4) setUpdateRows 함수를 호출하여, updatedRow를 이전 updateRows 배열에 추가.
  (5) 이전 updateRows 배열을 복사하고, updatedRow를 추가한 새로운 배열을 생성하여 업데이트, updatedRow 객체 반환.

  - 이 함수가 반환한 updatedRow 객체는, DataGrid에서 수정된 행(row)의 새로운 데이터로 사용

  const processRowUpdate = (newRow) => {
    const updatedRow = { ...newRow }; 
    setUpdateRows(prevRows => [...prevRows, updatedRow]);
    return updatedRow;
  };

4. 수정을 위한 Button

 - 버튼 클릭시 실행되는 handleDelete 함수

<Button onClick={handleUpdate}>수정</Button>

5. Button handleUpdate 함수

 (1) updateRows 배열을 반복하면서 배열 안에 들어있는 resultId, resultItemCd, resultItemNm, resultCatCd, resultCatNm 값을 가져옴
 (2) selectedIds 배열에서 현재 resultId 값과 일치하는 항목을 필터링(검사)하고, 수정할 데이터 배열 dataToUpdate 를 생성
 (3) dataToUpdate 배열이 비어있지 않은 경우 axios.post 를 사용하여 수정 API 호출을 실행.

       (이때, data 프로퍼티에는 dataToUpdate 배열이 전달됨)
 (4) axios.post 호출을 Promise 객체로 반환하므로, 배열 promises 에 Promise 객체를 저장.

     (Promise를 사용 하기 전에는, 수정 개수가 4개면, alert창이 4번이나 실행되었었음...)
 (5) dataToUpdate 배열이 비어있으면, Promise.resolve() 를 반환

     (Promise.resolve()는 인자로 전달된 값을 가지고 즉시 이행(fulfilled) 상태인 Promise 객체를 반환하는 정적 메소드임.

        즉, 새로운 Promise 객체를 반환하며, 이행 상태인 Promise 객체가 반환되므로 .then() 메소드로 연결될 수 있다.)

 (6) Promise.all(promises) 는 모든 Promise 객체가 완료될 때까지 기다렸다가, 모든 Promise 객체가 완료되면 then() 블록을 실행.

  const handleUpdate = async () => {
    const promises = updateRows.map((row) => {
      const resultId = row.id;
      const resultItemCd = row.itemCd;
      const resultItemNm = row.itemNm;
      const resultCatCd = row.catCd;
      const resultCatNm = row.catNm;
  
      const dataToUpdate = selectedIds
        .filter((id) => id === resultId)
        .map((id) => ({
          id,
          itemCd: resultItemCd,
          itemNm: resultItemNm,
          catCd: resultCatCd,
          catNm: resultCatNm,
        }));
        if (dataToUpdate.length > 0) {
        return axios.post('API url', {
          data: dataToUpdate,
        });
      }
      return Promise.resolve();
    });
  
    Promise.all(promises)
      .then(() => {
        alert('선택한 상품이 수정되었습니다.');
        window.location.reload();
      })
      .catch(() => {
        alert('상품 수정에 실패하였습니다.');
      });
    };

 

최종코드

 

더보기
import React, { useState } from 'react';
import axios from 'axios';
import Button from '@mui/material/Button';
import { DataGrid } from '@mui/x-data-grid';
import TextField from '@mui/material/TextField';

const Crud = () => {

  // 1. useState
  const [rows, setRows] = useState([]);
  const [itemCd, setItemCd] = useState('');
  const [itemNm, setItemNm] = useState('');
  const [catCd, setCatCd] = useState('');
  const [catNm, setCatNm] = useState('');
  const [catList, setCatList] = useState([]);

  // 2. TextField 초기화시키기
  const textFormat = () => {
    setItemCd('');
    setItemNm('');
    setCatCd('');
    setCatNm('');
  }

  // 3-1. 상품 조회 
  const columns = [
    { field: 'id', headerName: '순번', width: 100 },
    { field: 'itemCd', headerName: '품목 코드', width: 130, editable: true },
    { field: 'itemNm', headerName: '품명', width: 250, editable: true },
    { field: 'catCd', headerName: '분류', width: 150, renderEditCell: renderSelectEdit, editable: true },
    { field: 'catNm', headerName: '코드', width: 100 },
    { field: 'cdt', headerName: '등록일', width: 200 },
  ];
    
  // 3-2. 상품조회 Button
  const handleView = () => {
    axios.post('API url', {
      itemCd: itemCd,
      itemNm: itemNm,
      catCd: catCd,
      catNm: catNm,
    })
    .then((response) => {
      const array = response.data.data.map((item, index) => ({
        // id: index + 1,
        id: item.id,
        itemCd: item.itemCd,
        itemNm: item.itemNm,
        catCd: item.catCd,
        catNm: item.catNm,
        cdt: item.cdt,
      }));
    
      // 3-3. Datagrid에 넣어줄 rows 값 세팅
      setRows(array);
      textFormat();
      return;
    })
    .catch(() => {
      alert("상품조회에 실패하였습니다.")
    });
  }
  
  // 4. 상품 등록
  const handleJoin = () => {
    textFormat();
    if(!itemCd || !itemNm || !catCd || !catNm) {
      alert("모든 사항을 입력해주세요.");
      return;
    } 
    axios.post('API url', {
      itemCd: itemCd,
      itemNm: itemNm,
      catCd: catCd,
      catNm: catNm,
    })
    .then(() => {
      alert("상품등록이 완료되었습니다.");
      handleView();
    })
    .catch(() => {
      alert("상품등록에 실패하였습니다.");
    })
  }

  // *. 상품 수정과 삭제를 위한 select 변수
  const [selectedIds, setSelectedIds] = useState([]);

  // 5. 상품 수정
  // 5-1. 수정된 rows 저장할 변수 선언(배열)
  const [updateRows, setUpdateRows] = useState([]);

  // 5-2. datagrid가 수정될때, updateRows에 데이터를 배열로 저장
  const processRowUpdate = (newRow) => {
    const updatedRow = { ...newRow }; 
    setUpdateRows(prevRows => [...prevRows, updatedRow]);
    return updatedRow;
  };
  
  // 5-3. 일치하는 아이디 검사 후, axios 통신해서 데이터 수정
  const handleUpdate = async () => {
    const promises = updateRows.map((row) => {
      const resultId = row.id;
      const resultItemCd = row.itemCd;
      const resultItemNm = row.itemNm;
      const resultCatCd = row.catCd;
      const resultCatNm = row.catNm;
  
      const dataToUpdate = selectedIds
        .filter((id) => id === resultId)
        .map((id) => ({
          id,
          itemCd: resultItemCd,
          itemNm: resultItemNm,
          catCd: resultCatCd,
          catNm: resultCatNm,
        }));
        if (dataToUpdate.length > 0) {
        return axios.post('API url', {
          data: dataToUpdate,
        });
      }
      return Promise.resolve();
    });
  
    // 수정건수가 n건이여도 1번만 alert창 실행하게 하는 promise
    Promise.all(promises)
      .then(() => {
        alert('선택한 상품이 수정되었습니다.');
        window.location.reload();
      })
      .catch(() => {
        alert('상품 수정에 실패하였습니다.');
      });
    };

  // 6. 상품 삭제
  const handleDelete = () => {
    if(selectedIds.length === 0){
      alert("아무 상품도 선택되지 않았습니다.");
      return;
    }
    axios.post('API url', {
      data: selectedIds.map((id) => ({ id })),
    })
    .then(() => {
      alert("상품이 성공적으로 삭제되었습니다.");
      handleView();
    })
    .catch(() => {
      alert("상품삭제에 실패하였습니다.");
    })
    
  }
  
  return (
        <div>  
          <span>품목코드</span>
          <UploadTextField
            type="text"
            value={itemCd}
            onChange={(e) => setItemCd(e.target.value)}
            size="small"
            sx={{ width: 90 }}
          />

          <span>품명</span>
          <UploadTextField
            type="text"
            value={itemNm}
            onChange={(e) => setItemNm(e.target.value)}
            size="small"
          />

          <span>분류</span>
          <UploadTextField
            type="text"
            value={catCd}
            onChange={(e) => {
              setCatCd(e.target.value);
              const selectedOption = catList.find((option) => option.value === e.target.value);
              setCatNm(selectedOption.code);
            }}
            select
            size="small"
            sx={{ width: 140 }}
          >
            {catList.map((option) => (
              <MenuItem key={option.value} value={option.value}>
                {option.label}
              </MenuItem>
            ))}
            </UploadTextField>

          <span>코드</span>
          <UploadTextField
            value={catNm}
            onChange={(e) => setCatNm(e.target.value)}
            disabled
            size="small"
            sx={{ width: 50 }}
          >
          </UploadTextField>

          <span>생성 수량</span>
          <UploadTextField
            type="number"
            size="small"
            inputProps={{ min: 0, max: 9999, maxLength: 4 }}
            sx={{ width: 90 }}
          />

          <Button
            onClick={handleJoin}
          >등록
          </Button>

          <Button
            onClick={handleUpdate}
          >수정
          </Button>

          <Button
            onClick={handleDelete}
          >삭제
          </Button>

          <Button
            onClick={handleView}
          >조회
          </Button>

        </div>

        <DataGrid
          rows={rows}
          columns={columns}
          checkboxSelection
          editMode='row'          
          processRowUpdate={processRowUpdate}
          onProcessRowUpdateError={(error) => error}
          onRowSelectionModelChange={(newSelectedIds) => {
            setSelectedIds(newSelectedIds);
          }}
          selectedIds={selectedIds}
          slots={{
            pagination: CustomPagination,
          }}
        >
        </DataGrid>
	</div>
    );
}

export default Crud;

 

반응형