[Project] React 무한 루프 발생 에러

이슬기·2024년 2월 13일
0

project

목록 보기
30/42

react-dom.development.js:86 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
at EmpDetail (http://localhost:3000/static/js/bundle.js:1999:76)
at div
at div
at EmpInfos (http://localhost:3000/static/js/bundle.js:2803:76)
at div
at div
at Admin (http://localhost:3000/static/js/bundle.js:1643:74)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:62933:5)
at Routes (http://localhost:3000/static/js/bundle.js:63624:5)
at AppRouter
at div
at div
at Provider (http://localhost:3000/static/js/bundle.js:123364:3)
at div
at App
at EntryPoint (http://localhost:3000/static/js/bundle.js:163:72)
at Router (http://localhost:3000/static/js/bundle.js:63558:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:61530:5)

React 프로젝트를 진행하던 중에 위와 같이 무한 루프가 발생하고 있었다.

오류 메시지에 따르면 "Maximum update depth exceeded"라고 하고 있으며, 일반적으로 컴포넌트가 무한 루프에 빠져있을 때 발생합니다. 이러한 문제는 주로 setState 메소드가 렌더링 시에 호출되어 상태가 변경될 때 다시 렌더링이 계속해서 발생하는 경우에 발생합니다.

이 오류는 보통 useEffect 훅 내에서 setState를 호출할 때 발생하는데, useEffect 내에서 상태를 변경하면 그것이 의존성 배열(dependency array)에 포함되어야 합니다. 만약 의존성 배열이 빈 배열 []로 설정되어 있거나, 혹은 의존성 배열에 있는 값이 렌더링마다 변하는 경우, 무한 루프에 빠질 수 있습니다.

따라서 해당 컴포넌트의 useEffect를 확인하고, 그 안에서 setState를 호출하는 부분이 있는지 확인하십시오. 그리고 useEffect의 의존성 배열을 적절하게 설정하여 문제를 해결할 수 있습니다.

문제를 해결하기 위해 다음과 같은 단계를 따를 수 있습니다:

  1. 의존성 배열 수정: EmpDetail 컴포넌트의 useEffect 훅에서 selectedEmployee를 의존성 배열에 사용하고 있습니다. 그러나 이것이 렌더링 시마다 변하기 때문에 무한 루프에 빠집니다. 이를 해결하기 위해 useEffect 의존성 배열에서 selectedEmployee 대신 dispatch 함수를 제거하여 이 컴포넌트가 불필요하게 많은 렌더링을 피하도록 만들 수 있습니다. 왜냐하면 selectedEmployee는 Redux 스토어에서 가져오는 상태이기 때문에 해당 상태가 변경될 때만 useEffect가 실행되어야 합니다.

  2. EmpInfos 컴포넌트 수정: EmpInfos 컴포넌트에서는 EmpDetail 컴포넌트의 handleUpdate 함수를 주석 처리한 것 같습니다. 이 함수를 다시 활성화하고 EmpDetail 컴포넌트에 handleUpdate를 props로 전달하여 수정된 상세 정보를 업데이트 할 수 있습니다.

EmpDetail 수정 전

import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Button } from 'react-bootstrap';
import styles from './empDetailInfo.module.css';
import { getEmpList, saveEmpDetails, setDetail } from '../../redux/empInfosSlice';

const EmpDetail = () => {
  const dispatch = useDispatch();
  const selectedEmployee = useSelector(state => state.empInfos.selectedEmployee) || {};
  const [editing, setEditing] = useState(false);
  const [updatedEmployee, setUpdatedEmployee] = useState(selectedEmployee);
  const [originalEmployee, setOriginalEmployee] = useState(selectedEmployee);

  useEffect(() => {
    setUpdatedEmployee(selectedEmployee);
    setOriginalEmployee(selectedEmployee);
  }, [selectedEmployee]);

  const inputFields = [
    { label: '사원명', name: 'E_NAME', type: 'text' },
    { label: '성별', name: 'E_GENDER', type: 'text' },
    { label: '생년월일', name: 'E_BIRTH', type: 'date' },
    { label: '사원번호', name: 'E_NO', type: 'text' },
    { label: '입사일', name: 'E_HIREDATE', type: 'date' },
    { label: '퇴사일', name: 'E_ENDDATE', type: 'date' },
    { label: '연락처', name: 'E_PHONE', type: 'text' },
    { label: '이메일', name: 'E_EMAIL', type: 'text' },
    { label: '주소', name: 'E_ADDRESS', type: 'text' },
    { label: '부서', name: 'DEPT_NAME', type: 'text' },
    { label: '비밀번호', name: 'E_PASSWORD', type: 'password' },
    { label: '권한', name: 'E_AUTH', type: 'text' },
    { label: '현황', name: 'E_STATUS', type: 'text' },
    { label: '직종', name: 'E_OCCUP', type: 'text' },
    { label: '직급', name: 'E_RANK', type: 'text' },
  ];

  const handleEdit = () => {
    setEditing(true);
  };

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setUpdatedEmployee(prevState => ({
      ...prevState, [name]: value
    }));
  };

  const handleCancel = () => {
    setEditing(false);
    setUpdatedEmployee(originalEmployee); // 수정 취소 시 원래 정보로 되돌림
  };

  // 수정된 직원 정보 저장 후, 전체 직원 목록을 다시 가져오도록 수정
  const handleSaveChanges = () => {
    dispatch(saveEmpDetails(updatedEmployee)) // 수정된 직원 정보 저장
    .then(() => {
      dispatch(setDetail(updatedEmployee)); // Redux 스토어에서 선택된 직원 정보 업데이트
      setEditing(false); // 수정 모드 해제

      // 수정된 직원 정보를 Redux 스토어에서 가져와 전체 직원 목록을 업데이트
      dispatch(getEmpList()); // 전체 직원 목록 다시 가져오기
    })
    .catch(error => {
      console.error('Error saving employee details: ', error);
    });
  }

  const renderInputField = ({ label, name, type }, index) => (
    <div className={styles.empInfoItem} key={name}>
      <div className={styles.label}>{label}</div>
      <input
        type={type}
        style={{ border: '1px solid lightgray', background: 'transparent', margin: '5px', paddingLeft: '50px' }}
        value={updatedEmployee[name] || ''}
        onChange={handleInputChange} // 입력값 변경 시 updatedEmployee 상태 업데이트
        readOnly={!editing} // 수정 모드일 때만 readOnly 해제
        name={name} // input 요소의 name 속성 추가
      />
      {index !== inputFields.length - 1 && <div className={styles.divider} />}
    </div>
  );

  return (
    <div style={{ padding: '20px', borderLeft: '1px solid' }}>
      <h5>직원 상세 정보</h5>
      <div className="col-2">
        {/* 수정 모드일 때만 저장 버튼 표시 */}
        {editing && <Button variant="warning" onClick={handleSaveChanges}>저장</Button>}
        <Button variant="warning" onClick={editing ? handleCancel : handleEdit}>
          {editing ? '취소' : '수정'}
        </Button>
      </div>
      <div className={styles.empInfoWrap}>
        <div className={styles.empPicture}>
          사진
        </div>
        {inputFields.map(renderInputField)}
      </div>
    </div>
  );
};

export default EmpDetail;

EmpDetail 수정 후

EmpInfos 수정 전

import React, { useEffect } from 'react'
import styles from './empInfo.module.css'
import EmpListAll from './EmpListAll'
import EmpDetail from './EmpDetail'
import EmpEdu from './EmpEdu'
import EmpExp from './EmpExp'
import EmpCerti from './EmpCerti'
import { useDispatch, useSelector } from 'react-redux'
import { getEmpList, setDetail } from '../../redux/empInfosSlice' // 액션 및 셀럭터 import

const EmpInfos = () => {
  const dispatch = useDispatch();
  const empList = useSelector(state => state.empInfos.value); // 변경된 상태명을 가져옴
  const empDetail = useSelector(state => state.empInfos.selectedEmployee); // 변경된 상태명을 가져옴

  useEffect(() => {
    dispatch(getEmpList());
  }, [dispatch]);

  const handleUpdate = (updatedDetail) => {
    // 수정 버튼 클릭 시 호출되는 함수
    // 수정된 상세정보를 스토어에 업데이트
    // updatedDetail을 사용하여 서버로 업데이트 요청을 보내고, 성공 시 dispatch(setDetail(updateDetail))를 호출
    // -> 스토어 상태 업데이트되고 자동으로 UI도 업데이트 된다.
    dispatch(setDetail(updatedDetail)); // 선택된 직원을 store에 저장
  }

  return (
    <>
    <div className={styles.innerEmpInfoWrap}>
      <div className={styles.empListContentWrap}>
        <EmpListAll />
      </div>
      <div className={styles.empDetailWrap}>
        <EmpDetail 
         /*  handleUpdate={handleUpdate} */
        />
      </div>
      <div className={styles.empBaseInfoWrap}>
        <EmpEdu 
          empDetail={empDetail}
        />
        <EmpExp 
          empDetail={empDetail}
        />
        <EmpCerti
          empDetail={empDetail}
        />
      </div> 
    </div>
    
    </>
  )
}

export default EmpInfos

EmpInfos 수정 후

0개의 댓글

관련 채용 정보