Each feature is contained in a single hook, but features are not independent.
For example, the detail panel has an impact on the row height.
To allows hooks to interact and produce a coherent state, various patterns are presented on this page.
For each pattern, you will find a list of where such pattern is used, why it is necessary, and an overview of its behavior.
- Pipe-processing
- Plugin state enrichment
- Add custom behavior to an API method
- Feature limitation
- Component children processing
- Family-processing
A pipe processing is a pattern allowing plugins or components to enrich data used by another plugin.
We can classify the pipe-processing into several categories:
Goal: Allow plugins to enrich another plugin state before saving it.
Publisher: useGridColumns
plugin before updating state.columns
.
Why register to this processing
A few possible reasons could be to:
- Add some columns (for example processor of the Selection plugin)
- Re-order the columns (for example processor of the Column Pinning plugin).
Example:
const addCustomFeatureColumn = React.useCallback<GridPipeProcessor<'hydrateColumns'>>(
(columnsState) => {
const customFeatureColumn = getCustomFeatureColumn();
const shouldHaveCustomFeatureColumn = !props.disableCustomFeature;
const haveCustomFeatureColumn = columnsState.lookup[customFeatureColumn.field] != null;
if (shouldHaveCustomFeatureColumn && !haveCustomFeatureColumn) {
columnsState.lookup[customFeatureColumn.field] = customFeatureColumn;
columnsState.orderedFields = [customFeatureColumn.field, ...columnsState.orderedFields];
}
// ⚠ The `columnsState` passed to the processors can contain the columns returned by the previous processing.
// If the plugin is not enabled during the current processing, it must check if its columns are present, and if so remove them.
else if (!shouldHaveCustomFeatureColumn && haveCustomFeatureColumn) {
delete columnsState.lookup[customFeatureColumn.field];
columnsState.orderedFields = columnsState.orderedFields.filter(
(field) => field !== customFeatureColumn.field,
);
}
return columnsState;
},
[apiRef, classes, getCustomFeatureColumn],
);
useGridRegisterPipeProcessor(apiRef, 'hydrateColumns', addCustomFeatureColumn);
Publisher: useGridRowsMeta
plugin before updating state.rowsMeta
(it is called for each row).
Why register to this processing
- Modify the base height of a row or add the height of some custom elements (for example processor of the Detail Panel plugin increases the row height when the detail panel is open).
Example:
const addCustomFeatureHeight = React.useCallback<GridPipeProcessor<'rowHeight'>>(
(initialValue, row) => {
if (props.disableCustomFeature) {
return {
...initialValue,
customFeature: 0,
};
}
return {
...initialValue,
customFeature: customFeatureHeightLookup[row.id],
};
},
[apiRef, customFeatureHeightLookup],
);
useGridRegisterPipeProcessor(apiRef, 'rowHeight', addCustomFeatureHeight);
Publisher: useGridRows
plugin before updating state.rows
.
Why register to this processing: Add some rows (for example processor of the Aggregation plugin).
Example:
const addGroupFooterRows = React.useCallback<GridPipeProcessor<'hydrateRows'>>((groupingParams) => {
const ids = [...groupingParams.ids];
const idRowsLookup = { ...groupingParams.idRowsLookup };
const tree = { ...groupingParams.tree };
const footerId = 'auto-generated-group-footer-root';
ids.push(footerId);
idRowsLookup[footerId] = {};
tree[footerId] = {
id: footerId,
isAutoGenerated: true,
parent: null,
depth: 0,
groupingKey: null,
groupingField: null,
position: 'footer',
};
return {
...groupingParams,
ids,
idRowsLookup,
tree,
};
}, []);
useGridRegisterPipeProcessor(apiRef, 'hydrateRows', addGroupFooterRows);
Goal: To add some data on the value returned by an API method (for example exportState
) or to apply some custom behavior based on the input value of an API method (for example restoreState
)
Publisher: useGridStatePersistence
plugin when calling apiRef.current.exportState
.
Why register to this processing: Add a portable state to the returned value of apiRef.current.exportState
.
Example:
const stateExportPreProcessing = React.useCallback<GridPipeProcessor<'exportState'>>(
(prevState) => {
const customFeatureModel = gridCustomFeatureModel(apiRef);
// Avoids adding a value equals to the default value
if (customFeatureModel.length === 0) {
return prevState;
}
return {
...prevState,
customFeature: {
model: customFeatureModel,
},
};
},
[apiRef],
);
useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing);
Publisher: useGridStatePersistence
plugin when calling apiRef.current.restoreState
.
Why register to this processing: Update the state based on the value passed to apiRef.current.restoreState
.
Example:
const stateRestorePreProcessing = React.useCallback<GridPipeProcessor<'restoreState'>>(
(params, context) => {
const customFeatureModel = context.stateToRestore.customFeature?.model;
if (customFeatureModel == null) {
return params;
}
// This part should not cause any re-render (no call to `apiRef.current.forceUpdate`)
// Be carefull when calling methods like `apiRef.current.setCustomFeature` which often automatically triggers a re-render.
apiRef.current.setState(mergeStateWithCustomFeatureModel(customFeatureModel));
return {
...params,
// Add a callback that will be run after all the processors are applied
callbacks: [...params.callbacks, apiRef.current.applyCustomFeatureDerivedStates],
};
},
[apiRef],
);
useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
Publisher: UseGridScroll
when calling apiRef.current.scrollToIndexes
.
Why register to this processing: Modify the target scroll coordinates.
Examples:
const calculateScrollLeft = React.useCallback<GridPipeProcessor<'scrollToIndexes'>>(
(initialValue, params) => {
if (props.disableCustomFeature) {
return initialValue;
}
return {
...initialValue,
left: getCustomFeatureCompatibleScrollLeft(initialValue),
};
},
[apiRef, props.disableCustomFeature],
);
useGridRegisterPipeProcessor(apiRef, 'scrollToIndexes', calculateScrollLeft);
Goal: To block the application of another plugin (for example canBeReorder
)
Publisher: useGridColumnReorder
when dragging a column over another.
Why register to this processing:
Example:
const checkIfCanBeReordered = React.useCallback<GridPipeProcessor<'canBeReordered'>>(
(initialValue, context) => {
if (context.targetIndex === 0) {
return false;
}
return initialValue;
},
[apiRef, pinnedColumns],
);
useGridRegisterPipeProcessor(apiRef, 'canBeReordered', checkIfCanBeReordered);
Goal: Allow plugins to enrich the children of a component.
Publisher: GridColumnMenu
component on render.
Why register to this processing: Add one or multiple menu items to GridColumnMenu
.
Example:
const addColumnMenuItems = React.useCallback<GridPipeProcessor<'columnMenu'>>(
(initialValue, column) => {
if (props.disableCustomFeature) {
return initialValue;
}
if (column.hasCustomFeature === false) {
return initialValue;
}
return [...initialValue, <Divider />, <GridCustoMFeatureMenuItems />];
},
[props.disableCustomFeature],
);
useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuItems);
Publisher: GridToolbarExport
component on render.
Why register to this processing: Add menu options according to the props and the plan (excel can only be added with premium plan).
// Example from useGridExcelExport
const addExportMenuButtons = React.useCallback<GridPipeProcessor<'exportMenu'>>(
(
initialValue, // the array of components already added
options: { excelOptions }, // the options received by GridToolbarExport
) => {
if (options.excelOptions?.disableToolbarButton) {
return initialValue;
}
return [
...initialValue,
{
component: <GridExcelExportMenuItem options={options.excelOptions} />,
componentName: 'excelExport', // the name of the export is used to sort options
},
];
},
[],
);
useGridRegisterPipeProcessor(apiRef, 'exportMenu', addExportMenuButtons);
Publisher: GridPreferencePanel
component on render.
Why register to this processing: Modify the rendered panel in GridPreferencePanel
based on the current value.
Example:
const preferencePanelPreProcessing = React.useCallback<GridPipeProcessor<'preferencePanel'>>(
(initialValue, value) => {
if (value === GridPreferencePanelsValue.customFeature) {
const CustomFeaturePanel = props.components.CustomFeaturePanel;
return <CustomFeaturePanel {...props.componentsProps?.customFeaturePanel} />;
}
return initialValue;
},
[props.components.CustomFeaturePanel, props.componentsProps?.customFeaturePanel],
);
useGridRegisterPipeProcessor(apiRef, 'preferencePanel', preferencePanelPreProcessing);
:::warning This behavior should probably be improved to be a strategy for processing to avoid having each processor check the value. :::
A strategy processing is a pattern allowing plugins or component to register processors that will be applied only when the correct strategy is active.
If we are using the Tree Data, we want the Tree Data plugin to be responsible for the following behaviors:
- Create the row tree
- Sort the rows
- Decide if a row matches the filters or not and if it should be expanded or not