Mercurial
diff react_games/src/API_works/pagination_2.tsx @ 37:fb9bcd3145cb
[ReactGames] Few games I made using react just to practice few things.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 01 Dec 2025 20:22:47 -0800 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/react_games/src/API_works/pagination_2.tsx Mon Dec 01 20:22:47 2025 -0800 @@ -0,0 +1,226 @@ +import { CSSProperties, useCallback, useEffect, useRef, useState } from "react"; + +interface PersonJson { + name: string; + height: string; + mass: string; + hair_color: string; + skin_color: string; + eye_color: string; + birth_year: string; + gender: string; + homeworld: string; + films: string[]; + species: string; + vehicles: string[]; + starships: string[]; + created: string; + edited: string; + url: string; +} + +interface Person extends PersonJson {} + +interface APIResponse { + next: string | null; + previous: string | null; + results: PersonJson[]; +} + +interface PageData extends APIResponse { + curr: string; +} + +const BASE_URL = "https://swapi.py4e.com/api/people/"; + +const cacheMap: Map<string, Promise<PageData>> = new Map() + +const getPeople = async ( + url: string, + signal: AbortSignal, +): Promise<PageData> => { + const cacheValue = cacheMap.get(url); + if (cacheValue) return cacheValue; + + const response = await fetch(url, { signal }); + if (!response.ok) throw new Error("Server Error"); + const pageData = await response.json(); + + cacheMap.set(url, pageData); + return { ...pageData, curr: url }; +} + +interface PersonRowProp { + person: Person; +} + +interface StylesProp { + row: CSSProperties, +} + +const styles: StylesProp = { + row: { + display: "flex", + justifyContent: "space-between", + borderBottom: "1px solid black", + marginBottom: 10 + } +} + +const PersonRow = ({person}: PersonRowProp) => { + return ( + <div style={styles.row}> + <p>{ person.name }</p> + <p>{ person.birth_year }</p> + </div> + ) +} + +const constructSearchParam = (pageData: PageData) => { + const sp = new URLSearchParams(); + if (pageData.next) { + sp.set("next", pageData.next) + } + if (pageData.previous) { + sp.set("previous", pageData.previous) + } + sp.set("curr", pageData.curr) + return sp; +} + +const constructUrl = (url: string, sp: URLSearchParams) => { + return `${url}?${sp.toString()}` +} + +type Direction = "next" | "previous" + +type APIType = "page" | "offset" | "cursor" | "link" + +const constructUrlFromSP = (apiType: APIType, sp: URLSearchParams): string | null => { + switch(apiType) { + case "page": + case "offset": + case "cursor": + return `${BASE_URL}?${sp.toString()}` + case "link": + return sp.get("curr"); + } +} + +const updateSP = ( + apiType: APIType, + direction: Direction, + sp: URLSearchParams +) => { + switch(apiType) { + case "page": + sp.set( + "page", + direction === "next" ? + String(Number(sp.get("page") as string) + 1) : + String(Math.max(Number(sp.get("page") as string) - 1, 1)) + ); + return sp; + case "offset": + sp.set("offset", + direction === "next" ? + String(Number(sp.get("offset") as string) + Number(sp.get("offset") as string)) : + String(Number(sp.get("offset") as string) - Math.min(Number(sp.get("offset") as string))) + ); + return sp; + case "cursor": + // TODO get id from people: + return sp; + case "link": + const curr = sp.get("curr") as string; + const next = sp.get("next"); + const previous = sp.get("previous"); + if (direction === "next") { + sp.delete("next"); + if (next) { + sp.set("curr", next); + } + sp.set("previous", curr); + } else { + sp.delete("previous"); + if (previous) { + sp.set("curr", previous); + } + sp.set("next", curr); + } + sp.set("offset", String(Number(sp.get("offset")) as number + 1)); + return sp; + } +} + +const usePageData = (api: APIType) => { + const [apiType] = useState<APIType>(api); + const [people, setPeople] = useState<Person[]>([]); + const [hasNext, setHasNext] = useState(true); + const [hasPrevious, setHasPrevious] = useState(true); + const abortRef = useRef<AbortController | null>(null); + + /** Single path for: fetch -> set state -> reflect URL */ + const load = useCallback(async (url: string, replace = false) => { + // cancel any prior request + abortRef.current?.abort(); + const ac = new AbortController(); + abortRef.current = ac; + + const page = await getPeople(url, ac.signal); + + // state + setPeople(page.results); + + if (apiType === "link") { + const sp = constructSearchParam(page); + setHasNext(Boolean(page.next)); + setHasPrevious(Boolean(page.previous)); + const target = `${location.pathname}?${sp.toString()}`; + replace ? history.replaceState(null, "", target) : history.pushState(null, "", target); + } else { + const sp = new URL(url).searchParams; + const target = `${location.pathname}?${sp.toString()}`; + replace ? history.replaceState(null, "", target) : history.pushState(null, "", target); + } + }, [apiType]); + + // Initial hydrate: reuse load() + useEffect(() => { + const sp = new URLSearchParams(location.search); + const url = constructUrlFromSP(apiType, sp) ?? BASE_URL; + load(url, /* replace */ true); + return () => abortRef.current?.abort(); + }, [apiType, load]); + + // Button handler: reuse load() + const handleOnClick = useCallback((direction: Direction) => { + const sp = new URLSearchParams(location.search); + const nextSP = updateSP(apiType, direction, sp); + const nextUrl = constructUrlFromSP(apiType, nextSP); + if (nextUrl) load(nextUrl); + }, [apiType, load]); + + return { people, hasNext, hasPrevious, handleOnClick }; +}; + +const PageTableComponent = () => { + const {hasNext, hasPrevious, people, handleOnClick} = usePageData("link"); + return ( + <> + <div style={{...styles.row, width: "30%", borderBottom: "none" }}> + <button disabled={!hasNext} onClick={() => handleOnClick("next")}> Next </button> + <button disabled={!hasPrevious} onClick={() => handleOnClick("previous")}> Previous </button> + </div> + <div style={styles.row}> + <p>Name</p> + <p>Birth_year</p> + </div> + {people.map((person) => (<PersonRow person={person} />) )} + </> + ) +} + +export { + PageTableComponent, +}