diff --git a/src/app/components/SpeciesColorPicker.js b/src/app/components/SpeciesColorPicker.js index 79fd548..5746a03 100644 --- a/src/app/components/SpeciesColorPicker.js +++ b/src/app/components/SpeciesColorPicker.js @@ -310,17 +310,45 @@ const SpeciesColorSelect = ({ ); }; -export function useAllValidPetPoses(fetchOptions) { - const { loading, error, data: validsBuffer } = useFetch( - "/api/validPetPoses", - { ...fetchOptions, responseType: "arrayBuffer" } - ); +let cachedResponseForAllValidPetPoses = null; + +/** + * useAllValidPoses fetches the valid pet poses, as a `valids` object ready to + * pass into the various validity-checker utility functions! + * + * In addition to the network caching, we globally cache this response in the + * client code as `cachedResponseForAllValidPetPoses`. This helps prevent extra + * re-renders when client-side navigating between pages, similar to how cached + * data from GraphQL serves on the first render, without a loading state. + */ +export function useAllValidPetPoses() { + const networkResponse = useFetch("/api/validPetPoses", { + responseType: "arrayBuffer", + // If we already have globally-cached valids, skip the request. + skip: cachedResponseForAllValidPetPoses != null, + }); + + // Use the globally-cached response if we have one, or await the network + // response if not. + const response = cachedResponseForAllValidPetPoses || networkResponse; + const { loading, error, data: validsBuffer } = response; const valids = React.useMemo( () => validsBuffer && new DataView(validsBuffer), [validsBuffer] ); + // Once a network response comes in, save it as the globally-cached response. + React.useEffect(() => { + if ( + networkResponse && + !networkResponse.loading && + !cachedResponseForAllValidPetPoses + ) { + cachedResponseForAllValidPetPoses = networkResponse; + } + }, [networkResponse]); + return { loading, error, valids }; } diff --git a/src/app/util.js b/src/app/util.js index c78c0ea..59f7772 100644 --- a/src/app/util.js +++ b/src/app/util.js @@ -241,14 +241,14 @@ export function usePageTitle(title, { skip = false } = {}) { * * Our limited API is designed to match the `use-http` library! */ -export function useFetch(url, { responseType, ...fetchOptions }) { +export function useFetch(url, { responseType, skip, ...fetchOptions }) { // Just trying to be clear about what you'll get back ^_^` If we want to // fetch non-binary data later, extend this and get something else from res! if (responseType !== "arrayBuffer") { throw new Error(`unsupported responseType ${responseType}`); } - const [loading, setLoading] = React.useState(true); + const [loading, setLoading] = React.useState(skip ? false : true); const [error, setError] = React.useState(null); const [data, setData] = React.useState(null); @@ -258,6 +258,10 @@ export function useFetch(url, { responseType, ...fetchOptions }) { const fetchOptionsAsJson = JSON.stringify(fetchOptions); React.useEffect(() => { + if (skip) { + return; + } + let canceled = false; fetch(url, JSON.parse(fetchOptionsAsJson)) @@ -284,7 +288,7 @@ export function useFetch(url, { responseType, ...fetchOptions }) { return () => { canceled = true; }; - }, [url, fetchOptionsAsJson]); + }, [skip, url, fetchOptionsAsJson]); return { loading, error, data }; }