diff --git a/package-lock.json b/package-lock.json index 0a4af52..5c6bcfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "project-1", "version": "0.0.0", "dependencies": { + "@tanstack/react-query": "^5.103.0", "react": "^19.2.8", "react-dom": "^19.2.8" }, @@ -1068,6 +1069,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.103.0.tgz", + "integrity": "sha512-PfafnHQHEu7mZDsSalxSW/EBxPgF77k3atIJeQbqbb1Z7DmK2mH6YLJyqrvbbDk5M6Ua/egs7LLEs04a8robxA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.103.0.tgz", + "integrity": "sha512-Kn/cvTrNwFYpS/q1MYghMsVTSinmAKV1U2AnM3/x07PiczvC+nMoySqNpQK1jgDzVCOIHk5LlF2FhTlJKQmPAQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.103.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz", diff --git a/package.json b/package.json index 74ba36f..1e069d2 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/react-query": "^5.103.0", "react": "^19.2.8", "react-dom": "^19.2.8" }, diff --git a/src/App.tsx b/src/App.tsx index 2812750..249cb9a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,14 @@ import "./App.css"; import HomePage from "./pages/HomePage"; +import CharactersPage from "./pages/CharactersPage"; function App() { - return ; + return ( +
+ + +
+ ); } export default App; diff --git a/src/components/CharacterDetailCard.tsx b/src/components/CharacterDetailCard.tsx index 9fb735a..c00c649 100644 --- a/src/components/CharacterDetailCard.tsx +++ b/src/components/CharacterDetailCard.tsx @@ -1,5 +1,5 @@ import "./CharacterDetailCard.css"; -import { useEffect, useState } from "react"; +import { useQuery } from '@tanstack/react-query' import type { Character } from "../types/Character"; import DetailCardShape from "../assets/detail-card-shape.svg"; @@ -23,6 +23,25 @@ type Episode = { episode: string; }; +// AI was used to create the fetchEpisodes function +async function fetchEpisodes(character: Character): Promise { + const episodeIds = character.episode + .map((episodeUrl) => episodeUrl.split('/').pop()) + .join(','); + + const response = await fetch( + `https://rickandmortyapi.com/api/episode/${episodeIds}` + ); + + if (!response.ok) { + throw new Error(`Could not fetch episodes: ${response.status}`); + } + + const data = await response.json(); + + return Array.isArray(data) ? data : [data]; +} + export default function CharacterDetailCard({ character, onClose, @@ -31,28 +50,11 @@ export default function CharacterDetailCard({ isFavorite, onToggleFavorite, }: CharacterDetailCardProps) { - const [episodes, setEpisodes] = useState([]); - - // AI was used to create this useEffect function. - useEffect(() => { - async function fetchEpisodes() { - try { - const episodeData = await Promise.all( - character.episode.map(async (episodeUrl) => { - const response = await fetch(episodeUrl); - if (!response.ok) { - throw new Error(`Could not fetch episode: ${response.status}`); - } - return response.json(); - }), - ); - setEpisodes(episodeData); - } catch (error) { - console.error(error); - } - } - fetchEpisodes(); - }, [character]); + + const { data: episodes = [] } = useQuery({ + queryKey: ['episodes', character.id], + queryFn: () => fetchEpisodes(character) + }); return (
diff --git a/src/main.tsx b/src/main.tsx index 31358ab..b273eb5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,12 +1,15 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import "./index.css"; import App from "./App.tsx"; -import CharactersPage from "./pages/CharactersPage.tsx"; + +const queryClient = new QueryClient() createRoot(document.getElementById("root")!).render( - - + + + , ); diff --git a/src/pages/CharactersPage.tsx b/src/pages/CharactersPage.tsx index ac82c9e..fc16ddd 100644 --- a/src/pages/CharactersPage.tsx +++ b/src/pages/CharactersPage.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import CharacterCardSmall from "../components/CharacterCardSmall"; import CharacterDetailCard from "../components/CharacterDetailCard"; import SearchIcon from "../assets/search.svg"; +import { useQuery } from '@tanstack/react-query'; import type { Character } from "../types/Character"; @@ -15,8 +16,20 @@ const filters: { title: string; key: FilterCategory; options: string[] }[] = [ { title: "Status", key: "status", options: ["Alive", "Dead", "Unknown"] }, ]; +// AI was used to create the fetchCharacters function +async function fetchCharacters(): Promise { + const response = await fetch("https://rickandmortyapi.com/api/character"); + + if (!response.ok) { + throw new Error(`Could not fetch characters: ${response.status}`); + } + + const data = await response.json(); + return data.results; +} + export default function CharactersPage() { - const [characters, setCharacters] = useState([]); + const {data: characters = []} = useQuery({queryKey: ['characters'], queryFn: fetchCharacters}); const [selectedCharacter, setSelectedCharacter] = useState( null, ); @@ -59,26 +72,6 @@ export default function CharactersPage() { ); }, [favoriteCharacterIds]); - useEffect(() => { - async function fetchCharacters() { - try { - const response = await fetch( - "https://rickandmortyapi.com/api/character", - ); - - if (!response.ok) { - throw new Error(`Could not fetch characters: ${response.status}`); - } - - const data = await response.json(); - setCharacters(data.results); - } catch (error) { - console.error(error); - } - } - fetchCharacters(); - }, []); - const filteredCharacters = characters.filter((character) => { const matchesSearch = character.name .toLowerCase() @@ -150,7 +143,7 @@ export default function CharactersPage() { } return ( -
+
handleToggleFavorite(selectedCharacter.id)} /> )} -
+ ); } diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index aafc02e..a51e7d2 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,11 +1,7 @@ import Hero from "../components/Hero"; function HomePage() { - return ( -
- -
- ); + return ; } export default HomePage;