neue Homepage auf react basis

This commit is contained in:
2026-08-07 21:39:30 +02:00
parent 164bdd19ad
commit ee89442c04
52191 changed files with 1230242 additions and 58 deletions
@@ -0,0 +1,29 @@
import type { GridGetRowsParams, GridGetRowsResponse } from "../../../models/gridDataSource.mjs";
export type GridDataSourceCacheDefaultConfig = {
/**
* Time To Live for each cache entry in milliseconds.
* After this time the cache entry will become stale and the next query will result in cache miss.
* @default 300_000 (5 minutes)
*/
ttl?: number;
/**
* Function to generate a cache key from the params.
* @param {GridGetRowsParams} params The params to generate the cache key from.
* @returns {string} The cache key.
* @default `getKeyDefault()`
*/
getKey?: (params: GridGetRowsParams) => string;
};
export declare function getKeyDefault(params: GridGetRowsParams): string;
export declare class GridDataSourceCacheDefault {
private cache;
private ttl;
private getKey;
constructor({
ttl,
getKey
}: GridDataSourceCacheDefaultConfig);
set(key: GridGetRowsParams, value: GridGetRowsResponse): void;
get(key: GridGetRowsParams): GridGetRowsResponse | undefined;
clear(): void;
}
@@ -0,0 +1,29 @@
import type { GridGetRowsParams, GridGetRowsResponse } from "../../../models/gridDataSource.js";
export type GridDataSourceCacheDefaultConfig = {
/**
* Time To Live for each cache entry in milliseconds.
* After this time the cache entry will become stale and the next query will result in cache miss.
* @default 300_000 (5 minutes)
*/
ttl?: number;
/**
* Function to generate a cache key from the params.
* @param {GridGetRowsParams} params The params to generate the cache key from.
* @returns {string} The cache key.
* @default `getKeyDefault()`
*/
getKey?: (params: GridGetRowsParams) => string;
};
export declare function getKeyDefault(params: GridGetRowsParams): string;
export declare class GridDataSourceCacheDefault {
private cache;
private ttl;
private getKey;
constructor({
ttl,
getKey
}: GridDataSourceCacheDefaultConfig);
set(key: GridGetRowsParams, value: GridGetRowsResponse): void;
get(key: GridGetRowsParams): GridGetRowsResponse | undefined;
clear(): void;
}
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GridDataSourceCacheDefault = void 0;
exports.getKeyDefault = getKeyDefault;
function getKeyDefault(params) {
return JSON.stringify([params.filterModel, params.sortModel, params.start, params.end]);
}
class GridDataSourceCacheDefault {
constructor({
ttl = 300_000,
getKey = getKeyDefault
}) {
this.cache = {};
this.ttl = ttl;
this.getKey = getKey;
}
set(key, value) {
const keyString = this.getKey(key);
const expiry = Date.now() + this.ttl;
this.cache[keyString] = {
value,
expiry
};
}
get(key) {
const keyString = this.getKey(key);
const entry = this.cache[keyString];
if (!entry) {
return undefined;
}
if (Date.now() > entry.expiry) {
delete this.cache[keyString];
return undefined;
}
return entry.value;
}
clear() {
this.cache = {};
}
}
exports.GridDataSourceCacheDefault = GridDataSourceCacheDefault;
@@ -0,0 +1,36 @@
export function getKeyDefault(params) {
return JSON.stringify([params.filterModel, params.sortModel, params.start, params.end]);
}
export class GridDataSourceCacheDefault {
constructor({
ttl = 300_000,
getKey = getKeyDefault
}) {
this.cache = {};
this.ttl = ttl;
this.getKey = getKey;
}
set(key, value) {
const keyString = this.getKey(key);
const expiry = Date.now() + this.ttl;
this.cache[keyString] = {
value,
expiry
};
}
get(key) {
const keyString = this.getKey(key);
const entry = this.cache[keyString];
if (!entry) {
return undefined;
}
if (Date.now() > entry.expiry) {
delete this.cache[keyString];
return undefined;
}
return entry.value;
}
clear() {
this.cache = {};
}
}
@@ -0,0 +1,31 @@
import type { GridGetRowsParams, GridUpdateRowParams } from "../../../models/gridDataSource.mjs";
export declare class GridGetRowsError<T extends GridGetRowsParams = GridGetRowsParams> extends Error {
/**
* The parameters used in the failed request
*/
readonly params: T;
/**
* The original error that caused this error
*/
readonly cause?: Error;
constructor(options: {
message: string;
params: T;
cause?: Error;
});
}
export declare class GridUpdateRowError extends Error {
/**
* The parameters used in the failed request
*/
readonly params: GridUpdateRowParams;
/**
* The original error that caused this error
*/
readonly cause?: Error;
constructor(options: {
message: string;
params: GridUpdateRowParams;
cause?: Error;
});
}
@@ -0,0 +1,31 @@
import type { GridGetRowsParams, GridUpdateRowParams } from "../../../models/gridDataSource.js";
export declare class GridGetRowsError<T extends GridGetRowsParams = GridGetRowsParams> extends Error {
/**
* The parameters used in the failed request
*/
readonly params: T;
/**
* The original error that caused this error
*/
readonly cause?: Error;
constructor(options: {
message: string;
params: T;
cause?: Error;
});
}
export declare class GridUpdateRowError extends Error {
/**
* The parameters used in the failed request
*/
readonly params: GridUpdateRowParams;
/**
* The original error that caused this error
*/
readonly cause?: Error;
constructor(options: {
message: string;
params: GridUpdateRowParams;
cause?: Error;
});
}
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GridUpdateRowError = exports.GridGetRowsError = void 0;
class GridGetRowsError extends Error {
/**
* The parameters used in the failed request
*/
/**
* The original error that caused this error
*/
constructor(options) {
super(options.message);
this.name = 'GridGetRowsError';
this.params = options.params;
this.cause = options.cause;
}
}
exports.GridGetRowsError = GridGetRowsError;
class GridUpdateRowError extends Error {
/**
* The parameters used in the failed request
*/
/**
* The original error that caused this error
*/
constructor(options) {
super(options.message);
this.name = 'GridUpdateRowError';
this.params = options.params;
this.cause = options.cause;
}
}
exports.GridUpdateRowError = GridUpdateRowError;
@@ -0,0 +1,32 @@
export class GridGetRowsError extends Error {
/**
* The parameters used in the failed request
*/
/**
* The original error that caused this error
*/
constructor(options) {
super(options.message);
this.name = 'GridGetRowsError';
this.params = options.params;
this.cause = options.cause;
}
}
export class GridUpdateRowError extends Error {
/**
* The parameters used in the failed request
*/
/**
* The original error that caused this error
*/
constructor(options) {
super(options.message);
this.name = 'GridUpdateRowError';
this.params = options.params;
this.cause = options.cause;
}
}
@@ -0,0 +1,10 @@
export declare const gridGetRowsParamsSelector: (args_0: import("react").RefObject<{
state: import("../../../models/gridStateCommunity.mjs").GridStateCommunity;
} | null>) => {
groupKeys: never[];
paginationModel: import("@mui/x-data-grid").GridPaginationModel;
sortModel: import("@mui/x-data-grid").GridSortModel;
filterModel: import("@mui/x-data-grid").GridFilterModel;
start: number;
end: number;
};
@@ -0,0 +1,10 @@
export declare const gridGetRowsParamsSelector: (args_0: import("react").RefObject<{
state: import("../../../models/gridStateCommunity.js").GridStateCommunity;
} | null>) => {
groupKeys: never[];
paginationModel: import("@mui/x-data-grid").GridPaginationModel;
sortModel: import("@mui/x-data-grid").GridSortModel;
filterModel: import("@mui/x-data-grid").GridFilterModel;
start: number;
end: number;
};
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.gridGetRowsParamsSelector = void 0;
var _gridFilterSelector = require("../filter/gridFilterSelector");
var _gridSortingSelector = require("../sorting/gridSortingSelector");
var _gridPaginationSelector = require("../pagination/gridPaginationSelector");
var _createSelector = require("../../../utils/createSelector");
const gridGetRowsParamsSelector = exports.gridGetRowsParamsSelector = (0, _createSelector.createSelector)(_gridFilterSelector.gridFilterModelSelector, _gridSortingSelector.gridSortModelSelector, _gridPaginationSelector.gridPaginationModelSelector, (filterModel, sortModel, paginationModel) => ({
groupKeys: [],
paginationModel,
sortModel,
filterModel,
start: paginationModel.page * paginationModel.pageSize,
end: paginationModel.page * paginationModel.pageSize + paginationModel.pageSize - 1
}));
@@ -0,0 +1,12 @@
import { gridFilterModelSelector } from "../filter/gridFilterSelector.mjs";
import { gridSortModelSelector } from "../sorting/gridSortingSelector.mjs";
import { gridPaginationModelSelector } from "../pagination/gridPaginationSelector.mjs";
import { createSelector } from "../../../utils/createSelector.mjs";
export const gridGetRowsParamsSelector = createSelector(gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, (filterModel, sortModel, paginationModel) => ({
groupKeys: [],
paginationModel,
sortModel,
filterModel,
start: paginationModel.page * paginationModel.pageSize,
end: paginationModel.page * paginationModel.pageSize + paginationModel.pageSize - 1
}));
@@ -0,0 +1,2 @@
export { GridDataSourceCacheDefault, type GridDataSourceCacheDefaultConfig } from "./cache.mjs";
export { GridGetRowsError, GridUpdateRowError } from "./gridDataSourceError.mjs";
@@ -0,0 +1,2 @@
export { GridDataSourceCacheDefault, type GridDataSourceCacheDefaultConfig } from "./cache.js";
export { GridGetRowsError, GridUpdateRowError } from "./gridDataSourceError.js";
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "GridDataSourceCacheDefault", {
enumerable: true,
get: function () {
return _cache.GridDataSourceCacheDefault;
}
});
Object.defineProperty(exports, "GridGetRowsError", {
enumerable: true,
get: function () {
return _gridDataSourceError.GridGetRowsError;
}
});
Object.defineProperty(exports, "GridUpdateRowError", {
enumerable: true,
get: function () {
return _gridDataSourceError.GridUpdateRowError;
}
});
var _cache = require("./cache");
var _gridDataSourceError = require("./gridDataSourceError");
@@ -0,0 +1,2 @@
export { GridDataSourceCacheDefault } from "./cache.mjs";
export { GridGetRowsError, GridUpdateRowError } from "./gridDataSourceError.mjs";
@@ -0,0 +1,47 @@
import type { GridDataSourceCache, GridGetRowsParams, GridUpdateRowParams, GridGetRowsOptions } from "../../../models/gridDataSource.mjs";
import type { GridRowId, GridRowModel } from "../../../models/gridRows.mjs";
import type { GridDataSourceCacheDefaultConfig } from "./cache.mjs";
/**
* The parameters for the `fetchRows` method.
*/
export type GridDataSourceFetchRowsParams<T> = Partial<T> & GridGetRowsOptions;
export interface GridDataSourceFetchRowChildrenOptions {
showChildrenLoading?: boolean;
}
export interface GridDataSourceApi {
/**
* The data source API.
*/
dataSource: {
/**
* Fetches the rows from the server.
* If no `parentId` option is provided, it fetches the root rows.
* Any missing parameter from `params` will be filled from the state (sorting, filtering, etc.).
* @param {GridRowId} parentId The id of the parent node (default: `GRID_ROOT_GROUP_ID`).
* @param {GridDataSourceFetchRowsParams<GridGetRowsParams>} params Request parameters override.
* @returns {Promise<void>} A promise that resolves when the rows are fetched.
*/
fetchRows: (parentId?: GridRowId, params?: GridDataSourceFetchRowsParams<GridGetRowsParams>) => Promise<void>;
/**
* The data source cache object.
*/
cache: GridDataSourceCache;
/**
* Syncs the row with the server and updates in the grid.
* @param {GridUpdateRowParams} params The parameters for the edit operation.
* @returns {Promise<GridRowModel> | undefined} The updated row or `undefined` if `dataSource.updateRow` is not passed.
*/
editRow: (params: GridUpdateRowParams) => Promise<GridRowModel> | undefined;
};
}
export interface GridDataSourceApiBase {
fetchRows: GridDataSourceApi['dataSource']['fetchRows'];
cache: GridDataSourceApi['dataSource']['cache'];
editRow: GridDataSourceApi['dataSource']['editRow'];
}
export interface GridDataSourceBaseOptions {
cacheOptions?: GridDataSourceCacheDefaultConfig;
fetchRowChildren?: (parents: GridRowId[], fetchParams: GridGetRowsParams[], showChildrenLoading?: boolean) => void;
clearDataSourceState?: () => void;
handleEditRow?: (params: GridUpdateRowParams, updatedRow: GridRowModel) => void;
}
@@ -0,0 +1,47 @@
import type { GridDataSourceCache, GridGetRowsParams, GridUpdateRowParams, GridGetRowsOptions } from "../../../models/gridDataSource.js";
import type { GridRowId, GridRowModel } from "../../../models/gridRows.js";
import type { GridDataSourceCacheDefaultConfig } from "./cache.js";
/**
* The parameters for the `fetchRows` method.
*/
export type GridDataSourceFetchRowsParams<T> = Partial<T> & GridGetRowsOptions;
export interface GridDataSourceFetchRowChildrenOptions {
showChildrenLoading?: boolean;
}
export interface GridDataSourceApi {
/**
* The data source API.
*/
dataSource: {
/**
* Fetches the rows from the server.
* If no `parentId` option is provided, it fetches the root rows.
* Any missing parameter from `params` will be filled from the state (sorting, filtering, etc.).
* @param {GridRowId} parentId The id of the parent node (default: `GRID_ROOT_GROUP_ID`).
* @param {GridDataSourceFetchRowsParams<GridGetRowsParams>} params Request parameters override.
* @returns {Promise<void>} A promise that resolves when the rows are fetched.
*/
fetchRows: (parentId?: GridRowId, params?: GridDataSourceFetchRowsParams<GridGetRowsParams>) => Promise<void>;
/**
* The data source cache object.
*/
cache: GridDataSourceCache;
/**
* Syncs the row with the server and updates in the grid.
* @param {GridUpdateRowParams} params The parameters for the edit operation.
* @returns {Promise<GridRowModel> | undefined} The updated row or `undefined` if `dataSource.updateRow` is not passed.
*/
editRow: (params: GridUpdateRowParams) => Promise<GridRowModel> | undefined;
};
}
export interface GridDataSourceApiBase {
fetchRows: GridDataSourceApi['dataSource']['fetchRows'];
cache: GridDataSourceApi['dataSource']['cache'];
editRow: GridDataSourceApi['dataSource']['editRow'];
}
export interface GridDataSourceBaseOptions {
cacheOptions?: GridDataSourceCacheDefaultConfig;
fetchRowChildren?: (parents: GridRowId[], fetchParams: GridGetRowsParams[], showChildrenLoading?: boolean) => void;
clearDataSourceState?: () => void;
handleEditRow?: (params: GridUpdateRowParams, updatedRow: GridRowModel) => void;
}
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,7 @@
import type { RefObject } from '@mui/x-internals/types';
import type { GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.mjs";
import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.mjs";
/**
* Community version of the data source hook. Contains implementation of the `useGridDataSourceBase` hook.
*/
export declare const useGridDataSource: (apiRef: RefObject<GridPrivateApiCommunity>, props: DataGridProcessedProps) => void;
@@ -0,0 +1,7 @@
import type { RefObject } from '@mui/x-internals/types';
import type { GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.js";
import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.js";
/**
* Community version of the data source hook. Contains implementation of the `useGridDataSourceBase` hook.
*/
export declare const useGridDataSource: (apiRef: RefObject<GridPrivateApiCommunity>, props: DataGridProcessedProps) => void;
@@ -0,0 +1,33 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useGridDataSource = void 0;
var React = _interopRequireWildcard(require("react"));
var _useGridApiMethod = require("../../utils/useGridApiMethod");
var _useGridRegisterStrategyProcessor = require("../../core/strategyProcessing/useGridRegisterStrategyProcessor");
var _useGridEvent = require("../../utils/useGridEvent");
var _useGridDataSourceBase = require("./useGridDataSourceBase");
/**
* Community version of the data source hook. Contains implementation of the `useGridDataSourceBase` hook.
*/
const useGridDataSource = (apiRef, props) => {
const {
api,
strategyProcessor,
events,
setStrategyAvailability
} = (0, _useGridDataSourceBase.useGridDataSourceBase)(apiRef, props);
(0, _useGridApiMethod.useGridApiMethod)(apiRef, api.public, 'public');
(0, _useGridRegisterStrategyProcessor.useGridRegisterStrategyProcessor)(apiRef, strategyProcessor.strategyName, strategyProcessor.group, strategyProcessor.processor);
Object.entries(events).forEach(([event, handler]) => {
(0, _useGridEvent.useGridEvent)(apiRef, event, handler);
});
React.useEffect(() => {
setStrategyAvailability();
}, [setStrategyAvailability]);
};
exports.useGridDataSource = useGridDataSource;
@@ -0,0 +1,26 @@
'use client';
import * as React from 'react';
import { useGridApiMethod } from "../../utils/useGridApiMethod.mjs";
import { useGridRegisterStrategyProcessor } from "../../core/strategyProcessing/useGridRegisterStrategyProcessor.mjs";
import { useGridEvent as addEventHandler } from "../../utils/useGridEvent.mjs";
import { useGridDataSourceBase } from "./useGridDataSourceBase.mjs";
/**
* Community version of the data source hook. Contains implementation of the `useGridDataSourceBase` hook.
*/
export const useGridDataSource = (apiRef, props) => {
const {
api,
strategyProcessor,
events,
setStrategyAvailability
} = useGridDataSourceBase(apiRef, props);
useGridApiMethod(apiRef, api.public, 'public');
useGridRegisterStrategyProcessor(apiRef, strategyProcessor.strategyName, strategyProcessor.group, strategyProcessor.processor);
Object.entries(events).forEach(([event, handler]) => {
addEventHandler(apiRef, event, handler);
});
React.useEffect(() => {
setStrategyAvailability();
}, [setStrategyAvailability]);
};
@@ -0,0 +1,31 @@
import type { RefObject } from '@mui/x-internals/types';
import type { GridDataSourceCache } from "../../../models/gridDataSource.mjs";
import { CacheChunkManager, DataSourceRowsUpdateStrategy } from "./utils.mjs";
import type { GridDataSourceApi, GridDataSourceBaseOptions } from "./models.mjs";
import type { GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.mjs";
import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.mjs";
import type { GridStrategyProcessor } from "../../core/strategyProcessing/index.mjs";
import type { GridEventListener } from "../../../models/events/index.mjs";
import type { GridRowId } from "../../../models/gridRows.mjs";
export declare const useGridDataSourceBase: <Api extends GridPrivateApiCommunity>(apiRef: RefObject<Api>, props: Pick<DataGridProcessedProps, 'dataSource' | 'dataSourceCache' | 'dataSourceKeepPreviousData' | 'onDataSourceError' | 'pageSizeOptions' | 'pagination' | 'signature' | 'dataSourceRevalidateMs'>, options?: GridDataSourceBaseOptions) => {
api: {
public: GridDataSourceApi;
};
debouncedFetchRows: ((parentId?: GridRowId, params?: import("./models.mjs").GridDataSourceFetchRowsParams<import("@mui/x-data-grid").GridGetRowsParams>) => Promise<void>) & import("@mui/utils/debounce").Cancelable;
strategyProcessor: {
strategyName: DataSourceRowsUpdateStrategy;
group: 'dataSourceRootRowsUpdate';
processor: GridStrategyProcessor<"dataSourceRootRowsUpdate">;
};
setStrategyAvailability: () => void;
startPolling: () => void;
stopPolling: () => void;
cacheChunkManager: CacheChunkManager;
cache: GridDataSourceCache;
events: {
strategyAvailabilityChange: GridEventListener<"strategyAvailabilityChange">;
sortModelChange: (...params: unknown[]) => void;
filterModelChange: (...params: unknown[]) => void;
paginationModelChange: (...params: unknown[]) => void;
};
};
@@ -0,0 +1,31 @@
import type { RefObject } from '@mui/x-internals/types';
import type { GridDataSourceCache } from "../../../models/gridDataSource.js";
import { CacheChunkManager, DataSourceRowsUpdateStrategy } from "./utils.js";
import type { GridDataSourceApi, GridDataSourceBaseOptions } from "./models.js";
import type { GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.js";
import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.js";
import type { GridStrategyProcessor } from "../../core/strategyProcessing/index.js";
import type { GridEventListener } from "../../../models/events/index.js";
import type { GridRowId } from "../../../models/gridRows.js";
export declare const useGridDataSourceBase: <Api extends GridPrivateApiCommunity>(apiRef: RefObject<Api>, props: Pick<DataGridProcessedProps, 'dataSource' | 'dataSourceCache' | 'dataSourceKeepPreviousData' | 'onDataSourceError' | 'pageSizeOptions' | 'pagination' | 'signature' | 'dataSourceRevalidateMs'>, options?: GridDataSourceBaseOptions) => {
api: {
public: GridDataSourceApi;
};
debouncedFetchRows: ((parentId?: GridRowId, params?: import("./models.js").GridDataSourceFetchRowsParams<import("@mui/x-data-grid").GridGetRowsParams>) => Promise<void>) & import("@mui/utils/debounce").Cancelable;
strategyProcessor: {
strategyName: DataSourceRowsUpdateStrategy;
group: 'dataSourceRootRowsUpdate';
processor: GridStrategyProcessor<"dataSourceRootRowsUpdate">;
};
setStrategyAvailability: () => void;
startPolling: () => void;
stopPolling: () => void;
cacheChunkManager: CacheChunkManager;
cache: GridDataSourceCache;
events: {
strategyAvailabilityChange: GridEventListener<"strategyAvailabilityChange">;
sortModelChange: (...params: unknown[]) => void;
filterModelChange: (...params: unknown[]) => void;
paginationModelChange: (...params: unknown[]) => void;
};
};
@@ -0,0 +1,400 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useGridDataSourceBase = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var React = _interopRequireWildcard(require("react"));
var _useLazyRef = _interopRequireDefault(require("@mui/utils/useLazyRef"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _debounce = _interopRequireDefault(require("@mui/utils/debounce"));
var _warning = require("@mui/x-internals/warning");
var _isDeepEqual = require("@mui/x-internals/isDeepEqual");
var _gridRowsUtils = require("../rows/gridRowsUtils");
var _utils = require("../../../utils/utils");
var _strategyProcessing = require("../../core/strategyProcessing");
var _useGridSelector = require("../../utils/useGridSelector");
var _gridPaginationSelector = require("../pagination/gridPaginationSelector");
var _gridRowsSelector = require("../rows/gridRowsSelector");
var _gridDataSourceSelector = require("./gridDataSourceSelector");
var _utils2 = require("./utils");
var _cache = require("./cache");
var _gridDataSourceError = require("./gridDataSourceError");
const _excluded = ["skipCache", "keepChildrenExpanded", "showChildrenLoading"];
const noopCache = {
clear: () => {},
get: () => undefined,
set: () => {}
};
function getCache(cacheProp, options = {}) {
if (cacheProp === null) {
return noopCache;
}
return cacheProp ?? new _cache.GridDataSourceCacheDefault(options);
}
const useGridDataSourceBase = (apiRef, props, options = {}) => {
const setStrategyAvailability = React.useCallback(() => {
apiRef.current.setStrategyAvailability(_strategyProcessing.GridStrategyGroup.DataSource, _utils2.DataSourceRowsUpdateStrategy.Default, props.dataSource ? () => true : () => false);
}, [apiRef, props.dataSource]);
const [currentStrategy, setCurrentStrategy] = React.useState(apiRef.current.getActiveStrategy(_strategyProcessing.GridStrategyGroup.DataSource));
const standardRowsUpdateStrategyActive = React.useMemo(() => {
return currentStrategy === _utils2.DataSourceRowsUpdateStrategy.Default || currentStrategy === _utils2.DataSourceRowsUpdateStrategy.GroupedData;
}, [currentStrategy]);
const paginationModel = (0, _useGridSelector.useGridSelector)(apiRef, _gridPaginationSelector.gridPaginationModelSelector);
const lastRequestId = React.useRef(0);
// `false` while a request is in flight, and for any request that errors or is discarded as
// stale. Lets the mount effect tell "rows are already displayed" from "the fetch never landed".
const rowsAreUpToDate = React.useRef(false);
// Requests that are still running and will apply their response when they settle.
const pendingRequestCount = React.useRef(0);
const pollingIntervalRef = React.useRef(null);
const onDataSourceErrorProp = props.onDataSourceError;
const revalidateMs = props.dataSourceRevalidateMs;
const cacheChunkManager = (0, _useLazyRef.default)(() => {
if (!props.pagination) {
return new _utils2.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 _utils2.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;
}
const _ref = params || {},
{
skipCache,
keepChildrenExpanded,
showChildrenLoading
} = _ref,
getRowsParams = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
const fetchParams = (0, _extends2.default)({}, (0, _gridDataSourceSelector.gridGetRowsParamsSelector)(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), getRowsParams);
if (parentId && parentId !== _gridRowsUtils.GRID_ROOT_GROUP_ID && props.signature !== 'DataGrid') {
options.fetchRowChildren?.([parentId], [fetchParams], showChildrenLoading);
return;
}
options.clearDataSourceState?.();
const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams);
const responses = cacheKeys.map(cacheKey => cache.get(cacheKey));
if (!skipCache && responses.every(response => response !== undefined)) {
// Bump the request id so any cache-miss request still in flight is treated as
// stale and won't override the cached data we're about to apply.
lastRequestId.current += 1;
apiRef.current.applyStrategyProcessor('dataSourceRootRowsUpdate', {
response: _utils2.CacheChunkManager.mergeResponses(responses),
fetchParams,
options: {
skipCache,
keepChildrenExpanded
}
});
rowsAreUpToDate.current = true;
if (standardRowsUpdateStrategyActive) {
apiRef.current.setLoading(false);
}
return;
}
// Manage loading state only for the default strategy
if (standardRowsUpdateStrategyActive || apiRef.current.getRowsCount() === 0) {
apiRef.current.setLoading(true);
}
const requestId = lastRequestId.current + 1;
lastRequestId.current = requestId;
rowsAreUpToDate.current = false;
pendingRequestCount.current += 1;
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('dataSourceRootRowsUpdate', {
response: getRowsResponse,
fetchParams,
options: {
skipCache,
keepChildrenExpanded
}
});
rowsAreUpToDate.current = true;
}
} catch (originalError) {
if (lastRequestId.current === requestId) {
apiRef.current.applyStrategyProcessor('dataSourceRootRowsUpdate', {
error: originalError,
fetchParams,
options: {
skipCache,
keepChildrenExpanded
}
});
if (typeof onDataSourceErrorProp === 'function') {
onDataSourceErrorProp(new _gridDataSourceError.GridGetRowsError({
message: originalError?.message,
params: fetchParams,
cause: originalError
}));
} else if (process.env.NODE_ENV !== 'production') {
(0, _warning.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 {
pendingRequestCount.current -= 1;
if (standardRowsUpdateStrategyActive && lastRequestId.current === requestId) {
apiRef.current.setLoading(false);
}
}
}, [cacheChunkManager, cache, apiRef, standardRowsUpdateStrategyActive, props.dataSource?.getRows, onDataSourceErrorProp, options, props.signature]);
const handleStrategyActivityChange = React.useCallback(() => {
setCurrentStrategy(apiRef.current.getActiveStrategy(_strategyProcessing.GridStrategyGroup.DataSource));
}, [apiRef]);
const fetchRowChildrenOption = options.fetchRowChildren;
const revalidate = (0, _useEventCallback.default)(async () => {
const getRows = props.dataSource?.getRows;
if (!getRows || !standardRowsUpdateStrategyActive) {
return;
}
const revalidateExpandedGroups = () => {
if (currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.GroupedData || !fetchRowChildrenOption) {
return;
}
const rowTree = (0, _gridRowsSelector.gridRowTreeSelector)(apiRef);
const visibleRows = (0, _gridPaginationSelector.gridVisibleRowsSelector)(apiRef).rows;
const expandedGroupIds = visibleRows.reduce((acc, row) => {
const node = rowTree[row.id];
if (node.type === 'group' && node.id !== _gridRowsUtils.GRID_ROOT_GROUP_ID && node.childrenExpanded === true) {
acc.push(row.id);
}
return acc;
}, []);
if (expandedGroupIds.length > 0) {
fetchRowChildrenOption(expandedGroupIds, [], false);
}
};
const fetchParams = (0, _extends2.default)({}, (0, _gridDataSourceSelector.gridGetRowsParamsSelector)(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}));
const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams);
const responses = cacheKeys.map(cacheKey => cache.get(cacheKey));
if (responses.every(response => response !== undefined)) {
revalidateExpandedGroups();
return;
}
try {
const response = await getRows(fetchParams);
const currentParams = (0, _extends2.default)({}, (0, _gridDataSourceSelector.gridGetRowsParamsSelector)(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}));
if (!(0, _isDeepEqual.isDeepEqual)(fetchParams, currentParams)) {
return;
}
const cacheResponses = cacheChunkManager.splitResponse(fetchParams, response);
cacheResponses.forEach((cacheResponse, key) => cache.set(key, cacheResponse));
apiRef.current.applyStrategyProcessor('dataSourceRootRowsUpdate', {
response,
fetchParams,
options: {}
});
revalidateExpandedGroups();
} catch {
// Ignore background revalidation errors.
}
});
const stopPolling = React.useCallback(() => {
if (pollingIntervalRef.current !== null) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}, []);
const startPolling = (0, _useEventCallback.default)(() => {
stopPolling();
if (revalidateMs <= 0 || !standardRowsUpdateStrategyActive) {
return;
}
pollingIntervalRef.current = setInterval(revalidate, revalidateMs);
});
const handleDataUpdate = React.useCallback(params => {
if ('error' in params) {
// Reset the rows on error, even with `dataSourceKeepPreviousData`. The previous
// rows belong to the previous query while the pagination/sorting/filtering controls
// already reflect the failed request, so keeping them would present stale data as if
// it satisfied the new query. This matches TanStack Query, where `keepPreviousData`
// clears the placeholder once the query settles with an error.
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);
startPolling();
}, [apiRef, startPolling]);
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;
}
if (finalRowUpdate && !(0, _isDeepEqual.isDeepEqual)(finalRowUpdate, params.previousRow)) {
// Reset the outdated cache, only if the row is _actually_ updated
apiRef.current.dataSource.cache.clear();
}
apiRef.current.updateNestedRows([finalRowUpdate], []);
return finalRowUpdate;
} catch (errorThrown) {
if (typeof onDataSourceErrorProp === 'function') {
onDataSourceErrorProp(new _gridDataSourceError.GridUpdateRowError({
message: errorThrown?.message,
params,
cause: errorThrown
}));
} else if (process.env.NODE_ENV !== 'production') {
(0, _warning.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(() => (0, _debounce.default)(fetchRows, 0), [fetchRows]);
const handleFetchRowsOnParamsChange = React.useCallback(() => {
// `dataSourceKeepPreviousData` only applies to the flat `Default` strategy. For
// `GroupedData` (tree data / row grouping), skipping the synchronous `setRows([])`
// would leave the existing tree merged on top of the new response and render rows
// in stale sort order (https://github.com/mui/mui-x/pull/21619).
// This handler is only wired up when a standard strategy is active via the `runIf`
// guards on the returned `events` object.
const activeStrategy = apiRef.current.getActiveStrategy(_strategyProcessing.GridStrategyGroup.DataSource);
const keepPreviousData = props.dataSourceKeepPreviousData && activeStrategy === _utils2.DataSourceRowsUpdateStrategy.Default;
if (!keepPreviousData) {
// Clear the rows first and immediately mark the grid as loading so the overlay
// selector never observes the intermediate `rows=[] && loading=false` state that
// would otherwise pick `noRowsOverlay`. Order matters: `setRows([])` rebuilds
// `state.rows` from `props.loading`, so the `setLoading(true)` call must come after
// it to survive the rebuild.
apiRef.current.setRows([]);
}
apiRef.current.setLoading(true);
stopPolling();
debouncedFetchRows();
}, [apiRef, props.dataSourceKeepPreviousData, stopPolling, debouncedFetchRows]);
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 (!standardRowsUpdateStrategyActive) {
stopPolling();
}
}, [standardRowsUpdateStrategyActive, stopPolling]);
React.useEffect(() => {
if (revalidateMs <= 0) {
stopPolling();
}
}, [revalidateMs, stopPolling]);
React.useEffect(() => stopPolling, [stopPolling]);
const lastApiRef = React.useRef(apiRef);
const lastStrategy = React.useRef(currentStrategy);
const lastDataSource = React.useRef(props.dataSource);
React.useEffect(() => {
// `<Activity mode="hidden">` leaves the root element in the document, a real unmount does not.
// Only discard the in-flight response in the latter case, so hiding lets the request land.
const rootElement = apiRef.current?.rootElementRef?.current ?? null;
const ignoreInFlightRequest = () => {
if (!rootElement?.isConnected) {
lastRequestId.current += 1;
}
};
// Return early if the proper strategy isn't set yet
// Context: https://github.com/mui/mui-x/issues/19650
if (currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.Default && currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.LazyLoading && currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.GroupedData && currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.LazyLoadedGroupedData) {
return undefined;
}
const dependenciesChanged = lastApiRef.current !== apiRef || lastStrategy.current !== currentStrategy || lastDataSource.current !== props.dataSource;
lastApiRef.current = apiRef;
lastStrategy.current = currentStrategy;
lastDataSource.current = props.dataSource;
// Re-mounting the effect (`<Activity />` becoming visible again, for instance) must not
// re-fetch data that is already displayed.
if (!dependenciesChanged && (rowsAreUpToDate.current || pendingRequestCount.current > 0)) {
return ignoreInFlightRequest;
}
if (props.dataSource) {
stopPolling();
// `dataSourceKeepPreviousData` only applies to the flat `Default` strategy (mirroring
// `handleFetchRowsOnParamsChange`). Keep the previous rows visible when the `dataSource`
// reference changes so the feature isn't silently defeated for a non-memoized
// `dataSource`. Other strategies must still reset the rows to keep their order consistent
// with the new response (https://github.com/mui/mui-x/pull/21619).
if (!props.dataSourceKeepPreviousData || currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.Default) {
apiRef.current.setRows([]);
}
apiRef.current.dataSource.cache.clear();
apiRef.current.dataSource.fetchRows();
}
return ignoreInFlightRequest;
}, [apiRef, props.dataSource, props.dataSourceKeepPreviousData, currentStrategy, stopPolling]);
React.useEffect(() => {
// `dataSourceKeepPreviousData` is a no-op for tree data and row grouping: those
// strategies always reset the rows on refetch to keep their order consistent with the
// response. Warn so the limitation is discoverable at runtime, not only in the docs.
if (process.env.NODE_ENV !== 'production' && props.dataSourceKeepPreviousData && (currentStrategy === _utils2.DataSourceRowsUpdateStrategy.GroupedData || currentStrategy === _utils2.DataSourceRowsUpdateStrategy.LazyLoadedGroupedData)) {
(0, _warning.warnOnce)(['MUI X: The `dataSourceKeepPreviousData` prop only applies to flat data.', 'It is ignored when tree data or row grouping is enabled, because the rows are always reset on refetch to keep their order consistent with the response.', 'For more details, see https://mui.com/x/react-data-grid/server-side-data/#keep-previous-data-while-fetching.']);
}
}, [props.dataSourceKeepPreviousData, currentStrategy]);
return {
api: {
public: dataSourceApi
},
debouncedFetchRows,
strategyProcessor: {
strategyName: _utils2.DataSourceRowsUpdateStrategy.Default,
group: 'dataSourceRootRowsUpdate',
processor: handleDataUpdate
},
setStrategyAvailability,
startPolling,
stopPolling,
cacheChunkManager,
cache,
events: {
strategyAvailabilityChange: handleStrategyActivityChange,
sortModelChange: (0, _utils.runIf)(standardRowsUpdateStrategyActive, handleFetchRowsOnParamsChange),
filterModelChange: (0, _utils.runIf)(standardRowsUpdateStrategyActive, handleFetchRowsOnParamsChange),
paginationModelChange: (0, _utils.runIf)(standardRowsUpdateStrategyActive, handleFetchRowsOnParamsChange)
}
};
};
exports.useGridDataSourceBase = useGridDataSourceBase;
@@ -0,0 +1,392 @@
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["skipCache", "keepChildrenExpanded", "showChildrenLoading"];
import * as React from 'react';
import useLazyRef from '@mui/utils/useLazyRef';
import useEventCallback from '@mui/utils/useEventCallback';
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.mjs";
import { runIf } from "../../../utils/utils.mjs";
import { GridStrategyGroup } from "../../core/strategyProcessing/index.mjs";
import { useGridSelector } from "../../utils/useGridSelector.mjs";
import { gridPaginationModelSelector, gridVisibleRowsSelector } from "../pagination/gridPaginationSelector.mjs";
import { gridRowTreeSelector } from "../rows/gridRowsSelector.mjs";
import { gridGetRowsParamsSelector } from "./gridDataSourceSelector.mjs";
import { CacheChunkManager, DataSourceRowsUpdateStrategy } from "./utils.mjs";
import { GridDataSourceCacheDefault } from "./cache.mjs";
import { GridGetRowsError, GridUpdateRowError } from "./gridDataSourceError.mjs";
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 [currentStrategy, setCurrentStrategy] = React.useState(apiRef.current.getActiveStrategy(GridStrategyGroup.DataSource));
const standardRowsUpdateStrategyActive = React.useMemo(() => {
return currentStrategy === DataSourceRowsUpdateStrategy.Default || currentStrategy === DataSourceRowsUpdateStrategy.GroupedData;
}, [currentStrategy]);
const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector);
const lastRequestId = React.useRef(0);
// `false` while a request is in flight, and for any request that errors or is discarded as
// stale. Lets the mount effect tell "rows are already displayed" from "the fetch never landed".
const rowsAreUpToDate = React.useRef(false);
// Requests that are still running and will apply their response when they settle.
const pendingRequestCount = React.useRef(0);
const pollingIntervalRef = React.useRef(null);
const onDataSourceErrorProp = props.onDataSourceError;
const revalidateMs = props.dataSourceRevalidateMs;
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;
}
const _ref = params || {},
{
skipCache,
keepChildrenExpanded,
showChildrenLoading
} = _ref,
getRowsParams = _objectWithoutPropertiesLoose(_ref, _excluded);
const fetchParams = _extends({}, gridGetRowsParamsSelector(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), getRowsParams);
if (parentId && parentId !== GRID_ROOT_GROUP_ID && props.signature !== 'DataGrid') {
options.fetchRowChildren?.([parentId], [fetchParams], showChildrenLoading);
return;
}
options.clearDataSourceState?.();
const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams);
const responses = cacheKeys.map(cacheKey => cache.get(cacheKey));
if (!skipCache && responses.every(response => response !== undefined)) {
// Bump the request id so any cache-miss request still in flight is treated as
// stale and won't override the cached data we're about to apply.
lastRequestId.current += 1;
apiRef.current.applyStrategyProcessor('dataSourceRootRowsUpdate', {
response: CacheChunkManager.mergeResponses(responses),
fetchParams,
options: {
skipCache,
keepChildrenExpanded
}
});
rowsAreUpToDate.current = true;
if (standardRowsUpdateStrategyActive) {
apiRef.current.setLoading(false);
}
return;
}
// Manage loading state only for the default strategy
if (standardRowsUpdateStrategyActive || apiRef.current.getRowsCount() === 0) {
apiRef.current.setLoading(true);
}
const requestId = lastRequestId.current + 1;
lastRequestId.current = requestId;
rowsAreUpToDate.current = false;
pendingRequestCount.current += 1;
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('dataSourceRootRowsUpdate', {
response: getRowsResponse,
fetchParams,
options: {
skipCache,
keepChildrenExpanded
}
});
rowsAreUpToDate.current = true;
}
} catch (originalError) {
if (lastRequestId.current === requestId) {
apiRef.current.applyStrategyProcessor('dataSourceRootRowsUpdate', {
error: originalError,
fetchParams,
options: {
skipCache,
keepChildrenExpanded
}
});
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 {
pendingRequestCount.current -= 1;
if (standardRowsUpdateStrategyActive && lastRequestId.current === requestId) {
apiRef.current.setLoading(false);
}
}
}, [cacheChunkManager, cache, apiRef, standardRowsUpdateStrategyActive, props.dataSource?.getRows, onDataSourceErrorProp, options, props.signature]);
const handleStrategyActivityChange = React.useCallback(() => {
setCurrentStrategy(apiRef.current.getActiveStrategy(GridStrategyGroup.DataSource));
}, [apiRef]);
const fetchRowChildrenOption = options.fetchRowChildren;
const revalidate = useEventCallback(async () => {
const getRows = props.dataSource?.getRows;
if (!getRows || !standardRowsUpdateStrategyActive) {
return;
}
const revalidateExpandedGroups = () => {
if (currentStrategy !== DataSourceRowsUpdateStrategy.GroupedData || !fetchRowChildrenOption) {
return;
}
const rowTree = gridRowTreeSelector(apiRef);
const visibleRows = gridVisibleRowsSelector(apiRef).rows;
const expandedGroupIds = visibleRows.reduce((acc, row) => {
const node = rowTree[row.id];
if (node.type === 'group' && node.id !== GRID_ROOT_GROUP_ID && node.childrenExpanded === true) {
acc.push(row.id);
}
return acc;
}, []);
if (expandedGroupIds.length > 0) {
fetchRowChildrenOption(expandedGroupIds, [], false);
}
};
const fetchParams = _extends({}, gridGetRowsParamsSelector(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}));
const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams);
const responses = cacheKeys.map(cacheKey => cache.get(cacheKey));
if (responses.every(response => response !== undefined)) {
revalidateExpandedGroups();
return;
}
try {
const response = await getRows(fetchParams);
const currentParams = _extends({}, gridGetRowsParamsSelector(apiRef), apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}));
if (!isDeepEqual(fetchParams, currentParams)) {
return;
}
const cacheResponses = cacheChunkManager.splitResponse(fetchParams, response);
cacheResponses.forEach((cacheResponse, key) => cache.set(key, cacheResponse));
apiRef.current.applyStrategyProcessor('dataSourceRootRowsUpdate', {
response,
fetchParams,
options: {}
});
revalidateExpandedGroups();
} catch {
// Ignore background revalidation errors.
}
});
const stopPolling = React.useCallback(() => {
if (pollingIntervalRef.current !== null) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}, []);
const startPolling = useEventCallback(() => {
stopPolling();
if (revalidateMs <= 0 || !standardRowsUpdateStrategyActive) {
return;
}
pollingIntervalRef.current = setInterval(revalidate, revalidateMs);
});
const handleDataUpdate = React.useCallback(params => {
if ('error' in params) {
// Reset the rows on error, even with `dataSourceKeepPreviousData`. The previous
// rows belong to the previous query while the pagination/sorting/filtering controls
// already reflect the failed request, so keeping them would present stale data as if
// it satisfied the new query. This matches TanStack Query, where `keepPreviousData`
// clears the placeholder once the query settles with an error.
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);
startPolling();
}, [apiRef, startPolling]);
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;
}
if (finalRowUpdate && !isDeepEqual(finalRowUpdate, params.previousRow)) {
// Reset the outdated cache, only if the row is _actually_ updated
apiRef.current.dataSource.cache.clear();
}
apiRef.current.updateNestedRows([finalRowUpdate], []);
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 handleFetchRowsOnParamsChange = React.useCallback(() => {
// `dataSourceKeepPreviousData` only applies to the flat `Default` strategy. For
// `GroupedData` (tree data / row grouping), skipping the synchronous `setRows([])`
// would leave the existing tree merged on top of the new response and render rows
// in stale sort order (https://github.com/mui/mui-x/pull/21619).
// This handler is only wired up when a standard strategy is active via the `runIf`
// guards on the returned `events` object.
const activeStrategy = apiRef.current.getActiveStrategy(GridStrategyGroup.DataSource);
const keepPreviousData = props.dataSourceKeepPreviousData && activeStrategy === DataSourceRowsUpdateStrategy.Default;
if (!keepPreviousData) {
// Clear the rows first and immediately mark the grid as loading so the overlay
// selector never observes the intermediate `rows=[] && loading=false` state that
// would otherwise pick `noRowsOverlay`. Order matters: `setRows([])` rebuilds
// `state.rows` from `props.loading`, so the `setLoading(true)` call must come after
// it to survive the rebuild.
apiRef.current.setRows([]);
}
apiRef.current.setLoading(true);
stopPolling();
debouncedFetchRows();
}, [apiRef, props.dataSourceKeepPreviousData, stopPolling, debouncedFetchRows]);
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 (!standardRowsUpdateStrategyActive) {
stopPolling();
}
}, [standardRowsUpdateStrategyActive, stopPolling]);
React.useEffect(() => {
if (revalidateMs <= 0) {
stopPolling();
}
}, [revalidateMs, stopPolling]);
React.useEffect(() => stopPolling, [stopPolling]);
const lastApiRef = React.useRef(apiRef);
const lastStrategy = React.useRef(currentStrategy);
const lastDataSource = React.useRef(props.dataSource);
React.useEffect(() => {
// `<Activity mode="hidden">` leaves the root element in the document, a real unmount does not.
// Only discard the in-flight response in the latter case, so hiding lets the request land.
const rootElement = apiRef.current?.rootElementRef?.current ?? null;
const ignoreInFlightRequest = () => {
if (!rootElement?.isConnected) {
lastRequestId.current += 1;
}
};
// Return early if the proper strategy isn't set yet
// Context: https://github.com/mui/mui-x/issues/19650
if (currentStrategy !== DataSourceRowsUpdateStrategy.Default && currentStrategy !== DataSourceRowsUpdateStrategy.LazyLoading && currentStrategy !== DataSourceRowsUpdateStrategy.GroupedData && currentStrategy !== DataSourceRowsUpdateStrategy.LazyLoadedGroupedData) {
return undefined;
}
const dependenciesChanged = lastApiRef.current !== apiRef || lastStrategy.current !== currentStrategy || lastDataSource.current !== props.dataSource;
lastApiRef.current = apiRef;
lastStrategy.current = currentStrategy;
lastDataSource.current = props.dataSource;
// Re-mounting the effect (`<Activity />` becoming visible again, for instance) must not
// re-fetch data that is already displayed.
if (!dependenciesChanged && (rowsAreUpToDate.current || pendingRequestCount.current > 0)) {
return ignoreInFlightRequest;
}
if (props.dataSource) {
stopPolling();
// `dataSourceKeepPreviousData` only applies to the flat `Default` strategy (mirroring
// `handleFetchRowsOnParamsChange`). Keep the previous rows visible when the `dataSource`
// reference changes so the feature isn't silently defeated for a non-memoized
// `dataSource`. Other strategies must still reset the rows to keep their order consistent
// with the new response (https://github.com/mui/mui-x/pull/21619).
if (!props.dataSourceKeepPreviousData || currentStrategy !== DataSourceRowsUpdateStrategy.Default) {
apiRef.current.setRows([]);
}
apiRef.current.dataSource.cache.clear();
apiRef.current.dataSource.fetchRows();
}
return ignoreInFlightRequest;
}, [apiRef, props.dataSource, props.dataSourceKeepPreviousData, currentStrategy, stopPolling]);
React.useEffect(() => {
// `dataSourceKeepPreviousData` is a no-op for tree data and row grouping: those
// strategies always reset the rows on refetch to keep their order consistent with the
// response. Warn so the limitation is discoverable at runtime, not only in the docs.
if (process.env.NODE_ENV !== 'production' && props.dataSourceKeepPreviousData && (currentStrategy === DataSourceRowsUpdateStrategy.GroupedData || currentStrategy === DataSourceRowsUpdateStrategy.LazyLoadedGroupedData)) {
warnOnce(['MUI X: The `dataSourceKeepPreviousData` prop only applies to flat data.', 'It is ignored when tree data or row grouping is enabled, because the rows are always reset on refetch to keep their order consistent with the response.', 'For more details, see https://mui.com/x/react-data-grid/server-side-data/#keep-previous-data-while-fetching.']);
}
}, [props.dataSourceKeepPreviousData, currentStrategy]);
return {
api: {
public: dataSourceApi
},
debouncedFetchRows,
strategyProcessor: {
strategyName: DataSourceRowsUpdateStrategy.Default,
group: 'dataSourceRootRowsUpdate',
processor: handleDataUpdate
},
setStrategyAvailability,
startPolling,
stopPolling,
cacheChunkManager,
cache,
events: {
strategyAvailabilityChange: handleStrategyActivityChange,
sortModelChange: runIf(standardRowsUpdateStrategyActive, handleFetchRowsOnParamsChange),
filterModelChange: runIf(standardRowsUpdateStrategyActive, handleFetchRowsOnParamsChange),
paginationModelChange: runIf(standardRowsUpdateStrategyActive, handleFetchRowsOnParamsChange)
}
};
};
@@ -0,0 +1,25 @@
import type { GridGetRowsParams, GridGetRowsResponse } from "../../../models/gridDataSource.mjs";
export declare enum DataSourceRowsUpdateStrategy {
Default = "set-flat-rows",
LazyLoading = "replace-row-range",
GroupedData = "set-grouped-rows",
LazyLoadedGroupedData = "replace-grouped-row-range",
}
/**
* Provides better cache hit rate by:
* 1. Splitting the data into smaller chunks to be stored in the cache (cache `set`)
* 2. Merging multiple cache entries into a single response to get the required chunk (cache `get`)
*/
export declare class CacheChunkManager {
private chunkSize;
/**
* @param chunkSize The number of rows to store in each cache entry.
* If not set, the whole array will be stored in a single cache entry.
* Setting this value to smallest page size will result in better cache hit rate.
* Has no effect if cursor pagination is used.
*/
constructor(chunkSize: number);
getCacheKeys: (key: GridGetRowsParams) => GridGetRowsParams[];
splitResponse: (key: GridGetRowsParams, response: GridGetRowsResponse) => Map<GridGetRowsParams, GridGetRowsResponse>;
static mergeResponses: (responses: GridGetRowsResponse[]) => GridGetRowsResponse;
}
@@ -0,0 +1,25 @@
import type { GridGetRowsParams, GridGetRowsResponse } from "../../../models/gridDataSource.js";
export declare enum DataSourceRowsUpdateStrategy {
Default = "set-flat-rows",
LazyLoading = "replace-row-range",
GroupedData = "set-grouped-rows",
LazyLoadedGroupedData = "replace-grouped-row-range",
}
/**
* Provides better cache hit rate by:
* 1. Splitting the data into smaller chunks to be stored in the cache (cache `set`)
* 2. Merging multiple cache entries into a single response to get the required chunk (cache `get`)
*/
export declare class CacheChunkManager {
private chunkSize;
/**
* @param chunkSize The number of rows to store in each cache entry.
* If not set, the whole array will be stored in a single cache entry.
* Setting this value to smallest page size will result in better cache hit rate.
* Has no effect if cursor pagination is used.
*/
constructor(chunkSize: number);
getCacheKeys: (key: GridGetRowsParams) => GridGetRowsParams[];
splitResponse: (key: GridGetRowsParams, response: GridGetRowsResponse) => Map<GridGetRowsParams, GridGetRowsResponse>;
static mergeResponses: (responses: GridGetRowsResponse[]) => GridGetRowsResponse;
}
@@ -0,0 +1,80 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DataSourceRowsUpdateStrategy = exports.CacheChunkManager = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
let DataSourceRowsUpdateStrategy = exports.DataSourceRowsUpdateStrategy = /*#__PURE__*/function (DataSourceRowsUpdateStrategy) {
DataSourceRowsUpdateStrategy["Default"] = "set-flat-rows";
DataSourceRowsUpdateStrategy["LazyLoading"] = "replace-row-range";
DataSourceRowsUpdateStrategy["GroupedData"] = "set-grouped-rows";
DataSourceRowsUpdateStrategy["LazyLoadedGroupedData"] = "replace-grouped-row-range";
return DataSourceRowsUpdateStrategy;
}({});
/**
* Provides better cache hit rate by:
* 1. Splitting the data into smaller chunks to be stored in the cache (cache `set`)
* 2. Merging multiple cache entries into a single response to get the required chunk (cache `get`)
*/
class CacheChunkManager {
/**
* @param chunkSize The number of rows to store in each cache entry.
* If not set, the whole array will be stored in a single cache entry.
* Setting this value to smallest page size will result in better cache hit rate.
* Has no effect if cursor pagination is used.
*/
constructor(chunkSize) {
this.chunkSize = chunkSize;
}
getCacheKeys = key => {
if (this.chunkSize < 1 || typeof key.start !== 'number') {
return [key];
}
// split the range into chunks
const chunkedKeys = [];
for (let i = key.start; i <= key.end; i += this.chunkSize) {
const end = Math.min(i + this.chunkSize - 1, key.end);
chunkedKeys.push((0, _extends2.default)({}, key, {
start: i,
end
}));
}
return chunkedKeys;
};
splitResponse = (key, response) => {
const cacheKeys = this.getCacheKeys(key);
if (cacheKeys.length === 1) {
return new Map([[key, response]]);
}
const responses = new Map();
cacheKeys.forEach(chunkKey => {
const isLastChunk = chunkKey.end === key.end;
const responseSlice = (0, _extends2.default)({}, response, {
pageInfo: (0, _extends2.default)({}, response.pageInfo, {
// If the original response had page info, update that information for all but last chunk and keep the original value for the last chunk
hasNextPage: response.pageInfo?.hasNextPage !== undefined && !isLastChunk ? true : response.pageInfo?.hasNextPage,
nextCursor: response.pageInfo?.nextCursor !== undefined && !isLastChunk ? response.rows[chunkKey.end + 1].id : response.pageInfo?.nextCursor
}),
rows: typeof chunkKey.start !== 'number' || typeof key.start !== 'number' ? response.rows : response.rows.slice(chunkKey.start - key.start, chunkKey.end - key.start + 1)
});
responses.set(chunkKey, responseSlice);
});
return responses;
};
static mergeResponses = responses => {
if (responses.length === 1) {
return responses[0];
}
return responses.reduce((acc, response) => (0, _extends2.default)({}, response, {
rows: [...acc.rows, ...response.rows]
}), {
rows: [],
rowCount: 0,
pageInfo: {}
});
};
}
exports.CacheChunkManager = CacheChunkManager;
@@ -0,0 +1,73 @@
import _extends from "@babel/runtime/helpers/esm/extends";
export let DataSourceRowsUpdateStrategy = /*#__PURE__*/function (DataSourceRowsUpdateStrategy) {
DataSourceRowsUpdateStrategy["Default"] = "set-flat-rows";
DataSourceRowsUpdateStrategy["LazyLoading"] = "replace-row-range";
DataSourceRowsUpdateStrategy["GroupedData"] = "set-grouped-rows";
DataSourceRowsUpdateStrategy["LazyLoadedGroupedData"] = "replace-grouped-row-range";
return DataSourceRowsUpdateStrategy;
}({});
/**
* Provides better cache hit rate by:
* 1. Splitting the data into smaller chunks to be stored in the cache (cache `set`)
* 2. Merging multiple cache entries into a single response to get the required chunk (cache `get`)
*/
export class CacheChunkManager {
/**
* @param chunkSize The number of rows to store in each cache entry.
* If not set, the whole array will be stored in a single cache entry.
* Setting this value to smallest page size will result in better cache hit rate.
* Has no effect if cursor pagination is used.
*/
constructor(chunkSize) {
this.chunkSize = chunkSize;
}
getCacheKeys = key => {
if (this.chunkSize < 1 || typeof key.start !== 'number') {
return [key];
}
// split the range into chunks
const chunkedKeys = [];
for (let i = key.start; i <= key.end; i += this.chunkSize) {
const end = Math.min(i + this.chunkSize - 1, key.end);
chunkedKeys.push(_extends({}, key, {
start: i,
end
}));
}
return chunkedKeys;
};
splitResponse = (key, response) => {
const cacheKeys = this.getCacheKeys(key);
if (cacheKeys.length === 1) {
return new Map([[key, response]]);
}
const responses = new Map();
cacheKeys.forEach(chunkKey => {
const isLastChunk = chunkKey.end === key.end;
const responseSlice = _extends({}, response, {
pageInfo: _extends({}, response.pageInfo, {
// If the original response had page info, update that information for all but last chunk and keep the original value for the last chunk
hasNextPage: response.pageInfo?.hasNextPage !== undefined && !isLastChunk ? true : response.pageInfo?.hasNextPage,
nextCursor: response.pageInfo?.nextCursor !== undefined && !isLastChunk ? response.rows[chunkKey.end + 1].id : response.pageInfo?.nextCursor
}),
rows: typeof chunkKey.start !== 'number' || typeof key.start !== 'number' ? response.rows : response.rows.slice(chunkKey.start - key.start, chunkKey.end - key.start + 1)
});
responses.set(chunkKey, responseSlice);
});
return responses;
};
static mergeResponses = responses => {
if (responses.length === 1) {
return responses[0];
}
return responses.reduce((acc, response) => _extends({}, response, {
rows: [...acc.rows, ...response.rows]
}), {
rows: [],
rowCount: 0,
pageInfo: {}
});
};
}