diff --git a/src/hooks/useBindEnter.jsx b/src/hooks/useBindEnter.jsx
new file mode 100644
index 0000000..d65e622
--- /dev/null
+++ b/src/hooks/useBindEnter.jsx
@@ -0,0 +1,19 @@
+import { useEffect } from 'react';
+
+const useBindEnter = (setQuery = () => {}, inputRef) => {
+ useEffect(() => {
+ const handlerEnterKeyPress = (e) => {
+ if (e.code === 'Enter') {
+ setQuery(inputRef.current.value);
+ }
+ };
+
+ document.addEventListener('keyup', handlerEnterKeyPress);
+
+ return () => {
+ document.removeEventListener('keyup', handlerEnterKeyPress);
+ };
+ }, []);
+};
+
+export default useBindEnter;
diff --git a/src/hooks/useGetAnimeDetail.jsx b/src/hooks/useGetAnimeDetail.jsx
new file mode 100644
index 0000000..0ffc7e6
--- /dev/null
+++ b/src/hooks/useGetAnimeDetail.jsx
@@ -0,0 +1,9 @@
+import { useQuery } from '@tanstack/react-query';
+
+const useGetAnimeDetail = (animeID) => {
+ return useQuery(['anime', { animeID }], async () =>
+ (await fetch(`https://api.jikan.moe/v4/anime/${animeID}`)).json(),
+ );
+};
+
+export default useGetAnimeDetail;
diff --git a/src/hooks/useGetCharacterDetail.jsx b/src/hooks/useGetCharacterDetail.jsx
new file mode 100644
index 0000000..d0dc10b
--- /dev/null
+++ b/src/hooks/useGetCharacterDetail.jsx
@@ -0,0 +1,9 @@
+import { useQuery } from '@tanstack/react-query';
+
+const useGetCharacterDetail = (characterID) => {
+ return useQuery(['character', { characterID }], async () =>
+ (await fetch(`https://api.jikan.moe/v4/characters/${characterID}`)).json(),
+ );
+};
+
+export default useGetCharacterDetail;
diff --git a/src/hooks/useSearchAnime.jsx b/src/hooks/useSearchAnime.jsx
new file mode 100644
index 0000000..a5fd3b2
--- /dev/null
+++ b/src/hooks/useSearchAnime.jsx
@@ -0,0 +1,18 @@
+import { useInfiniteQuery } from '@tanstack/react-query';
+
+import { search } from '../apis/apis';
+
+const useSearchAnime = (type, query, url) => {
+ return useInfiniteQuery(
+ ['search', { type, query }],
+ ({ pageParam = url }) => search(pageParam),
+ {
+ getNextPageParam: (lastPage, _) =>
+ lastPage.pagination.has_next_page
+ ? `${url}&page=${lastPage.pagination.current_page + 1}`
+ : undefined,
+ },
+ );
+};
+
+export default useSearchAnime;
diff --git a/src/main.jsx b/src/main.jsx
index 9a4eb31..d136134 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -4,15 +4,15 @@ import { ToastContainer } from 'react-toastify';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import LoadingComponent from './components/loading/LoadingComponent';
import ShareLayout from './components/layout/ShareLayout';
+import LoadingComponent from './components/loading/LoadingComponent';
const HomePage = lazy(() => import('./page/HomePage'));
const AnimePage = lazy(() => import('./page/AnimePage'));
const SearchPage = lazy(() => import('./page/SearchPage'));
+const Error404Page = lazy(() => import('./page/Error404Page'));
const AnimeDetailPage = lazy(() => import('./page/AnimeDetailPage'));
const CharacterDetailPage = lazy(() => import('./page/CharacterDetailPage'));
-const Error404Page = lazy(() => import('./page/Error404Page'));
import 'swiper/css';
import 'react-toastify/dist/ReactToastify.css';
@@ -29,8 +29,14 @@ render(
} />
} />
} />
-
} />
-
} />
+
}
+ />
+
}
+ />
} />
@@ -49,5 +55,5 @@ render(
,
- document.getElementById('root')
+ document.getElementById('root'),
);
diff --git a/src/page/AnimeDetailPage.jsx b/src/page/AnimeDetailPage.jsx
index ee6bbc0..bab66e3 100644
--- a/src/page/AnimeDetailPage.jsx
+++ b/src/page/AnimeDetailPage.jsx
@@ -1,9 +1,7 @@
+import { toast } from 'react-toastify';
import { useNavigate, useParams } from 'react-router-dom';
import { getRating } from '../utils/getRating';
-import { useQuery } from '@tanstack/react-query';
-import { getAnimeDetail } from '../apis/apis';
-import { toast } from 'react-toastify';
import {
DetailListItem,
@@ -15,16 +13,19 @@ import {
LoadingComponent,
} from '../components';
+import useGetAnimeDetail from '../hooks/useGetAnimeDetail';
+
const AnimeDetailPage = () => {
const { animeID } = useParams();
const navigate = useNavigate();
- const { data, error, isLoading } = useQuery(['anime', animeID], () => getAnimeDetail(animeID));
+ const { data, error, isLoading } = useGetAnimeDetail(animeID);
if (error) {
toast.error('Something went wrong! Please try again!');
return navigate('/');
}
+
if (isLoading) return
;
const {
@@ -52,23 +53,23 @@ const AnimeDetailPage = () => {
{data && (
-
+
-
+
{title} ({year || 'Empty year'})
-
+
{title_japanese} - {`Rank: ${rank}`}
@@ -97,11 +98,21 @@ const AnimeDetailPage = () => {
- {genres.length > 0 &&
}
+ {genres.length > 0 && (
+
+ )}
-
-
+
+
{
-
-
+
+
{
-
+
- Description:
- {synopsis || "Description's empty"}
+
+ Description:{' '}
+
+
+ {synopsis || "Description's empty"}
+
diff --git a/src/page/AnimePage.jsx b/src/page/AnimePage.jsx
index aa4e169..d7d28a7 100644
--- a/src/page/AnimePage.jsx
+++ b/src/page/AnimePage.jsx
@@ -2,19 +2,19 @@ import { AnimeList } from '../components';
const AnimePage = () => {
return (
-
+
- Season now
+ Season now
- Season Upcoming
+ Season Upcoming
diff --git a/src/page/CharacterDetailPage.jsx b/src/page/CharacterDetailPage.jsx
index 59b75c4..e1cb4c6 100644
--- a/src/page/CharacterDetailPage.jsx
+++ b/src/page/CharacterDetailPage.jsx
@@ -1,42 +1,42 @@
-import { useNavigate, useParams } from 'react-router-dom';
-import { useQuery } from '@tanstack/react-query';
import { toast } from 'react-toastify';
-
-import { getCharacterDetail } from '../apis/apis';
+import { useNavigate, useParams } from 'react-router-dom';
import { IconEmail, IconFavorite, LoadingComponent } from '../components';
+import useGetCharacterDetail from '../hooks/useGetCharacterDetail';
+
const CharacterDetailPage = () => {
const navigate = useNavigate();
const { characterID } = useParams();
- const { data, isError, isLoading } = useQuery(['character', characterID], () =>
- getCharacterDetail(characterID)
- );
+ const { data, isError, isLoading } = useGetCharacterDetail(characterID);
+
if (isError) {
toast.error('Something went wrong! Please try again!');
return navigate('/');
}
+
if (isLoading) return
;
- const { images, name, name_kanji, nicknames, favorites, about, url } = data.data;
+ const { images, name, name_kanji, nicknames, favorites, about, url } =
+ data.data;
return (
{data && (
-
+
-
+
{name} ({name_kanji || 'empty japanese name'})
@@ -53,10 +53,12 @@ const CharacterDetailPage = () => {
-
+
About:
- {about || "Description's empty"}
+
+ {about || "Description's empty"}
+
diff --git a/src/page/Error404Page.jsx b/src/page/Error404Page.jsx
index 118817b..436b8b8 100644
--- a/src/page/Error404Page.jsx
+++ b/src/page/Error404Page.jsx
@@ -1,19 +1,23 @@
import { useNavigate } from 'react-router-dom';
-import errorImg from '../images/404.png';
import { Button } from '../components';
+import errorImg from '../images/404.png';
+
const Error404Page = () => {
const navigate = useNavigate();
return (
-
+
-
+
Oops!
You need map!
- navigate('/')} className='p-4 text-white !bg-purple-600'>
+ navigate('/')}
+ className='p-4 text-white !bg-purple-600'
+ >
Turn Back
diff --git a/src/page/HomePage.jsx b/src/page/HomePage.jsx
index 522334c..3a2d8f3 100644
--- a/src/page/HomePage.jsx
+++ b/src/page/HomePage.jsx
@@ -1,6 +1,6 @@
const HomePage = () => {
return (
-
+
Project: Anime page with reactJS + Tailwindcss by SoSmoothy
@@ -9,7 +9,7 @@ const HomePage = () => {
diff --git a/src/page/SearchPage.jsx b/src/page/SearchPage.jsx
index 1160c81..987a9f1 100644
--- a/src/page/SearchPage.jsx
+++ b/src/page/SearchPage.jsx
@@ -1,28 +1,30 @@
import { v4 } from 'uuid';
import { toast } from 'react-toastify';
import { useNavigate } from 'react-router-dom';
-import { useEffect, useRef, useState } from 'react';
+import { useRef, useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
-import { useInfiniteQuery } from '@tanstack/react-query';
-import { search } from '../apis/apis';
import { AnimeItem, AnimeItemSkeleton, CharacterItem } from '../components';
+import useSearchAnime from '../hooks/useSearchAnime';
+
const SearchPage = ({ type }) => {
const navigate = useNavigate();
const [query, setQuery] = useState('naruto');
+
const inputRef = useRef(null);
const searchBtnRef = useRef(null);
const url = `https://api.jikan.moe/v4/${type}?q=${query}`;
- const { data, hasNextPage, fetchNextPage, isError, isLoading, isFetchingNextPage } =
- useInfiniteQuery([`search-${type}`, query], ({ pageParam = url }) => search(pageParam), {
- getNextPageParam: (lastPage, _) =>
- lastPage.pagination.has_next_page
- ? `${url}&page=${lastPage.pagination.current_page + 1}`
- : undefined,
- });
+ const {
+ data,
+ hasNextPage,
+ fetchNextPage,
+ isError,
+ isLoading,
+ isFetchingNextPage,
+ } = useSearchAnime(type, query, url);
if (isError) {
toast.error('Something went wrong! Please try again!');
@@ -44,12 +46,12 @@ const SearchPage = ({ type }) => {
}, []);
return (
-
+
-
+
{
setQuery(inputRef.current.value)}
- className='p-3 absolute top-0 right-0 bottom-0 bg-purple-600 text-white font-semibold hover:opacity-75 duration-300 active:scale-90 rounded-r-md'
+ className='absolute top-0 bottom-0 right-0 p-3 font-semibold text-white duration-300 bg-purple-600 hover:opacity-75 active:scale-90 rounded-r-md'
>
Search
-
+
{isLoading && (
-
+
{new Array(4).fill(0).map(() => (
))}
)}
-
+
{!isLoading &&
data.pages.map((pageData) =>
pageData.data.map((item) => (
<>
{type === 'anime' ? (
-
+
) : (
-
+
)}
>
- ))
+ )),
)}
{isFetchingNextPage &&
}