Skip to content

Instantly share code, notes, and snippets.

@WomB0ComB0
Created November 22, 2024 16:51
Show Gist options
  • Save WomB0ComB0/6d2b70f7077fb872cbf5dfe212d60164 to your computer and use it in GitHub Desktop.
Save WomB0ComB0/6d2b70f7077fb872cbf5dfe212d60164 to your computer and use it in GitHub Desktop.
Client-side fetch component to reduce excessive re-writing of the same sequence of code
'use client';
import React, { Suspense } from 'react';
import { ClientError, Loader } from '@/components'
import { fetcher } from "@/lib"
import { useSuspenseQuery } from '@tanstack/react-query';
import { catchError, parseCodePath } from '@/utils';
/**
* A React component that handles data fetching with built-in loading, error handling, and caching.
* Uses React Suspense and TanStack Query for efficient data management and rendering.
*
* @component
* @template T - The type of data being fetched
*
* @param {Object} props - Component props
* @param {string} props.url - The base URL to fetch data from
* @param {Record<string, string>} [props.params] - Optional query parameters to append to the URL
* @param {(data: T) => React.ReactNode} props.children - Render prop function that receives the fetched data
*
* @example
* ```tsx
* // Basic usage
* <DataLoader url="/api/users">
* {(data) => <UserList users={data} />}
* </DataLoader>
*
* // With query parameters
* <DataLoader
* url="/api/posts"
* params={{ category: "tech", limit: "10" }}
* >
* {(data) => <PostGrid posts={data} />}
* </DataLoader>
* ```
*
* @remarks
* - Implements automatic retrying and revalidation every 5 minutes
* - Caches data for 5 minutes before considering it stale
* - Shows loading spinner during data fetching
* - Handles and displays errors with ClientError component
* - Uses catchError utility for clean error handling
* - Supports URL query parameters
* - Integrates with React Suspense for loading states
*/
export const DataLoader: React.FC<{
url: string,
params?: Record<string, string>,
children: (data: any) => React.ReactNode
}> = ({ url, params, children }) => {
const { data, error } = useSuspenseQuery({
queryKey: [url, params],
queryFn: async () => {
const searchParams = params ? `?${new URLSearchParams(params)}` : '';
const fullUrl = `${url}${searchParams}`;
const [fetchError, response] = await catchError(
fetcher<any>(fullUrl).then(res => {
if (!res.ok) {
throw new Error(`${res.statusText} at DataLoader ${parseCodePath(fullUrl, fetch)}`);
}
return res.json();
})
);
if (fetchError) throw fetchError;
return response;
},
staleTime: 1000 * 60 * 5,
refetchInterval: 1000 * 60 * 5
});
return (
<Suspense fallback={<Loader />}>
{error && <ClientError error={error} />}
{children(data)}
</Suspense>
);
};
DataLoader.displayName = 'DataLoader';
export default DataLoader;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment