@mui/x-data-grid
Version:
The Community plan edition of the MUI X Data Grid components.
213 lines (211 loc) • 9.33 kB
JavaScript
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["skipCache"];
import * as React from 'react';
import useLazyRef from '@mui/utils/useLazyRef';
import debounce from '@mui/utils/debounce';
import { warnOnce } from '@mui/x-internals/warning';
import { isDeepEqual } from '@mui/x-internals/isDeepEqual';
import { GRID_ROOT_GROUP_ID } from "../rows/gridRowsUtils.js";
import { runIf } from "../../../utils/utils.js";
import { GridStrategyGroup } from "../../core/strategyProcessing/index.js";
import { useGridSelector } from "../../utils/useGridSelector.js";
import { gridPaginationModelSelector } from "../pagination/gridPaginationSelector.js";
import { gridGetRowsParamsSelector } from "./gridDataSourceSelector.js";
import { CacheChunkManager, DataSourceRowsUpdateStrategy } from "./utils.js";
import { GridDataSourceCacheDefault } from "./cache.js";
import { GridGetRowsError, GridUpdateRowError } from "./gridDataSourceError.js";
const noopCache = {
clear: () => {},
get: () => undefined,
set: () => {}
};
function getCache(cacheProp, options = {}) {
if (cacheProp === null) {
return noopCache;
}
return cacheProp ?? new GridDataSourceCacheDefault(options);
}
export const useGridDataSourceBase = (apiRef, props, options = {}) => {
const setStrategyAvailability = React.useCallback(() => {
apiRef.current.setStrategyAvailability(GridStrategyGroup.DataSource, DataSourceRowsUpdateStrategy.Default, props.dataSource ? () => true : () => false);
}, [apiRef, props.dataSource]);
const [defaultRowsUpdateStrategyActive, setDefaultRowsUpdateStrategyActive] = React.useState(false);
const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector);
const lastRequestId = React.useRef(0);
const onDataSourceErrorProp = props.onDataSourceError;
const cacheChunkManager = useLazyRef(() => {
if (!props.pagination) {
return new CacheChunkManager(paginationModel.pageSize);
}
const sortedPageSizeOptions = props.pageSizeOptions.map(option => typeof option === 'number' ? option : option.value).sort((a, b) => a - b);
const cacheChunkSize = Math.min(paginationModel.pageSize, sortedPageSizeOptions[0]);
return new CacheChunkManager(cacheChunkSize);
}).current;
const [cache, setCache] = React.useState(() => getCache(props.dataSourceCache, options.cacheOptions));
const fetchRows = React.useCallback(async (parentId, params) => {
const getRows = props.dataSource?.getRows;
if (!getRows) {
return;
}
if (parentId && parentId !== GRID_ROOT_GROUP_ID && props.signature !== 'DataGrid') {
options.fetchRowChildren?.([parentId]);
return;
}
options.clearDataSourceState?.();
const _ref = params || {},
{
skipCache
} = _ref,
getRowsParams = _objectWithoutPropertiesLoose(_ref, _excluded);
const fetchParams = _extends({}, gridGetRowsParamsSelector(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), getRowsParams);
const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams);
const responses = cacheKeys.map(cacheKey => cache.get(cacheKey));
if (!skipCache && responses.every(response => response !== undefined)) {
apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', {
response: CacheChunkManager.mergeResponses(responses),
fetchParams
});
return;
}
// Manage loading state only for the default strategy
if (defaultRowsUpdateStrategyActive || apiRef.current.getRowsCount() === 0) {
apiRef.current.setLoading(true);
}
const requestId = lastRequestId.current + 1;
lastRequestId.current = requestId;
try {
const getRowsResponse = await getRows(fetchParams);
const cacheResponses = cacheChunkManager.splitResponse(fetchParams, getRowsResponse);
cacheResponses.forEach((response, key) => cache.set(key, response));
if (lastRequestId.current === requestId) {
apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', {
response: getRowsResponse,
fetchParams
});
}
} catch (originalError) {
if (lastRequestId.current === requestId) {
apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', {
error: originalError,
fetchParams
});
if (typeof onDataSourceErrorProp === 'function') {
onDataSourceErrorProp(new GridGetRowsError({
message: originalError?.message,
params: fetchParams,
cause: originalError
}));
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(['MUI X: A call to `dataSource.getRows()` threw an error which was not handled because `onDataSourceError()` is missing.', 'To handle the error pass a callback to the `onDataSourceError` prop, for example `<DataGrid onDataSourceError={(error) => ...} />`.', 'For more detail, see https://mui.com/x/react-data-grid/server-side-data/#error-handling.'], 'error');
}
}
} finally {
if (defaultRowsUpdateStrategyActive && lastRequestId.current === requestId) {
apiRef.current.setLoading(false);
}
}
}, [cacheChunkManager, cache, apiRef, defaultRowsUpdateStrategyActive, props.dataSource?.getRows, onDataSourceErrorProp, options, props.signature]);
const handleStrategyActivityChange = React.useCallback(() => {
setDefaultRowsUpdateStrategyActive(apiRef.current.getActiveStrategy(GridStrategyGroup.DataSource) === DataSourceRowsUpdateStrategy.Default);
}, [apiRef]);
const handleDataUpdate = React.useCallback(params => {
if ('error' in params) {
apiRef.current.setRows([]);
return;
}
const {
response
} = params;
if (response.rowCount !== undefined) {
apiRef.current.setRowCount(response.rowCount);
}
apiRef.current.setRows(response.rows);
apiRef.current.unstable_applyPipeProcessors('processDataSourceRows', {
params: params.fetchParams,
response
}, true);
}, [apiRef]);
const dataSourceUpdateRow = props.dataSource?.updateRow;
const handleEditRowOption = options.handleEditRow;
const editRow = React.useCallback(async params => {
if (!dataSourceUpdateRow) {
return undefined;
}
try {
const finalRowUpdate = await dataSourceUpdateRow(params);
if (typeof handleEditRowOption === 'function') {
handleEditRowOption(params, finalRowUpdate);
return finalRowUpdate;
}
apiRef.current.updateNestedRows([finalRowUpdate], []);
if (finalRowUpdate && !isDeepEqual(finalRowUpdate, params.previousRow)) {
// Reset the outdated cache, only if the row is _actually_ updated
apiRef.current.dataSource.cache.clear();
}
return finalRowUpdate;
} catch (errorThrown) {
if (typeof onDataSourceErrorProp === 'function') {
onDataSourceErrorProp(new GridUpdateRowError({
message: errorThrown?.message,
params,
cause: errorThrown
}));
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(['MUI X: A call to `dataSource.updateRow()` threw an error which was not handled because `onDataSourceError()` is missing.', 'To handle the error pass a callback to the `onDataSourceError` prop, for example `<DataGrid onDataSourceError={(error) => ...} />`.', 'For more detail, see https://mui.com/x/react-data-grid/server-side-data/#error-handling.'], 'error');
}
throw errorThrown; // Let the caller handle the error further
}
}, [apiRef, dataSourceUpdateRow, onDataSourceErrorProp, handleEditRowOption]);
const dataSourceApi = {
dataSource: {
fetchRows,
cache,
editRow
}
};
const debouncedFetchRows = React.useMemo(() => debounce(fetchRows, 0), [fetchRows]);
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
if (props.dataSourceCache === undefined) {
return;
}
const newCache = getCache(props.dataSourceCache, options.cacheOptions);
setCache(prevCache => prevCache !== newCache ? newCache : prevCache);
}, [props.dataSourceCache, options.cacheOptions]);
React.useEffect(() => {
if (props.dataSource) {
apiRef.current.dataSource.cache.clear();
apiRef.current.dataSource.fetchRows();
}
return () => {
// ignore the current request on unmount
lastRequestId.current += 1;
};
}, [apiRef, props.dataSource]);
return {
api: {
public: dataSourceApi
},
debouncedFetchRows,
strategyProcessor: {
strategyName: DataSourceRowsUpdateStrategy.Default,
group: 'dataSourceRowsUpdate',
processor: handleDataUpdate
},
setStrategyAvailability,
cacheChunkManager,
cache,
events: {
strategyAvailabilityChange: handleStrategyActivityChange,
sortModelChange: runIf(defaultRowsUpdateStrategyActive, () => debouncedFetchRows()),
filterModelChange: runIf(defaultRowsUpdateStrategyActive, () => debouncedFetchRows()),
paginationModelChange: runIf(defaultRowsUpdateStrategyActive, () => debouncedFetchRows())
}
};
};