setState를 사용하기만 하면 무한루프가 발생해 계속해서 state값이 업데이트 되고 리렌더링되는 문제가 있었다.
LifeCycle에 대해 제대로 이해하지 못했기 때문인 것 같아 다시 정리해보려고 한다.
props가 변경되었을 때 리액트가 동작하는 과정을 먼저 알아보자.
componentWillRiceiveProps는 상위 컴포넌트로부터 props를 받을 때 실행된다. props를 받은 뒤 state를 업데이트하는 경우에 사용하면 좋다.
이 단계에서는 setState를 사용해도 된다.
shouldComponentUpdate는 props나 state가 변경되었을 때 다시 렌더링을 할지 말지 결정하는 API로, 불리언 값을 리턴한다.
shouldComponentUpdate리턴값이 true일 경우 다시 렌더링한다.
componentWilUpdate는 컴포넌트가 업데이트 되기 전에 실행되는 API이다.
이 때!!! setState를 변경하면 안된다. state가 변경되면 리렌더링을 해야하므로 무한 루프가 발생한다.
이 단계에서 의도치 않게 setState를 호출해 무한 리렌더링을 발생시킨 것 같다.
import React, { Component } from "react";
class UserListClass extends Component {
state = {
loading: true,
users: [],
};
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => this.setState({ users, loading: false }));
}
render() {
const { loading, users } = this.state;
if (loading) return <div>Loading...</div>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}
useEffect 는 react의 클래스 기반 컴포넌트의 생명주기 중 componentDidMount, componentDidUpdate, componentWillUnmount가 합쳐진 것으로 볼 수 있다.
useEffect는 react에게 컴포넌트가 렌더링을 한 후에 어떤 다른 일을 수행해야 하는지를 알려준다.
DOM 업데이트가 완료되고 나면 이 일들을 수행한다.
기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 useEffect가 실행된다.
처음 loading값이 true이기 때문에 loading...이 화면에 렌더링된다.
렌더링 이후 useEffect가 호출된다. users데이터를 가져오고 setUsers(users)로 받아온 users의 값을 users 라는 useState 변수에 넣어준다. loading은 false로 바꿔 주었으므로, setState를 마친 뒤 리렌더링될때 loading은 사라지고 user만 보여진다.
import React, { useState, useEffect } from "react";
function UserListFunction() {
const [loading, setLoading] = useState(true);
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => {
setUsers(users);
setLoading(false);
});
});
if (loading) return <div>Loading...</div>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
function MainPage({loggedIn}) {
const [eventsState, setEventsState] = useState([]);
useEffect(() => {
fetch('assets/event.json')
.then(response => response.json())
.then(data => data.events)
.then(events => setEventsState(events))
})
return (
<ul className={styles.event_container}>
{eventsState.map((event) => (
<li className={styles.event_list}>
<img className={styles.event_img}
key={event.details}
src={event.thumbnail}
alt="event_img"
/>
</li>
))}
</ul>
)
이렇게 했을 때, 이전에 다른 방법을 사용했을 때와는 달리 무사히 이미지가 렌더링 된다 (휴...)
데이터를 fetch로 받아온 다음에 렌더링을 다시 하는 것은 성공했다.
단, 계속해서 리렌더링 되는 문제는 여전히 남아있었다.
useEffect의 두번째 매개변수를 활용하면 된다 (이걸 잊고 넘겨버렸다 ㅎㅎ)
두 번째 매개변수를 넣어주면, 해당 값이 바뀔때만 effect를 재실행한다. [] 빈 배열만 넣어주면 어떤 값이 바뀌어도 efft가 재실행되지 않으므로 useEffect를 한 번만 실행시켜줄 수 있다.
따라서 setState로 값이 바뀜에 따라 다시 useEffect가 호출되고, setState가 호출되고, 값이 바뀌어서 다시 useEffect가 호출되는 무한루프의 늪에서 벗어날 수 있다.
useEffect(() => {
fetch('assets/event.json')
.then(response => response.json())
.then(data => data.events)
.then(events => setEventsState(events))
},[])
이렇게 두번째 매개변수로 빈 배열을 전달해주면, 한번만 렌더링된다!!
해결!!