Skip to content

Instantly share code, notes, and snippets.

@toky-nomena
Last active September 18, 2024 13:06
Show Gist options
  • Save toky-nomena/7d18638d1dd81eabbb7f825d9d79b4ef to your computer and use it in GitHub Desktop.
Save toky-nomena/7d18638d1dd81eabbb7f825d9d79b4ef to your computer and use it in GitHub Desktop.
import React, { useState, useEffect, useTransition } from 'react';
import { useQuery, useQueryClient, QueryFunction } from 'react-query';
import axios, { AxiosError } from 'axios';
import Select from 'react-select';
interface Option {
value: string;
label: string;
}
interface OptionDetails {
id: string;
description: string;
}
const CACHE_NAME = 'api-cache';
// Generic fetch function for GET requests with caching
const fetchFromApiWithCache = async <T,>(
url: string,
signal?: AbortSignal
): Promise<T> => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(url);
if (cachedResponse) {
return cachedResponse.json();
}
const { data } = await axios.get<T>(url, { signal });
const cacheResponse = new Response(JSON.stringify(data));
await cache.put(url, cacheResponse);
return data;
};
// Generic query function
const createQueryFunction = <T,>(baseUrl: string): QueryFunction<T, [string, string]> => {
return async ({ queryKey, signal }) => {
const [, id] = queryKey;
const url = `${baseUrl}/${id}`;
return fetchFromApiWithCache<T>(url, signal);
};
};
// Specific query function
const fetchOptionDetails = createQueryFunction<OptionDetails>('https://api.example.com/options');
// Main component
const SelectWithDetails: React.FC = () => {
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
const [isPending, startTransition] = useTransition();
const [showDetails, setShowDetails] = useState(false);
const queryClient = useQueryClient();
const { data: optionDetails, isLoading, error } = useQuery<OptionDetails, AxiosError>(
['optionDetails', selectedOption?.value ?? ''],
fetchOptionDetails,
{
enabled: !!selectedOption,
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
}
);
useEffect(() => {
return () => {
if (selectedOption) {
queryClient.cancelQueries(['optionDetails', selectedOption.value]);
}
};
}, [selectedOption, queryClient]);
const handleOptionChange = (newOption: Option | null) => {
setSelectedOption(newOption);
// Use startTransition for low priority rendering of details
startTransition(() => {
setShowDetails(!!newOption);
});
};
const options: Option[] = [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
];
return (
<div>
<Select
options={options}
value={selectedOption}
onChange={handleOptionChange}
isClearable
placeholder="Select an option"
/>
{isPending && <p>Loading...</p>}
{showDetails && (
<React.Suspense fallback={<p>Loading details...</p>}>
<OptionDetailsDisplay
isLoading={isLoading}
error={error}
optionDetails={optionDetails}
/>
</React.Suspense>
)}
</div>
);
};
// Separate component for option details
const OptionDetailsDisplay: React.FC<{
isLoading: boolean;
error: AxiosError | null;
optionDetails: OptionDetails | undefined;
}> = ({ isLoading, error, optionDetails }) => {
if (isLoading) return <p>Loading option details...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!optionDetails) return null;
return (
<div>
<h3>Option Details:</h3>
<p>ID: {optionDetails.id}</p>
<p>Description: {optionDetails.description}</p>
</div>
);
};
export default SelectWithDetails;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment