Skip to content

Latest commit

 

History

History
369 lines (265 loc) · 10.3 KB

processing.md

File metadata and controls

369 lines (265 loc) · 10.3 KB

Processing

Introduction

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.

Summary

  • Pipe-processing
    • Plugin state enrichment
    • Add custom behavior to an API method
    • Feature limitation
    • Component children processing
  • Family-processing

Pipe-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:

Plugin state enrichment

Goal: Allow plugins to enrich another plugin state before saving it.

Processing list

'hydrateColumns'

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);
'rowHeight'

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);
'hydrateRows'

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);

Add custom behavior to an API method

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)

List

'exportState'

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);
'restoreState'

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);
'scrollToIndexes'

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);

Feature limitation

Goal: To block the application of another plugin (for example canBeReorder)

List

'canBeReordered' (Pro only)

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);

Component children processing

Goal: Allow plugins to enrich the children of a component.

List

'columnMenu'

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);
'exportMenu'

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);
'preferencePanel'

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. :::

Strategy-processing

A strategy processing is a pattern allowing plugins or component to register processors that will be applied only when the correct strategy is active.

Example

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