인프런에 올라간 제로초님의 강의를 보고 정리한 내용입니다.
https://www.inflearn.com/course/web-game-react
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
리액트 형식 모듈화(import, export default)랑 누드 형식 모듈화(require, module.exports)를 같이 쓰면 에러가 발생한 게 아닐까 추정. 근데 그 전에는 잘만 섞어 썼는데? 게다가 이미 clint.jsx와 Baseball.jsx로 이미 나뉘어져 있었는데?
누드 형식으로 모듈화를 통일하니 또 에러 발생.
ES Modules may not assign module.exports or exports
리액트 형식으로 바꾸니 해결. 더이상 누드 형식 구문을 쓸 수 없는 것인가?
<ul>
{this.fruits.map((e, index) => {return(<Try key={index} value={e} index={index}/>)})}
</ul>
//Baseball.jsx
return (
<li id={this.props.value.name} key={this.props.value.name} >
{this.props.index} {this.props.value.name}는 {this.props.value.taste}
</li>
)
//Try.js
모듈화한 파일을 렌더링할 때 key를 넣었는데도 key가 없다고 인식.
해당 모듈의 부모 컴포넌트에서 Try 컴포넌트를 불어올 때 그 안에 key를 넣으니 문제 해결.
import React, {PureComponent, memo} from 'react';
//함수 컴포넌트
const Try = memo(({tryInfo}) => {
return (
<li>
<div>{tryInfo.try}</div>
<div>{tryInfo.result}</div>
</li>
)
});
/*
//클래스 컴포넌트
class Try extends PureComponent {
render() {
const { tryInfo } = this.props;
return (
<li>
<div>{tryInfo.try}</div>
<div>{tryInfo.result}</div>
</li>
)
}
}
*/
export default Try;
리렌더링이 되지 않아야 하는데, 전부 렌더링되고 있다.
자식 컴포넌트에만 한 것이 문제인가 싶어 부모 컴포넌트인 Baseball도 적용을 시켜주었으나, 변함없었다.
import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';
const ResponseCheck = () => {
const [state, setState] = useState("waiting");
const [message, setMessage] = useState("클릭해서 시작하세요.");
const [result, setResult] = useState([]);
const timeout = useRef(null);
const startTime = useRef();
const endTime = useRef();
const onClickScreen = () => {
if (state === "waiting") {
setState("ready");
setMessage("초록색이 되면 클릭하세요.");
timeout.current = setTimeout(() => {
setState("now");
setMessage("지금 클릭!");
startTime.current = new Date();
}, Math.floor(Math.random() * 1000) + 2000);
} else if (state == "ready") { // 성급하게 클릭
clearTimeout(timeout.current);
setState("waiting");
setMessage("성급하셨군요!");
} else if (state == "now") { // 반응속도 체크
endTime.current = new Date();
setState("waiting");
setMessage("클릭해서 시작하세요.");
setResult((prevState) => {
return [...prevState, endTime.current - startTime.current]
});//prevState.result
}
}
const renderAverage = () => {
return result.length === 0
? null
: <div>평균 시간 : {result.reduce((a, c) => a + c) / result.length}ms</div>
};
const Reset = () => {
setResult([]);
}
return (
<>
<div id="screen" className={state} onClick={onClickScreen}>
{message}
</div>
{renderAverage()}
<button onClick={Reset}>리셋</button>
</>
)
}
export default ResponseCheck;
클래스로 작성한 컴포넌트를 함수형으로 변경하는 과정에서 에러 발생.
setResult를 호출하고 화살표 함수를 써서 기존의 result에 새로운 요소를 추가한 배열을 리턴시켜 대입하는 과정에서 에러가 발생했다.
원인은 기존의 클래스 컴포넌트 코드에서 return [...prevState.result, endTime.current - startTime.current]
코드를 그대로 가져온 것 때문이었다.
클래스에서는 setstate((prevState) ⇒ {})
형태로 화살표 함수의 prevState가 해당 컴포넌트의 State를 가져서 state.result가 가능했던 반면 setResult(prevState) => {})
는 result만 가지고 있었기 때문.
고쳐줌과 동시에 가독성을 위해 prevState를 prevResult로 고쳐주었다.
import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';
const rspCoord = {
가위 : "0",
바위 : "-142px",
보 : '-284px',
}
const score = {
가위 : "1",
바 : "0",
보 : "-1",
}
class RSP extends Component {
state = {
result : "1",
imgCoord : 0,
score : 0,
}
interval;
componentDidMount()
{
const {imgCoord} = this.state;
console.log(imgCoord);
this.interval = setInterval(() => {
if (imgCoord === rspCoord.바위) {
this.setState({
imgCoord : rspCoord.가위
});
} else if (imgCoord === rspCoord.가위) {
this.setState({
imgCoord : rspCoord.보
});
} else if (imgCoord === rspCoord.보) {
this.setState({
imgCoord : rspCoord.바위
});
}
}, 2000);
}
componentDidUpdate()
{
console.log("componentDidUpdate");
}
componentWillUnmount()
{
console.log("componentWillUnmount");
clearInterval(this.interval);
}
onClickBtn = () => {
}
render() {
const {result, score, imgCoord } = this.state;
console.log("rendering");
return (
<>
<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
<div>
<button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
<button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
<button id="paper" className="btn" onClick={this.onClickBtn('보')}>보</button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</>
)
}
}
export default RSP;
강의와 똑같이 나왔던 에러.
비동기에서 함수 외부의 변수를 참조하려고 해서 생긴 문제였다.
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...
import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';
const rspCoord = {
가위 : "0",
바위 : "-142px",
보 : '-284px',
}
const scores = {
가위 : "1",
바위 : "0",
보 : "-1",
}
const computerChoice = (imgCoord) => {
return Object.entries(rspCoord).find((v) => {
return v[1] === imgCoord;
})[0];
}
class RSP extends Component {
state = {
result : "1",
imgCoord : "0",
score : 0,
}
interval;
componentDidMount()
{
this.interval = setInterval(() => {
const {imgCoord} = this.state;
console.log(imgCoord);
if (imgCoord === rspCoord.바위) {
this.setState({
imgCoord : rspCoord.가위
});
} else if (imgCoord === rspCoord.가위) {
this.setState({
imgCoord : rspCoord.보
});
} else if (imgCoord === rspCoord.보) {
this.setState({
imgCoord : rspCoord.바위
});
}
}, 1000);
}
componentDidUpdate()
{
console.log("componentDidUpdate");
}
componentWillUnmount()
{
console.log("componentWillUnmount");
clearInterval(this.interval);
}
onClickBtn = (choice) => {
clearInterval(this.interval);
const myScore = scores[choice];
const cquScore = scores[computerChoice(this.state.imgCoord)];
const diff = myScore - cquScore;
if (diff === 0) {
this.setState({
result : "비겼습니다."
})
} else if (diff === [-1, 2].includes(diff)) {
this.setState((prevState) => {
return {
result : "이겼습니다!",
score : prevState.score + 1,
}
})
} else {
this.setState((prevState) => {
return {
result : "졌습니다...",
score : prevState.score - 1,
}
})
}
}
render() {
const {result, score, imgCoord } = this.state;
console.log("rendering");
return (
<>
<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
<div>
<button id="rock" className="btn" onClick={() => this.onClickBtn('바위')}>바위</button>
<button id="scissor" className="btn" onClick={() => this.onClickBtn('가위')}>가위</button>
<button id="paper" className="btn" onClick={() => this.onClickBtn('보')}>보</button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</>
)
}
}
export default RSP;
에러를 해석한 결과 무한 반복이 일어난다는 의미로 판단하고 강의에는 없었던 componentDidUpdate() 부분을 지워보았지만 해결되지 않았다.
구글링 결과 어렵지 않게 해결할 수 있었는데, render()부분에서 button에 이벤트를 넣을 때 onClick={this.inClickBtn()}
으로 적는다면 함수를 부른다 > render를 다시한다 > 또 함수를 부른다 > 반복
의 상황이 발생하기 때문이다.
{} 블럭안에 함수명+() 이렇게 썼기때문에 바로 호출의 의미가 되는 것이다.
괄호를 지우거나, 인자가 있는 경우 화살표 함수의 형태로 수정해주었더니 바로 해결되었다.
깃허브에 커밋을 하면서 node_modules가 너무 무겁지 않나 하는 생각에 별고민없이 폴더를 제거하고 실행해보았더니 아무 문제 없이 실행. 공포와 충격. node_modules에 내가 import하려던 모듈들이 들어있던 게 아니었나? 분명 강의대로 하다가 설치한 건 맞는 건같은데... 용도에 대해 정확히 알아볼 필요가 있다.
//Form.jsx
import React, {useState, useCallback, useContext} from 'react'
import {TableContext, START_GAME} from './MineSearch'
const Form = () => {
const [row, setRow] = useState(10);
const [cell, setCell] = useState(10);
const [mine, setMine] = useState(20);
const {dispatch} = useContext(TableContext);
const onChangeRow = useCallback((e) => {
setRow(e.target.value);
}, [])
const onChangeCell = useCallback((e) => {
setCell(e.target.value);
}, [])
const onChangeMine = useCallback((e) => {
setMine(e.target.value);
}, [])
const onClickButton = useCallback(() => {
console.log("dispatched " + row, cell, mine);
dispatch({type: START_GAME, row, cell, mine},
[row, cell, mine]);
}, []) //useCallback을 사용하니 row,cell,mine 값을 변경해도 적용이 안됨.
console.log("actually " + row, cell, mine);
return (
<div>
<input type="number" placeholder="세로" value={row} onChange={onChangeRow}/>
<input type="number" placeholder="가로" value={cell} onChange={onChangeCell}/>
<input type="number" placeholder="지뢰" value={mine} onChange={onChangeMine}/>
<button onClick={onClickButton}>시작</button>
</div>
)
}
export default Form;
부모 컴포넌트로 input에 대한 정보를 dispatch하는 부분에 useCallback을 넣자 변동된 값이 적용되지 않는 이슈 발생.
콘솔로 출력한 결과 useCallback이 맨 처음의 row, cell, mine의 값을 계속 기억하고 있어서 발생한 문제로 판명. 두 번째 인자를 지워도 변동 사항 없음.
Invalid hook call. Kooks can only be called inside of the body of a function component. This could happen for one of the following reasions...
Hook을 import하면 발생하는 문제.
에러의 3번 조항인 You might have more than one copy of React in the same app
때문인데, Hooks에서만 import하는 React의 대상이 다를 때(다른 디렉토리일 때) 문제가 발생한다.
클래스 버전으로 바꾸면 문제 해결. 다만 webpack.config.js의 plugins에서 "@babel/plugin-proposal-class-properties"
설치해야함.
setState를 쓰려고 한 건 아닐지 살펴볼 것.
render()에서 함수를 호출할 때 onClick={this.inClickBtn()}
같이 작성할 경우 바로 호출의 의미가 되어 함수를 부른다 > render를 다시한다 > 또 함수를 부른다 > 반복
의 무한루프에 빠지게 된다.
괄호를 지우거나, 인자가 있는 경우 화살표 함수의 형태로 수정해주면 해결된다.