Tours (useEffect를 사용해서 데이터 fetch 해보기)
이번 프로젝트는 간단했지만 기본적인 리액트 기능을 다시 복습하기에 좋았다.
가령, useEffect를 사용해서 컴포넌트를 마운트 할 때 데이터를 받아온다든가,
props를 destructure해서 자식의 자식 컴포넌트에게까지 전달한다든가,
손주(;) 컴포넌트에서 할부지 컴포넌트까지😋 상태를 전달한다든가 하는 것들
그리고 filter나 substring 메소드도 오랜만에 본 듯!
우선 전체 코드 👇
App.js
import { useState, useEffect } from "react";
import Loading from "./Loading";
import Tours from "./Tours";
const url = "https://course-api.com/react-tours-project";
function App() {
const [loading, setLoading] = useState(true);
const [tours, setTours] = useState([]);
const removeTours = (id) => {
const newTours = tours.filter((tour) => tour.id !== id);
setTours(newTours);
};
const fetchTours = async () => {
setLoading(true);
try {
const response = await fetch(url);
const tours = await response.json();
setTours(tours);
setLoading(false);
} catch (error) {
setLoading(false);
console.log(error);
}
};
useEffect(() => {
fetchTours();
}, []);
if (loading) {
return (
<main>
<Loading />
</main>
);
}
if (tours.length === 0) {
return (
<main>
<div className="title">
<h2>no tours left</h2>
<button className="btn" onClick={fetchTours}>
refresh
</button>
</div>
</main>
);
}
return (
<main>
<Tours tours={tours} removeTours={removeTours} />
</main>
);
}
export default App;
Tours.js
import Tour from "./Tour";
const Tours = ({ tours, removeTours }) => {
return (
<section>
<div className="title">
<h2>our tours</h2>
<div className="underline"></div>
</div>
<div>
{tours.map((tour) => (
<Tour key={tour.id} {...tour} removeTours={removeTours} />
))}
</div>
</section>
);
};
export default Tours;
Tour.js
import { useState } from "react";
const Tour = ({ id, image, info, price, name, removeTours }) => {
const [readMore, setReadMore] = useState(false);
return (
<article className="single-tour">
<img src={image} alt={name} />
<footer>
<div className="tour-info">
<h4>{name}</h4>
<h4 className="tour-price">${price}</h4>
</div>
<p>
{readMore ? info : `${info.substring(0, 200)}...`}
<button onClick={() => setReadMore(!readMore)}>
{readMore ? "show less" : "show more"}
</button>
</p>
<button className="delete-btn" onClick={() => removeTours(id)}>
not interested
</button>
</footer>
</article>
);
};
export default Tour;
가장 핵심 기능 코드만 좀 보자면
1. 데이터 받아오기
function App() {
const [loading, setLoading] = useState(true);
const [tours, setTours] = useState([]);
const fetchTours = async () => {
setLoading(true);
try {
const response = await fetch(url);
const tours = await response.json();
setTours(tours);
setLoading(false);
} catch (error) {
setLoading(false);
console.log(error);
}
};
useEffect(() => {
fetchTours();
}, []);
if (loading) {
return (
<main>
<Loading />
</main>
);
}
return (
<main>
<Tours tours={tours} />
</main>
);
}
tours 데이터를 받아오는 fetchTours 함수를 만들어서
useEffect에서 컴포넌트가 처음 마운트 될 때 한 번 호출.
데이터를 받아오는 아주 짧은 시간 동안에 '로딩중..' 같은 컴포넌트 보여주고 싶다면
loading state를 만들어서 data를 받기 시작할 때는 true, data를 다 받아오면 false로 바꿔주면 된다.
그리고 아래에서 loading의 상태에 따라 원하는 컴포넌트를 리턴해주면 된다.
2. 관심 없는 tour 리스트에서 지우기
(필요 없는 코드는 다 지움)
Tour.js에서 'not interested' 버튼 누르면 props로 전달받았던 removeTours에 컴포넌트 아이디 값을 넣어서 전달한다.
const Tour = ({ id, removeTours }) => {
return (
<button className="delete-btn" onClick={() => removeTours(id)}>
not interested
</button>
);
};
Tour.js의 부모 컴포넌트인 Tours.js는 그냥 removeTours를 전달하는 중간 역할자
const Tours = ({ tours, removeTours }) => {
return (
<div>
{tours.map((tour) => (
<Tour key={tour.id} {...tour} removeTours={removeTours} />
))}
</div>
);
};
가장 최상위 컴포넌트인 App.js에
function App() {
const [loading, setLoading] = useState(true);
const [tours, setTours] = useState([]);
const removeTours = (id) => {
const newTours = tours.filter((tour) => tour.id !== id);
setTours(newTours);
};
const fetchTours = async () => {
setLoading(true);
try {
const response = await fetch(url);
const tours = await response.json();
setTours(tours);
setLoading(false);
} catch (error) {
setLoading(false);
console.log(error);
}
};
useEffect(() => {
fetchTours();
}, []);
if (loading) {
return (
<main>
<Loading />
</main>
);
}
if (tours.length === 0) {
return (
<main>
<div className="title">
<h2>no tours left</h2>
<button className="btn" onClick={fetchTours}>
refresh
</button>
</div>
</main>
);
}
return (
<main>
<Tours tours={tours} removeTours={removeTours} />
</main>
);
}
filter 메소드를 이용한 removeTours가 있음.
id가 일치하지 않는 tour만으로 이루어진 배열을 다시 setTours에 넣어줘서 컴포넌트들을 리렌더링한다.
이때 만약 tours의 길이가 0이라면 (즉, 모든 tour를 다 지워버렸다면)
남은 투어가 없다는 말과 함께 refresh버튼이 뜨도록 했다.
refresh버튼을 누르면 아까 만들었던 fetchTours 함수를 다시 실행시켜서
전체 tours 데이터를 받아오고 컴포넌트들을 처음 모습처럼 렌더링.