React/Mini Projects

Tours (useEffect를 사용해서 데이터 fetch 해보기)

윰윰로그 2021. 6. 1. 11:47

 

 

이번 프로젝트는 간단했지만 기본적인 리액트 기능을 다시 복습하기에 좋았다.

가령, 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 데이터를 받아오고 컴포넌트들을 처음 모습처럼 렌더링.