UNPKG

@mui/x-data-grid

Version:

The Community plan edition of the MUI X Data Grid components.

213 lines (211 loc) 9.33 kB
'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()) } }; };