앱을 새로고침하고 fetch 버튼을 클릭하면 영화가 화면에 표시되기까지 시간이 조금 걸린다.
이럴 때, 실제로는 로딩 아이콘이나 로딩 텍스트를 표시하여 사용자에게 현재 데이터를 불러오고 있다는 신호를 보내기도 한다.
로딩은 상태 관리를 통해 할 수 있다.
영화 데이터의 상태를 가져오면, 현재 이 데이터가 존재하는지 알 수 있다.
하지만 기다리는 중인지 알려주려면 또 다른 상태를 만들어야 한다.
isLoading
상태를 만들어서 로딩 상태를 관리해보자.
import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
// ✅ 로딩중인지 상태 관리
const [isLoading, setIsLoading] = useState(false);
const fetchMoviesHandler = async () => {
// ✅ 페치 버튼 클릭하면 loading 상태 true
setIsLoading(true);
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
// ✅ 비동기이긴 하지만 movies 데이터 업데이트 후
// loading 상태 다시 false로 만들기
setIsLoading(false);
};
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
//🔥 로딩중이 아닐 때(즉, 로딩 완료 시) && 무비 배열에 데이터 있을 때 && 데이터 화면에 표시하기
{!isLoading && movies.length > 0 && <MoviesList movies={movies} />}
//🔥 로딩은 완료되었지만 && 무비 배열에 데이터 없으면 && 영화가 없다고 표시하기
{!isLoading && movies.length === 0 && <p>Found no movies.</p>}
//🔥 로딩 중일 때 && 로딩중이라고 표시하기
{isLoading && <p>Loading...</p>}
</section>
</React.Fragment>
);
}
export default App;
import useHttp from "../../hooks/use-http";
import Section from "../UI/Section";
import TaskForm from "./TaskForm";
const NewTask = (props) => {
const { isLoading, error, sendRequest: sendTaskRequest } = useHttp();
//깊은 중첩 구조 피하기 위해 taskText 매개변수로 받기..^^...뭔소리여 아...
//두 번째 인자는 응답 데이터 받아서 뭔가 하는 함수
//taskData가 커스텀훅의 data
const createTask = (taskText, taskData) => {
const generatedId = taskData.name; // firebase-specific => "name" contains generated id
const createdTask = { id: generatedId, text: taskText };
props.onAddTask(createdTask);
};
const enterTaskHandler = async (taskText) => {
//폼이 제출될 때 마다 enterTaskHandler가 트리거되는데, 그럴때 마다 http 요청이 호출되어야 하므로 여기에서 호출한다.
sendTaskRequest(
{
url: "https://react-http-35c4a-default-rtdb.firebaseio.com/tasks.json",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: { text: taskText },
},
createTask.bind(null, taskText)
//bind()메소드 사용하여 taskText 매개변수 전달하기..
//bind()는 JS 기본 메서드로 어떤 함수에 대해서도 이를 사전 구성하기 위해 사용할 수 있다.
//bind에 보내는 첫 번재 인자는 실행이 예정된 함수에서 this 예약어를 사용하게 하는데, 여기서는 쓸모가 없으므로 null로 둔다.
//두 번째 인자를 호출 예정인 함수가 받는 첫번째 인자가 되므로 taskText를 전달하여 제출된 폼에서 taskTest를 찾게 하면 된다.
//나머지 인자인 taskData는 사전 설정되었기 때문에 이 위치에서 받는다.
//함수가 실제로 호출되는 useHttp에서 전달되는 다른 인자인 applyData의 경우, 간단하게 이 매개변수의 목록 끝에 추가하여 처리하면 된다.
//여기에 bind를 호출했기 때문에 이렇게 커스텀훅의 applyData의 유일한 인자로 전달되는 data는 createTask의 두 번째 인자로 추가된다.
);
};
return (
<Section>
<TaskForm onEnterTask={enterTaskHandler} loading={isLoading} />
{error && <p>{error}</p>}
</Section>
);
};
export default NewTask;
//이펙트 함수가 아닌 enterTaskHandler 에서만 sendTaskRequest를 호출하고 있기 때문에 useCallback 호출할 필요 없음
//따라서 무한루프 같은 문제는 발생하지 않음
//여기서 POST 요청은 컴포넌트가 재평가되어도 전송되지 않는다. 폼이 제출될 때맏 함수가 실행된다.
//App 컴포넌트와 이 컴포넌트의 차이 ㅇㅇ~
//App 컴포넌트에서는 이펙트 함수 안에서 요청을 보내서 요청 전송이 자주 발생할 위험성이 있었음