문제상황:
Nomad Coders의 트위터 클론코딩 강의의 #6.0 Cleaning JS (08:54) 의 github코드를 사용하여 테스트해보는 중 Home화면에서 Profile로 넘어가는 버튼 클릭시
1. Warning: Cannot update a component (`App`) while rendering a different component (`Unknown`). To locate the bad setState() call inside `Unknown`
2. Profile.js에서 Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
에러 로그 발생
· 에러로그 사진 1
· 에러로그 사진 2
· Profile.js 중 일부
해결방법:
refreshUser()의 위치를 onsubmit 내로 이동시켜서 해결
· Profile.js 전체코드
import React, { useEffect, useState } from "react";
import { authService, dbService } from "fbase";
import { useNavigate } from "react-router-dom";
import { collection, getDocs, query, where } from "@firebase/firestore";
import { orderBy } from "firebase/firestore";
export default ({ refreshUser, userObj }) => {
const navigate = useNavigate();
const [newDisplayName, setNewDisplayName] = useState(userObj.displayName);
const onLogOutClick = () => {
authService.signOut();
navigate("/");
};
const getMyNweets = async () => {
console.log(userObj.uid);
const q = query(
collection(dbService, "nweets"),
where("creatorId", "==", userObj.uid)
);
const querySnapshot = await getDocs(q);
//console.log(querySnapshot.docs);
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
});
};
const onChange = (event) => {
const {
target: { value },
} = event;
setNewDisplayName(value);
};
const onSubmit = async (event) => {
event.preventDefault();
if (userObj.displayName !== newDisplayName) {
await userObj.updateProfile({
displayName: newDisplayName,
});
refreshUser();
}};
useEffect(() => {
getMyNweets();
}, []);
return (
<div className="container">
<form onSubmit={onSubmit} className="profileForm">
<input
onChange={onChange}
type="text"
autoFocus
placeholder="Display name"
value={newDisplayName}
className="formInput"
/>
<input
type="submit"
value="Update Profile"
className="formBtn"
style={{
marginTop: 10,
}}
/>
</form>
<span className="formBtn cancelBtn logOut" onClick={onLogOutClick}>
Log Out
</span>
</div>
);
};
원인분석:
사실 에러로그의 trace가 제대로 안 떠있는줄 알고 혼란에 빠졌었는데,
에러로그에 제시된 링크인 https://github.com/facebook/react/issues/18178 로 들어가니 로그 왼쪽의 화살표 표시를 눌러 source of problem을 찾으라고 제시를 해주었다.
그래서 profile.js의 42번째 줄(refreshUser 부분)이 문제임을 확인 할 수 있었다.
그리고 나서,
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
이 에러로그를 구글링해보니 관련 오류의 해결글을 찾을 수 있었다.
https://stackoverflow.com/questions/48497358/reactjs-maximum-update-depth-exceeded-error
ReactJS: Maximum update depth exceeded error
I am trying to toggle the state of a component in ReactJS but I get an error stating: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside
stackoverflow.com
링크의 글을 간단히 요약하면, JSX로 쓰인 다음 예시에는 onClick시에 Maximum update depth exceeded....... 에러로그가 뜬다는 것이다.
toggle(){
const currentState = this.state.details;
this.setState({ details: !currentState });
}
{<td><span onClick={this.toggle()}>Details</span></td>}
이는 onClick시에 toggle()이 실행되고, 함수내에서 setState가 실행됨에 따라 다시 렌더링이 되면서 또 toggle()이 실행되고, 함수내에서 setState가 실행됨에 따라 다시 렌더링......되면서 무한 루프가 발생하기 때문이다.
그래서 toggle을 실행시키는 this.toggle()형태가 아닌 함수만을 넘겨주는 this.toggle 로 써주어야 한단다.
{<td><span onClick={this.toggle}>Details</span></td>}
이에 비추어보면, 내 코드에 문제가 생긴 과정은 다음과 같다.
1. Router.js 에서 App.js로부터 prop인 userObj, refreshUser를 받는다. refreshUser에서는 setUserObj(...)을 통해 현재의 유저정보를 갱신해준다.
· App.js 에서 정의된 refreshUser 함수
const refreshUser = () => {
const user = authService.currentUser;
if(user){setUserObj({
displayName: user.displayName,
uid: user.uid,
updateProfile: (args) => user.updateProfile(args),
});}
};
2. Router.js에서 <Profile>컴포넌트를 실행시킬때 Profile.js에 다시 prop으로 userObj,refreshUser을 넘겨준다.
· Router.js 중 일부
</Route>
<Route exact path="/profile" element={<Profile userObj={userObj} refreshUser={refreshUser}/>}>
</Route>
3. Home화면에서 Profile로 넘어가는 버튼 클릭시에,
사진의 맨 밑 부분의 refreshUser()가 실행되고, setUserObj(...)을 통해 state가 변경됨에 따라 무한루프가 발생한다.
사실 profile.js에서의 refreshUser()는 유저의 displayName이 변경되었을때 유저정보를 담는 userObj를 갱신해주기위해 존재하는데, 깃허브 코드의 복사,붙여넣기를 잘못하여 refreshUser()가 onSubmit함수 밖으로 튀어나와 이런일이 발생한 것이었다.
참고링크:
https://github.com/facebook/react/issues/18178#issuecomment-595846312
Bug: too hard to fix "Cannot update a component from inside the function body of a different component." · Issue #18178 · face
Note: React 16.13.1 fixed some cases where this was overfiring. If upgrading React and ReactDOM to 16.13.1 doesn't fix the warning, read this: #18178 (comment) React version: 16.13.0 Steps To R...
github.com
https://kss7547.tistory.com/36
React.js - 이벤트 Error: Maximum update depth... 해결법
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to preven..
kss7547.tistory.com
https://ko.reactjs.org/docs/faq-state.html
컴포넌트 State – React
A JavaScript library for building user interfaces
ko.reactjs.org