From e9cf90180f9458708ded955d547df6ce8211419f Mon Sep 17 00:00:00 2001 From: Yura Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 14 May 2026 23:48:28 +0200 Subject: [PATCH] caching --- src/App.tsx | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a030ba7..2722b3d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { z } from "zod" -import { useReducer, useEffect, createContext, useContext } from "react" +import { useReducer, useRef, useEffect, createContext, useContext } from "react" // TODO: Use Material UI later. // TODO: Improve error-handling. Introduce some proper server response. @@ -31,13 +31,16 @@ const Dimension = { } // === Page === +type PageNumber = number + type Page = { - page: number + page: PageNumber limit: number // page size } +type PageKey = string const LIMIT = 10 -const FIRST_PAGE = 1 +const FIRST_PAGE: PageNumber = 1 const Page = { init(): Page { @@ -55,6 +58,10 @@ const Page = { eq(page0: Page, page1: Page): boolean { return page0.page === page1.page && page0.limit === page1.limit }, + // for hashing in maps to avoid identity problems + key(page: Page): PageKey { + return `${page.page}:${page.limit}` + }, } // === api === @@ -145,13 +152,31 @@ type Msg = function useApp(): [State, Dispatch] { const [state, dispatch] = useReducer(update, State.init()) + // === Caching API calls === + // Could also cache `Promise` in case two requests are made really fast one after another (not really the case in this app so whatever) + const cacheRef = useRef>(new Map()) + + async function getImageIdsCached(page: Page): Promise { + const pageKey = Page.key(page) + const maybeImages = cacheRef.current.get(pageKey) + if (maybeImages === undefined) { + console.log("CACHE-MISS") + const images = await getImageIds(page) + cacheRef.current.set(pageKey, images) + return images + } else { + console.log("CACHE-HIT") + return maybeImages + } + } + // === initialization & reloading === useEffect(() => { // TODO: error-handling - getImageIds(state.page).then((imageIds) => { + getImageIdsCached(state.page).then((imageIds) => { dispatch({ tag: "imagesReceived", forPage: state.page, imageIds }) }) - }, [state.page]) + }, [state.page]) // Would have been amazing if we could put `Page.key(state.page)` inside of this. Then the trouble with identity would be gone. But we can't, because React compiler and linter would complain /facepalm function update(state: State, msg: Msg): State { switch (msg.tag) { @@ -162,7 +187,6 @@ function useApp(): [State, Dispatch] { return state } case "previousButtonClicked": { - console.log("prev") const newPage = Page.previous(state.page) if (Page.eq(newPage, state.page)) { return state // preserves identity @@ -171,7 +195,6 @@ function useApp(): [State, Dispatch] { } } case "nextButtonClicked": { - console.log("next") return { ...state, page: Page.next(state.page), imageIds: Remote.loading() } } case "imageClicked": {