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의 의존성 배열을 적절하게 설정하여 문제를 해결할 수 있습니다.
문제를 해결하기 위해 다음과 같은 단계를 따를 수 있습니다:
의존성 배열 수정: EmpDetail 컴포넌트의 useEffect 훅에서 selectedEmployee를 의존성 배열에 사용하고 있습니다. 그러나 이것이 렌더링 시마다 변하기 때문에 무한 루프에 빠집니다. 이를 해결하기 위해 useEffect 의존성 배열에서 selectedEmployee 대신 dispatch 함수를 제거하여 이 컴포넌트가 불필요하게 많은 렌더링을 피하도록 만들 수 있습니다. 왜냐하면 selectedEmployee는 Redux 스토어에서 가져오는 상태이기 때문에 해당 상태가 변경될 때만 useEffect가 실행되어야 합니다.
EmpInfos 컴포넌트 수정: EmpInfos 컴포넌트에서는 EmpDetail 컴포넌트의 handleUpdate 함수를 주석 처리한 것 같습니다. 이 함수를 다시 활성화하고 EmpDetail 컴포넌트에 handleUpdate를 props로 전달하여 수정된 상세 정보를 업데이트 할 수 있습니다.
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;
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