React를 이용한 Movie App
- 팀 과제 : 영화 소개 웹페이지 만들기
Movie App : Githup 링크
const { movieData, loading } = useMoive({
url: "list_movies.json?minimum_rating=8&limit=20&sort_by=like_count",
});
{movieData.map((movie) => (
<Movie
key={movie.id}
id={movie.id}
image={movie.medium_cover_image}
title={movie.title}
rating={movie.rating}
runtime={movie.runtime}
year={movie.year}
/>
))}
커스텀 훅을 만들어 영화 데이터를 fetch하였습니다. 커스텀 훅을 통해 받아온 데이터는 map을 통해 Movie 컴포넌트의 prop으로 전달해줍니다.
import { useEffect, useState } from "react";
const useMoive = ({ url }) => {
const BASE_URL = "https://yts.mx/api/v2/";
const [movieData, setMovieData] = useState();
const [loading, setLoading] = useState(true);
const getMovie = async () => {
const data = await (await fetch(BASE_URL + url)).json();
setMovieData(data.data.movies);
};
useEffect(() => {
if (movieData) setLoading(false);
}, [movieData]);
useEffect(() => {
if (url) {
getMovie();
}
}, [url]);
return { movieData, loading };
};
export default useMoive;
커스텀 훅인 useMovie는 데이터와 로딩값을 return합니다. API url을 받아 fetch한 후 데이터를 전달하고 데이터가 존재할 시 로딩값을 변경해줍니다. 이렇게 반환하는 데이터와 로딩값을 받는컴포넌트는 로딩중일시 데이터 출력을 막아줄 수 있습니다.
const { id } = useParams();
const { movieData, loading, refetcher } = useMoive({
url: `movie_details.json?movie_id=${id}&with_cast=true`,
type: "detail",
});
Detail Page는 useParams을 사용하여 파라미터값을 구해 영화 데이터를 fetch하였습니다. 단 Detail 정보를 주는 API url의 데이터는 홈 페이지의 데이터와 형태가 달라 type이라는 parameter를 주어 커스텀 훅 내에서 구별하도록 만들어주었습니다.
import { useEffect, useState } from "react";
const useMoive = ({ url, type = "movies" }) => {
const BASE_URL = "https://yts.mx/api/v2/";
const [movieData, setMovieData] = useState();
const [loading, setLoading] = useState(true);
const getMovie = async () => {
const data = await (await fetch(BASE_URL + url)).json();
console.log(data);
if (type === "movies") {
setMovieData(data.data.movies);
} else if (type === "detail") {
setMovieData(data.data.movie);
}
};
useEffect(() => {
if (movieData) setLoading(false);
}, [movieData]);
useEffect(() => {
if (url) {
getMovie();
}
}, [url]);
return { movieData, loading };
};
export default useMoive;
커스텀 훅은 type parameter이용해 데이터를 형태에 맞게 저장시켜줍니다.
const { movieData, loading, refetcher } = useMoive({ url: null });
Search Page는 쿼리 파라미터가 변경 할 때마다 커스텀 훅의 refetcher사용하여 API를 다시 fetch합니다.
import { useEffect, useState } from "react";
const useMoive = ({ url = null, type = "movies" }) => {
const BASE_URL = "https://yts.mx/api/v2/";
const [movieData, setMovieData] = useState();
const [loading, setLoading] = useState(true);
const getMovie = async () => {
const data = await (await fetch(BASE_URL + url)).json();
console.log(data);
if (type === "movies") {
setMovieData(data.data.movies);
} else if (type === "detail") {
setMovieData(data.data.movie);
}
};
const refetcher = async (refetch) => {
if (url && loading) return;
if (url) {
getMovie();
} else {
const data = await (
await fetch(BASE_URL + `list_movies.json?query_term=${refetch}`)
).json();
setMovieData(data.data.movies);
}
};
useEffect(() => {
if (movieData) setLoading(false);
}, [movieData]);
useEffect(() => {
if (url) {
getMovie();
}
}, [url]);
return { movieData, loading, refetcher };
};
export default useMoive;
커스텀 훅의 refetch를 실행시키면 refetch가 받은 parameter를 이용하여 fetch를 진해합니다.
React를 이용한 서비스
- React로 내 위치의 날씨 만들기
- React를 이용하여 할 일 목록 만들기
import React, { useEffect, useState } from "react";
const Weather = () => {
const [data, setData] = useState();
const API_KEY = "8b46d1d2fb5be43ae110f89a6e57cab0";
const getWeather = async (lat, lon) => {
const weatherData = await (
await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`
)
).json();
setData(weatherData);
};
const handleSuccess = (pos) => {
const lat = pos.coords.latitude;
const lon = pos.coords.longitude;
getWeather(lat, lon);
};
const handleError = (error) => {
console.log(error);
};
const getGeoLocation = () => {
navigator.geolocation.getCurrentPosition(handleSuccess, handleError);
};
useEffect(() => {
getGeoLocation();
}, []);
return (
<>
<div style={{ padding: 10, paddingRight: 20 }}>
{data
? `${data.name} ${data.weather[0].main} ${data.main.temp.toFixed(
1
)}°C`
: "loading"}
</div>
</>
);
};
export default Weather;
useEffect를 이용하여 컴포넌트가 랜더시 위치를 불러오고, 해당 위치를 이용하여 날씨 API를 통해 날씨를 불러옵니다.
import React, { useState } from "react";
import List from "./list";
const Todo = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const onSubmit = (evnet) => {
evnet.preventDefault();
setTodos((pre) => [...pre, { content: todo, id: Date.now() }]);
setTodo("");
};
const onChange = (event) => {
const {
target: { value },
} = event;
setTodo(value);
};
const onDelete = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<ul>
{todos.map((todo) => (
<List
key={todo.id}
content={todo.content}
id={todo.id}
onDelete={onDelete}
/>
))}
</ul>
<form onSubmit={onSubmit}>
<input
type="text"
value={todo}
onChange={onChange}
style={{
width: 400,
fontSize: 32,
color: "#fff",
padding: "10px",
border: "none",
borderBottom: "2px solid #fff",
backgroundColor: "inherit",
outline: "none",
}}
/>
</form>
</div>
);
};
export default Todo;
useState를 통해 객체 및 배열도 state로 만들 수 있습니다. 그러나 state를 직접 변경하면 안되기 때문에 배열을 추가하는 과정에서 push를 사용하지않고 새로운 배열을 만드는 방법으로 배열을 추가하였습니다. 배열의 삭제또한 할 일 목록에 미리 id를 부여하고 filter통해 state를 새로 만들었습니다. 그리곤 map을 이용하여 배열의 내용들을 출력할 수 있습니다.
API를 관리하기 위해 커스텀 훅을 만들어보았습니다. 페이지가 늘어날수록 처음 계획했던 커스텀 훅 형태와는 많이 달라졌습니다. 중복되는 fetch 사용 제어, refetch를 하기 위한 함수 생성등 커스텀 훅은 점점 많은 기능이 필요해졌습니다. 이렇듯 좀 더 효율적인 코딩을 위한 커스텀 훅을 다시 만든다면 효율적인 기능을 위한 제어와 방식을 먼저 생각해서 만들어야겠습니다.
#프로젝트캠프 #프로젝트캠프후기 #유데미 #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #리액트 #react #부트캠프 #리액트캠프
As I settled into the plush comfort of my favorite armchair, I prepared for another cinematic escapade into the realms of imagination and emotion. Tonight's selection on https://kinogo-la.zone/ promised a diverse array of films, each offering a unique perspective on life and humanity. With a bowl of popcorn in hand, I eagerly awaited the opening credits to roll, signaling the beginning of my journey.