Skip to content
This repository has been archived by the owner on Jan 4, 2023. It is now read-only.

Commit

Permalink
update error handle, infinite scroll, cache
Browse files Browse the repository at this point in the history
  • Loading branch information
sonicname committed Aug 18, 2022
1 parent fbdfbbe commit 883c78d
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 127 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^4.2.1",
"@tanstack/react-query-devtools": "^4.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-infinite-scroller": "^1.2.6",
"react-router-dom": "^6.3.0",
"react-toastify": "^9.0.8",
"swiper": "^8.1.0",
"swr": "^1.2.2"
"uuid": "^8.3.2"
},
"devDependencies": {
"@vitejs/plugin-react": "^1.0.7",
Expand Down
40 changes: 0 additions & 40 deletions src/App.jsx

This file was deleted.

19 changes: 19 additions & 0 deletions src/apis/apis.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const getAnimeDetail = async (id) => {
const res = await fetch(`https://api.jikan.moe/v4/anime/${id}`);
return res.json();
};

export const getListAnime = async (type) => {
const res = await fetch(`https://api.jikan.moe/v4/${type}`);
return res.json();
};

export const getCharacterDetail = async (id) => {
const res = await fetch(`https://api.jikan.moe/v4/characters/${id}`);
return res.json();
};

export const search = async (url) => {
const res = await fetch(url);
return res.json();
};
23 changes: 15 additions & 8 deletions src/components/anime/AnimeList.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import useSWR from "swr";
import { fetcher } from "../../utils/fetcher";
import { Swiper, SwiperSlide } from "swiper/react";
import AnimeItem from "./AnimeItem";
import AnimeItemSkeleton from "./AnimeItemSkeleton";
import { getListAnime } from "../../apis/apis";
import { useQuery } from "@tanstack/react-query";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";

const AnimeList = ({ url }) => {
const { data, error } = useSWR(url, fetcher);
const loading = !data && !error;
const AnimeList = ({ type }) => {
const { data, isError, isLoading } = useQuery(["list-anime", type], () =>
getListAnime(type)
);
const navigate = useNavigate();

if (error) console.error(error);
if (isError) {
toast.error("Something went wrong! Please try again!");
return navigate("/");
}

return (
<div className="anime-list">
<Swiper grabCursor={"true"} spaceBetween={20} slidesPerView={"auto"}>
{loading &&
{isLoading &&
new Array(10).fill(0).map((item, index) => (
<SwiperSlide key={index}>
<AnimeItemSkeleton />
</SwiperSlide>
))}
{!loading &&
{!isLoading &&
data &&
data.data.length > 0 &&
data.data.map((anime) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom";
import Header from "./Header";
import Footer from "./Footer";

const Main = () => {
const ShareLayout = () => {
return (
<>
<Header />
Expand All @@ -12,4 +12,4 @@ const Main = () => {
);
};

export default Main;
export default ShareLayout;
92 changes: 51 additions & 41 deletions src/components/search/SearchContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
import useSWR from "swr";
import AnimeItem from "../anime/AnimeItem";
import { fetcher } from "../../utils/fetcher";
import { Swiper, SwiperSlide } from "swiper/react";
import { useEffect, useRef, useState } from "react";
import CharacterItem from "../character/CharacterItem";
import AnimeItemSkeleton from "../anime/AnimeItemSkeleton";
import CharacterItemSkeleton from "../character/CharacterItemSkeleton";
import { search } from "../../apis/apis";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";
import { v4 } from "uuid";
import { useInfiniteQuery } from "@tanstack/react-query";
import InfiniteScroll from "react-infinite-scroller";

const SearchContainer = ({ type }) => {
const navigate = useNavigate();
const [query, setQuery] = useState("naruto");
const inputRef = useRef(null);
const searchBtnRef = useRef(null);

const url =
type === "anime"
? "https://api.jikan.moe/v4/anime"
: "https://api.jikan.moe/v4/characters";
const url = `https://api.jikan.moe/v4/${type}?q=${query}`;

const { data, error } = useSWR(`${url}?q=${query}`, fetcher);
const { data, hasNextPage, fetchNextPage, isError, isLoading } =
useInfiniteQuery(
[`search-${type}`, query],
({ pageParam = url }) => search(pageParam),
{
getNextPageParam: (lastPage, allPages) =>
lastPage.pagination.has_next_page
? `${url}&page=${lastPage.pagination.current_page + 1}`
: undefined,
}
);

if (isLoading) {
toast.info("Loading...");
}

if (error) console.error(error);
const loading = !data && !error;
if (isError) {
toast.error("Something went wrong! Please try again!");
return navigate("/");
}

useEffect(() => {
const handlerEnterKeyPress = (e) => {
Expand All @@ -36,6 +52,8 @@ const SearchContainer = ({ type }) => {
};
}, []);

console.log(data);

return (
<div className="page-container w-full">
<div className="flex flex-col gap-y-4">
Expand All @@ -57,37 +75,29 @@ const SearchContainer = ({ type }) => {
</div>

<div className="text-white mt-4">
<Swiper grabCursor={"true"} spaceBetween={20} slidesPerView={"auto"}>
{loading &&
new Array(4)
.fill(0)
.map((item, index) => (
<SwiperSlide key={index}>
{type === "anime" ? (
<AnimeItemSkeleton />
) : (
<CharacterItemSkeleton />
)}
</SwiperSlide>
))}
{!loading &&
data &&
data.data.length > 0 &&
data.data.map((item) => (
<SwiperSlide key={item.mal_id}>
{type === "anime" ? (
<AnimeItem anime={item} />
) : (
<CharacterItem character={item} />
)}
</SwiperSlide>
{isLoading && (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{new Array(4).fill(0).map(() => (
<AnimeItemSkeleton key={v4()} />
))}
{data && data.data.length <= 0 && (
<div className="text-md text-red-500 p-3">
Keyword: <span className="font-bold">{query}</span> is empty!
</div>
)}
</Swiper>
</div>
)}
<InfiniteScroll loadMore={fetchNextPage} hasMore={hasNextPage}>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{!isLoading &&
data.pages.map((pageData) =>
pageData.data.map((item) => (
<>
{type === "anime" ? (
<AnimeItem anime={item} key={item.mal_id} />
) : (
<CharacterItem character={item} key={item.mal_id} />
)}
</>
))
)}
</div>
</InfiniteScroll>
</div>
</div>
</div>
Expand Down
56 changes: 51 additions & 5 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { lazy, Suspense } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";

import App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import LoadingComponent from "./components/loading/LoadingComponent";
import ShareLayout from "./components/layout/ShareLayout";

const HomePage = lazy(() => import("./page/HomePage"));
const AnimePage = lazy(() => import("./page/AnimePage"));
const SearchPage = lazy(() => import("./page/SearchPage"));
const AnimeDetailPage = lazy(() => import("./page/AnimeDetailPage"));
const CharacterPage = lazy(() => import("./page/CharacterPage"));
const CharacterDetailPage = lazy(() => import("./page/CharacterDetailPage"));
const Error404Page = lazy(() => import("./page/Error404Page"));

import "swiper/css";
import "react-toastify/dist/ReactToastify.css";
import "./index.scss";
import { ToastContainer } from "react-toastify";

const queryClient = new QueryClient();

render(
<BrowserRouter>
<App />
</BrowserRouter>,
<QueryClientProvider client={queryClient} contextSharing={true}>
<BrowserRouter>
<Suspense fallback={<LoadingComponent />}>
<Routes>
<Route element={<ShareLayout />}>
<Route path={"/"} element={<HomePage />} />
<Route path={"/anime"} element={<AnimePage />} />
<Route path={"/anime/:animeID"} element={<AnimeDetailPage />} />
<Route path={"/character/"} element={<CharacterPage />} />
<Route
path={"/character/:characterID"}
element={<CharacterDetailPage />}
/>
<Route path={"/search"} element={<SearchPage />} />
</Route>

<Route path={"*"} element={<Error404Page />} />
</Routes>
<ToastContainer
position="top-right"
autoClose={2000}
hideProgressBar={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover={false}
/>
</Suspense>
</BrowserRouter>
</QueryClientProvider>,
document.getElementById("root")
);
21 changes: 13 additions & 8 deletions src/page/AnimeDetailPage.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { useParams } from "react-router-dom";
import useSWR from "swr";
import { fetcher } from "../utils/fetcher";
import { useNavigate, useParams } from "react-router-dom";

import DetailStatus from "../components/anime-details/DetailStatus";
import IconStar from "../components/icons/IconStar";
Expand All @@ -11,16 +9,23 @@ import IconRank from "../components/icons/IconRank";
import { getRating } from "../utils/getRating";
import DetailListItem from "../components/anime-details/DetailListItem";
import LoadingComponent from "../components/loading/LoadingComponent";
import { useQuery } from "@tanstack/react-query";
import { getAnimeDetail } from "../apis/apis";
import { toast } from "react-toastify";

const AnimeDetailPage = () => {
const { animeID } = useParams();
const navigate = useNavigate();

const { data, error } = useSWR(
`https://api.jikan.moe/v4/anime/${animeID}`,
fetcher
const { data, error, isLoading } = useQuery(["anime", animeID], () =>
getAnimeDetail(animeID)
);
if (error) console.error(error);
if (!data) return <LoadingComponent />;

if (error) {
toast.error("Something went wrong! Please try again!");
return navigate("/");
}
if (isLoading) return <LoadingComponent />;

const {
images,
Expand Down
6 changes: 3 additions & 3 deletions src/page/AnimePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ const AnimePage = () => {
<div className="page-container flex flex-col gap-y-10">
<section id="top-anime" className="text-white">
<h2 className="text-2xl font-semibold mb-3">Top Anime</h2>
<AnimeList url="https://api.jikan.moe/v4/top/anime" />
<AnimeList type="top/anime" />
</section>

<section id="season-now" className="text-white">
<h2 className="text-2xl font-semibold mb-3">Season now</h2>
<AnimeList url="https://api.jikan.moe/v4/seasons/now" />
<AnimeList type="seasons/now" />
</section>

<section id="season-upcoming" className="text-white">
<h2 className="text-2xl font-semibold mb-3">Season Upcoming</h2>
<AnimeList url="https://api.jikan.moe/v4/seasons/upcoming" />
<AnimeList type="seasons/upcoming" />
</section>
</div>
);
Expand Down
Loading

1 comment on commit 883c78d

@vercel
Copy link

@vercel vercel bot commented on 883c78d Aug 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.