@knowmax/genericlist-core
Version:
Knowmax Generic list with basic CRUD support without any user interface implementation.
141 lines (140 loc) • 5.72 kB
JavaScript
import { useEffect, useMemo, useState, useCallback } from "react";
import { headers, FetchError } from "@knowmax/http-utils";
/**
* A simplified React hook for fetching paginated list data from an API endpoint.
*
* This hook is designed as a lightweight alternative to the more complex `useList` hook,
* providing essential list functionality without advanced features like caching,
* state management, or CRUD operations.
*
* **Key Features:**
* - Simple pagination support
* - Automatic request abortion on unmount/dependency changes
* - Built-in loading and error states
* - TypeScript support with generic type parameter
* - Manual refetch capability
*
* **Use Cases:**
* - Read-only data lists
* - Simple pagination scenarios
* - When you don't need advanced caching or state management
* - Lightweight components with minimal list requirements
*
* @template T - The type of objects in the list
* @param configuration - Configuration object containing endpoint, authentication, and query parameters
* @param dependencies - Optional React dependencies array to trigger refetch when values change
* @returns An object containing the list result, loading state, error state, and refetch function
*
* @example
* ```tsx
* // Basic usage
* const { result, isLoading, error, refetch } = useSimpleList<User>({
* endpoint: '/api/users',
* token: 'your-auth-token',
* pageSize: 20,
* page: 1
* });
*
* // With filtering and ordering
* const { result, isLoading, error } = useSimpleList<Product>({
* endpoint: '/api/products',
* token: authToken,
* filter: 'category eq "electronics"',
* order: 'name ASC',
* count: true
* });
*
* // With dependencies
* const [searchTerm, setSearchTerm] = useState('');
* const { result, isLoading } = useSimpleList<Article>({
* endpoint: '/api/articles',
* token: authToken,
* filter: searchTerm ? `title contains "${searchTerm}"` : undefined
* }, [searchTerm]);
* ```
*
* @see {@link useList} for the full-featured alternative with caching and CRUD operations
*/
export const useSimpleList = (configuration, dependencies) => {
const [result, setResult] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
const [status, setStatus] = useState(undefined);
/**
* Internal function to fetch data from the API endpoint.
* Handles request building, authentication, error handling, and abort signals.
*
* @param abortSignal - Optional AbortSignal to cancel the request
*/
const fetchData = useCallback(async (abortSignal) => {
if (configuration.token) {
try {
setIsLoading(true);
setError(undefined);
// Build request body with all parameters using strongly typed IListRequest
const requestBody = {
take: configuration.pageSize ?? 10,
skip: ((configuration.page ?? 1) - 1) * (configuration.pageSize ?? 10),
orderBy: configuration.order || '',
filter: configuration.filter,
count: configuration.count ?? false
};
// Build headers with optional bearer token and JSON content type
const requestHeaders = headers().withContentTypeJson().withBearer(configuration.token);
const response = await fetch(configuration.endpoint, {
method: 'post',
headers: requestHeaders.export(),
body: JSON.stringify(requestBody),
signal: abortSignal
});
setStatus(response.status);
if (!response.ok) {
const errorData = await response.json().catch(() => undefined);
throw new FetchError(response, errorData);
}
const fetchedResult = await response.json();
// Only update state if the request wasn't aborted
if (!abortSignal?.aborted) {
setResult(fetchedResult);
}
}
catch (err) {
// Don't set error state if the request was aborted
if (err instanceof Error && err.name === 'AbortError') {
return;
}
const errorInstance = err instanceof Error ? err : new Error('An unknown error occurred');
if (!abortSignal?.aborted) {
setError(errorInstance);
setResult(undefined);
}
}
finally {
if (!abortSignal?.aborted) {
setIsLoading(false);
}
}
}
}, [configuration.endpoint, configuration.token, configuration.order, configuration.filter, configuration.page, configuration.pageSize]);
/**
* Public function to manually trigger a data refetch.
* Useful for refreshing data after external changes or user actions.
*/
const refetch = useCallback(async () => {
await fetchData();
}, [fetchData]);
useEffect(() => {
const abortController = new AbortController();
fetchData(abortController.signal);
// Cleanup function to abort the request if component unmounts or dependencies change
return () => {
abortController.abort();
};
}, dependencies ? [fetchData, configuration.token, ...dependencies] : [fetchData]);
return useMemo(() => ({
result,
isLoading,
error,
refetch
}), [result, isLoading, error, refetch]);
};