Today, We are going to create an application that shows the information of TV Show, using a search bar.
This project uses
- react-scripts (Styles)
- react-spinners (Loader)
- axios (request Http)
- react-icons (Icons)
- framer-motion (Animations)
Api: https://www.tvmaze.com/api
Code From Github: https://github.com/rodrigolazo/react-tvmaze
Code:
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;
Header/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>
);
}
hooks/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]);
}
Style
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;
}
Download the project to test the applied styles, I hope it helps you
Github: https://github.com/rodrigolazo/react-tvmaze
references:
https://www.youtube.com/watch?v=x7niho285qs
https://www.youtube.com/watch?v=IlnmWntmUns&t=2974s
Top comments (3)
Thank you for sharing, currently I am also watching TV shows on the TV MIX app which is very interesting. You can download this application here: modilimitado.com/tv-mix-apk/
Mit GERMAN-IPTV öffnen Sie eine neue Welt des Entertainment und der Qualität. Als führende Website in Deutschland sind wir stolz darauf, unseren Kunden die besten Dienstleistungen und die neuesten Inhalte anzubieten. Erhalten Sie unbegrenzten Zugang zu all Ihren Lieblings deutschen Kanälen, sowie zu den neuesten Filmen und Serien, mit einer kostenlosen 3-monatigen Testphase für unsere neuen Kunden. Abonnieren Sie jetzt und genießen Sie die beste GERMAN-IPTV Erfahrung in Deutschland!
I am absolutely sure that this system is great for watching TV shows. Anyway, this is exactlu what I was looking for! TYSM!