ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
231 lines • 9.65 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useList = void 0;
const react_1 = require("react");
const get_js_1 = __importDefault(require("lodash/get.js"));
const isEqual_js_1 = __importDefault(require("lodash/isEqual.js"));
const util_1 = require("../../util/index.cjs");
const core_1 = require("../../core/index.cjs");
const usePaginationState_1 = __importDefault(require("../usePaginationState.cjs"));
const useSortState_1 = __importDefault(require("../useSortState.cjs"));
const useRecordSelection_1 = require("./useRecordSelection.cjs");
const fetch_1 = require("../../dataProvider/fetch.cjs");
const export_1 = require("../../export/index.cjs");
const refetch = () => {
throw new Error('refetch is not available for a ListContext built from useList based on local data');
};
/**
* Handle filtering, sorting and pagination on local data.
*
* Returns the data and callbacks expected by <ListContext>.
*
* @example
* const data = [
* { id: 1, name: 'Arnold' },
* { id: 2, name: 'Sylvester' },
* { id: 3, name: 'Jean-Claude' },
* ]
*
* const MyComponent = () => {
* const listContext = useList({ data });
* return (
* <ListContextProvider value={listContext}>
* <Datagrid>
* <TextField source="id" />
* <TextField source="name" />
* </Datagrid>
* </ListContextProvider>
* );
* };
*
* @param {UseListOptions} props
* @param {RaRecord[]} props.data An array of records
* @param {Boolean} props.isFetching: Optional. A boolean indicating whether the data is being loaded
* @param {Boolean} props.isLoading: Optional. A boolean indicating whether the data has been loaded at least once
* @param {Error | String} props.error: Optional. The error if any occurred while loading the data
* @param {Object} props.filter: Optional. An object containing the filters applied on the data
* @param {Number} props.page: Optional. The initial page index
* @param {Number} props.perPage: Optional. The initial page size
* @param {SortPayload} props.sort: Optional. The initial sort (field and order)
* @param {filterCallback} prop.filterCallback Optional. A function that allows you to make a custom filter
*/
const useList = (props) => {
const { data, error, filter = defaultFilter, isFetching = false, isLoading = false, isPaused = false, isPending = false, isPlaceholderData = false, page: initialPage = 1, perPage: initialPerPage = 1000, sort: initialSort, filterCallback = defaultFilterCallback, exporter = export_1.defaultExporter, } = props;
const resource = (0, core_1.useResourceContext)(props);
const [finalItems, setFinalItems] = (0, react_1.useState)(() => ({
data,
total: data ? data.length : undefined,
}));
// pagination logic
const { page, setPage, perPage, setPerPage } = (0, usePaginationState_1.default)({
page: initialPage,
perPage: initialPerPage,
});
// sort logic
const { sort, setSort: setSortState } = (0, useSortState_1.default)(initialSort);
const setSort = (0, react_1.useCallback)((sort) => {
setSortState(sort);
setPage(1);
}, [setPage, setSortState]);
// selection logic
const [selectedIds, selectionModifiers] = (0, useRecordSelection_1.useRecordSelection)(resource
? {
resource,
}
: { disableSyncWithStore: true });
const onUnselectItems = (0, react_1.useCallback)((fromAllStoreKeys) => {
return selectionModifiers.unselect(selectedIds, fromAllStoreKeys);
}, [selectedIds, selectionModifiers]);
// filter logic
const filterRef = (0, react_1.useRef)(filter);
const [displayedFilters, setDisplayedFilters] = (0, react_1.useState)({});
const [filterValues, setFilterValues] = (0, react_1.useState)(filter);
const hideFilter = (0, react_1.useCallback)((filterName) => {
setDisplayedFilters(previousState => {
const { [filterName]: _, ...newState } = previousState;
return newState;
});
setFilterValues(previousState => {
const { [filterName]: _, ...newState } = previousState;
return newState;
});
}, [setDisplayedFilters, setFilterValues]);
const showFilter = (0, react_1.useCallback)((filterName, defaultValue) => {
setDisplayedFilters(previousState => ({
...previousState,
[filterName]: true,
}));
setFilterValues(previousState => (0, util_1.removeEmpty)({
...previousState,
[filterName]: defaultValue,
}));
}, [setDisplayedFilters, setFilterValues]);
const setFilters = (0, react_1.useCallback)((filters, displayedFilters = undefined) => {
setFilterValues((0, util_1.removeEmpty)(filters));
if (displayedFilters) {
setDisplayedFilters(displayedFilters);
}
setPage(1);
}, [setDisplayedFilters, setFilterValues, setPage]);
// handle filter prop change
(0, react_1.useEffect)(() => {
if (!(0, isEqual_js_1.default)(filter, filterRef.current)) {
filterRef.current = filter;
setFilterValues(filter);
}
}, [filter]);
const applyFilterAndSort = (0, react_1.useCallback)((records) => {
let tempData = records;
if (filterValues) {
const flattenFilterValues = (0, fetch_1.flattenObject)(filterValues);
tempData = records
.filter(record => Object.entries(flattenFilterValues).every(([filterName, filterValue]) => {
const recordValue = (0, get_js_1.default)(record, filterName);
const result = Array.isArray(recordValue)
? Array.isArray(filterValue)
? recordValue.some(item => filterValue.includes(item))
: recordValue.includes(filterValue)
: Array.isArray(filterValue)
? filterValue.includes(recordValue)
: filterName === 'q' // special full-text filter
? Object.keys(record).some(key => typeof record[key] ===
'string' &&
record[key]
.toLowerCase()
.includes(filterValue.toLowerCase()))
: filterValue == recordValue; // eslint-disable-line eqeqeq
return result;
}))
.filter(filterCallback);
}
if (sort.field) {
tempData = tempData.sort((a, b) => {
if ((0, get_js_1.default)(a, sort.field) > (0, get_js_1.default)(b, sort.field)) {
return sort.order === 'ASC' ? 1 : -1;
}
if ((0, get_js_1.default)(a, sort.field) < (0, get_js_1.default)(b, sort.field)) {
return sort.order === 'ASC' ? -1 : 1;
}
return 0;
});
}
return tempData;
}, [filterValues, filterCallback, sort.field, sort.order]);
// We do all the data processing (filtering, sorting, paginating) client-side
(0, react_1.useEffect)(() => {
if (isPending || !data)
return;
const filteredAndSorted = applyFilterAndSort(data);
const filteredLength = filteredAndSorted.length;
const paginatedData = filteredAndSorted.slice((page - 1) * perPage, page * perPage);
setFinalItems({
data: paginatedData,
total: filteredLength,
});
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(data),
applyFilterAndSort,
isPending,
page,
perPage,
setFinalItems,
]);
const onSelectAll = (0, react_1.useCallback)(() => {
const allIds = data?.map(({ id }) => id) || [];
selectionModifiers.select(allIds);
}, [data, selectionModifiers]);
const getData = (0, react_1.useCallback)(async ({ maxResults } = {}) => {
if (isPending || !data) {
return [];
}
const filteredAndSorted = applyFilterAndSort(data);
if (maxResults != null) {
return filteredAndSorted.slice(0, maxResults);
}
return filteredAndSorted;
}, [applyFilterAndSort, data, isPending]);
return {
sort,
data: isPending ? undefined : finalItems?.data ?? [],
defaultTitle: '',
error: error ?? null,
displayedFilters,
exporter,
filterValues,
hasNextPage: finalItems?.total == null
? false
: page * perPage < finalItems.total,
hasPreviousPage: page > 1,
hideFilter,
isFetching,
isLoading,
isPaused,
isPending,
isPlaceholderData,
onSelect: selectionModifiers.select,
onSelectAll,
onToggleItem: selectionModifiers.toggle,
onUnselectItems,
page,
perPage,
resource: '',
refetch,
selectedIds,
setFilters,
setPage,
setPerPage,
setSort,
showFilter,
total: finalItems?.total,
getData,
};
};
exports.useList = useList;
const defaultFilter = {};
const defaultFilterCallback = (record) => Boolean(record);
//# sourceMappingURL=useList.js.map