react-antd-admin-panel
Version:
Modern TypeScript-first React admin panel builder with Ant Design 6
233 lines • 7.51 kB
JavaScript
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { Get } from '../http/Get';
/**
* useList - React hook for list/table data management
*
* @template T - Data item type
* @param options - Hook options
* @returns Hook result with data, loading, pagination, and control functions
*
* @example
* ```tsx
* // Basic usage
* const { data, loading, refresh } = useList<User>({
* get: '/api/users',
* });
*
* // With pagination
* const { data, pagination, setPagination } = useList<User>({
* get: '/api/users',
* pagination: { pageSize: 10 },
* });
*
* // With filters
* const { data, filters, setFilters } = useList<User>({
* get: '/api/users',
* initialFilters: { status: 'active' },
* });
*
* // With selection
* const { data, selectedRowKeys, setSelectedRowKeys, selectedRows } = useList<User>({
* get: '/api/users',
* rowKey: 'id',
* });
*
* // With transform for custom API response
* const { data } = useList<User>({
* get: '/api/users',
* transform: (response) => ({
* data: response.items,
* total: response.totalCount,
* }),
* });
* ```
*/
export function useList(options) {
const { pagination: paginationConfig, initialFilters = {}, rowKey = 'id', enabled = true, } = options;
// State
const [data, setData] = useState([]);
const [loading, setLoading] = useState(enabled);
const [error, setError] = useState(undefined);
const [pagination, setPaginationState] = useState(() => ({
current: 1,
pageSize: 10,
total: 0,
...(paginationConfig !== false ? paginationConfig : {}),
}));
const [filters, setFiltersState] = useState(initialFilters);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
// Refs
const getInstanceRef = useRef(null);
const mountedRef = useRef(true);
const optionsRef = useRef(options);
optionsRef.current = options;
// Get row key for a record
const getRowKey = useCallback((record) => {
if (typeof rowKey === 'function') {
return rowKey(record);
}
return record[rowKey];
}, [rowKey]);
// Compute selected rows from data and selectedRowKeys
const selectedRows = useMemo(() => {
if (selectedRowKeys.length === 0)
return [];
return data.filter(item => selectedRowKeys.includes(getRowKey(item)));
}, [data, selectedRowKeys, getRowKey]);
/**
* Fetch data from the API
*/
const fetchData = useCallback(async () => {
// Cancel any pending request
if (getInstanceRef.current) {
getInstanceRef.current.cancel();
}
const currentOptions = optionsRef.current;
const currentGet = typeof currentOptions.get === 'string'
? { url: currentOptions.get }
: currentOptions.get;
// Build params with pagination and filters
const params = {
...currentGet.params,
...filters,
};
// Add pagination params if enabled
if (paginationConfig !== false) {
params.page = pagination.current;
params.pageSize = pagination.pageSize;
}
// Create new Get instance
const get = new Get()
.target(currentGet.url)
.params(params);
if (currentGet.headers) {
get.headers(currentGet.headers);
}
getInstanceRef.current = get;
if (mountedRef.current) {
setLoading(true);
setError(undefined);
}
try {
const response = await get.execute();
if (mountedRef.current) {
let resultData;
let total;
// Apply transform if provided
if (currentOptions.transform) {
const transformed = currentOptions.transform(response);
resultData = transformed.data;
total = transformed.total;
}
else if (Array.isArray(response)) {
// Response is already an array
resultData = response;
total = response.length;
}
else if (response && typeof response === 'object') {
// Try common response shapes
resultData = response.data || response.items || response.records || [];
total = response.total ?? response.totalCount ?? response.count ?? resultData.length;
}
else {
resultData = [];
total = 0;
}
setData(resultData);
if (paginationConfig !== false && total !== undefined) {
setPaginationState(prev => ({ ...prev, total }));
}
setLoading(false);
currentOptions.onSuccess?.(resultData);
}
}
catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
// Don't update state if request was aborted
if (error.name === 'CanceledError' || error.name === 'AbortError') {
return;
}
if (mountedRef.current) {
setError(error);
setLoading(false);
currentOptions.onError?.(error);
}
}
}, [filters, pagination.current, pagination.pageSize, paginationConfig]);
/**
* Refresh/refetch data
*/
const refresh = useCallback(async () => {
await fetchData();
}, [fetchData]);
/**
* Update pagination
*/
const setPagination = useCallback((newPagination) => {
setPaginationState(prev => ({ ...prev, ...newPagination }));
}, []);
/**
* Update filters
*/
const setFilters = useCallback((newFilters) => {
setFiltersState(newFilters);
// Reset to first page when filters change
setPaginationState(prev => ({ ...prev, current: 1 }));
}, []);
/**
* Clear selection
*/
const clearSelection = useCallback(() => {
setSelectedRowKeys([]);
}, []);
/**
* Reset to initial state
*/
const reset = useCallback(() => {
if (getInstanceRef.current) {
getInstanceRef.current.cancel();
}
setData([]);
setError(undefined);
setLoading(false);
setPaginationState({
current: 1,
pageSize: paginationConfig !== false ? (paginationConfig?.pageSize ?? 10) : 10,
total: 0,
});
setFiltersState(initialFilters);
setSelectedRowKeys([]);
}, [initialFilters, paginationConfig]);
// Fetch on mount and when dependencies change
useEffect(() => {
if (enabled) {
fetchData();
}
}, [enabled, fetchData]);
// Cleanup on unmount
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
if (getInstanceRef.current) {
getInstanceRef.current.cancel();
}
};
}, []);
return {
data,
loading,
error,
refresh,
pagination,
setPagination,
filters,
setFilters,
selectedRowKeys,
setSelectedRowKeys,
selectedRows,
clearSelection,
reset,
};
}
//# sourceMappingURL=useList.js.map