TV 쇼 React 앱을 만드는 방법 - tvmaze
11777 단어 reactapijavascripttutorial
이 프로젝트는
API: https://www.tvmaze.com/api
Github의 코드: https://github.com/rodrigolazo/react-tvmaze
프로젝트 구조
암호:
App.js
import styled from "styled-components";
import "./App.css";
import Header from "./components/header";
import { SearchBar } from "./components/searchBar";
const AppContainer = styled.div`
margin: auto;
padding: 0 530px;
`;
function App() {
return (
<>
<AppContainer>
<Header />
<SearchBar />
</AppContainer>
</>
);
}
export default App;
헤더/index.jsx
import React from 'react'
import logo from '../../img/logo.png'
const Header = () => {
return (
<header className='center'>
<img src={logo} alt='' />
</header>
)
}
export default Header
searchBar/index.jsx
import React from "react";
import styled from "styled-components";
import { IoClose, IoSearch } from "react-icons/io5";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { useClickOutside } from "react-click-outside-hook";
import { useEffect } from "react";
import { useRef } from "react";
import BeatLoader from "react-spinners/BeatLoader";
import { useDebounce } from "../../hooks/debounceHook";
import axios from "axios";
import { TvShow } from "../tvShow";
const SearchBarContainer = styled(motion.div)`
display: flex;
flex-direction: column;
width: 34em;
height: 3.8em;
background-color: #fff;
border-radius: 6px;
box-shadow: 0px 2px 12px 3px rgba(0, 0, 0, 0.14);
`;
const SearchInputContainer = styled.div`
width: 100%;
min-height: 4em;
display: flex;
align-items: center;
position: relative;
padding: 2px 15px;
`;
const SearchInput = styled.input`
width: 100%;
height: 100%;
outline: none;
border: none;
font-size: 21px;
color: #12112e;
font-weight: 500;
border-radius: 6px;
background-color: transparent;
&:focus {
outline: none;
&::placeholder {
opacity: 0;
}
}
&::placeholder {
color: #bebebe;
transition: all 250ms ease-in-out;
}
`;
const SearchIcon = styled.span`
color: #bebebe;
font-size: 27px;
margin-right: 10px;
margin-top: 6px;
vertical-align: middle;
`;
const CloseIcon = styled(motion.span)`
color: #bebebe;
font-size: 23px;
vertical-align: middle;
transition: all 200ms ease-in-out;
cursor: pointer;
&:hover {
color: #dfdfdf;
}
`;
const LineSeperator = styled.span`
display: flex;
min-width: 100%;
min-height: 2px;
background-color: #d8d8d878;
`;
const SearchContent = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 1em;
overflow-y: auto;
`;
const LoadingWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
const WarningMessage = styled.span`
color: #a1a1a1;
font-size: 14px;
display: flex;
align-self: center;
justify-self: center;
`;
const containerVariants = {
expanded: {
height: "30em",
},
collapsed: {
height: "3.8em",
},
};
const containerTransition = { type: "spring", damping: 22, stiffness: 150 };
export function SearchBar(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [tvShows, setTvShows] = useState([]);
const [noTvShows, setNoTvShows] = useState(false);
const isEmpty = !tvShows || tvShows.length === 0;
const changeHandler = (e) => {
e.preventDefault();
if (e.target.value.trim() === "") setNoTvShows(false);
setSearchQuery(e.target.value);
};
const expandContainer = () => {
setExpanded(true);
};
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoTvShows(false);
setTvShows([]);
if (inputRef.current) inputRef.current.value = "";
};
useEffect(() => {
if (isClickedOutside) collapseContainer();
}, [isClickedOutside]);
const prepareSearchQuery = (query) => {
const url = `http://api.tvmaze.com/search/shows?q=${query}`;
return encodeURI(url);
};
const searchTvShow = async () => {
if (!searchQuery || searchQuery.trim() === "") return;
setLoading(true);
setNoTvShows(false);
const URL = prepareSearchQuery(searchQuery);
const response = await axios.get(URL).catch((err) => {
console.log("Error: ", err);
});
if (response) {
console.log("Response: ", response.data);
if (response.data && response.data.length === 0) setNoTvShows(true);
setTvShows(response.data);
}
setLoading(false);
};
useDebounce(searchQuery, 500, searchTvShow);
return (
<SearchBarContainer
animate={isExpanded ? "expanded" : "collapsed"}
variants={containerVariants}
transition={containerTransition}
ref={parentRef}
>
<SearchInputContainer>
<SearchIcon>
<IoSearch />
</SearchIcon>
<SearchInput
placeholder="Search for series and TvShow"
onFocus={expandContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (
<CloseIcon
key="close-icon"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={collapseContainer}
transition={{ duration: 0.2 }}
>
<IoClose />
</CloseIcon>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator />}
{isExpanded && (
<SearchContent>
{isLoading && (
<LoadingWrapper>
<BeatLoader loading color="#3C948B" size={10} />
</LoadingWrapper>
)}
{!isLoading && isEmpty && !noTvShows && (
<LoadingWrapper>
<WarningMessage>Start typing to Search</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && noTvShows && (
<LoadingWrapper>
<WarningMessage>No series or Tv Shows found!</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && !isEmpty && (
<>
{tvShows.map(({ show }) => (
<TvShow
key={show.id}
thumbanilSrc={show.image && show.image.medium}
name={show.name}
rating={show.rating && show.rating.average}
url={show.url}
/>
))}
</>
)}
</SearchContent>
)}
</SearchBarContainer>
);
}
tvShow/index.jsx
import React from "react";
import styled from "styled-components";
const TvShowContainer = styled.div`
width: 100%;
min-height: 6em;
display: flex;
border-bottom: 2px solid #d8d8d852;
padding: 6px 8px;
align-items: center;
&:hover {
background-color: #dadada;
transition: all 0.3s ease;
border-radius: 3px;
}
`;
const Thumbnail = styled.div`
width: auto;
height: 100%;
display: flex;
flex: 0.4;
img {
width: auto;
height: 100%;
}
`;
const Name = styled.h3`
font-size: 15px;
color: #000;
margin-left: 10px;
flex: 2;
display: flex;
`;
const Rating = styled.span`
color: #a1a1a1;
font-size: 16px;
display: flex;
flex: 0.2;
`;
export function TvShow(props) {
const { thumbanilSrc, name, rating, url } = props;
return (
<TvShowContainer>
<Thumbnail>
<img src={thumbanilSrc} />
</Thumbnail>
<Name>
<a href={url} target="_blank">
{name}
</a>
</Name>
<Rating>{rating || "N/A"}</Rating>
</TvShowContainer>
);
}
후크/debounceHook.jsx
import React from "react";
import { useEffect } from "react";
import { useState } from "react";
export function useDebounce(value, timeout, callback) {
const [timer, setTimer] = useState(null);
const clearTimer = () => {
if (timer) clearTimeout(timer);
};
useEffect(() => {
clearTimer();
if (value && callback) {
const newTimer = setTimeout(callback, timeout);
setTimer(newTimer);
}
}, [value]);
}
스타일
App.css
body {
background: #000 url('img/bg.jpg') no-repeat center center/cover;
font-family: Arial, Helvetica, sans-serif;
}
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
header {
height: 200px;
}
header img {
width: 300px;
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
a {
color: #474747;
text-decoration: none;
}
결과
적용된 스타일을 테스트하려면 프로젝트를 다운로드하세요. 도움이 되었으면 합니다.
Github: https://github.com/rodrigolazo/react-tvmaze
참조:
Reference
이 문제에 관하여(TV 쇼 React 앱을 만드는 방법 - tvmaze), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/rodrigolazo/how-to-create-a-tv-show-react-app-tvmaze-f21텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)