Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[data grid] Did MUI X v7 break tree data children selection? #13391

Closed
genepaul opened this issue Jun 5, 2024 · 7 comments
Closed

[data grid] Did MUI X v7 break tree data children selection? #13391

genepaul opened this issue Jun 5, 2024 · 7 comments
Labels
component: data grid This is the name of the generic UI component, not the React module! feature: Selection Related to the data grid Selection feature feature: Tree data Related to the data grid Tree data feature support: commercial Support request from paid users support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/

Comments

@genepaul
Copy link
Contributor

genepaul commented Jun 5, 2024

The problem in depth

I posted this as a comment on #4248 but haven't gotten much response, and I'd like some help troubleshooting this. I'm using latest of the DataGrid Premium, and it seems the code posted in #4248 no longer works correctly. It seems some improvements were made where only affected cells are re-rendering when a checkbox is clicked, which does not allow a parent row to catch that a child was selected and it should render as indeterminate. Here's an example:

https://react-kckdew.stackblitz.io/

If you select a child row, notice the parent row doesn't render indeterminate until you collapse or expand that row (thus causing it to rerender).

Your environment

`npx @mui/envinfo`
   System:
    OS: macOS 14.4.1
  Binaries:
    Node: 20.12.1 - ~/.nvm/versions/node/v20.12.1/bin/node
    npm: 10.8.0 - ~/Developer/component-library/node_modules/.bin/npm
    pnpm: 9.1.1 - /usr/local/bin/pnpm
  Browsers:
    Chrome: 125.0.6422.142
    Edge: 125.0.2535.85
    Safari: 17.4.1
  npmPackages:
    @emotion/react: ^11.7.1 => 11.11.4 
    @emotion/styled: ^11.6.0 => 11.11.5 
    @mui/base:  5.0.0-beta.40 
    @mui/core-downloads-tracker:  5.15.18 
    @mui/icons-material: ^5.11.16 => 5.15.18 
    @mui/lab: 5.0.0-alpha.170 => 5.0.0-alpha.170 
    @mui/material: ^5.15.16 => 5.15.18 
    @mui/private-theming:  5.15.14 
    @mui/styled-engine:  5.15.14 
    @mui/system:  5.15.15 
    @mui/types:  7.2.14 
    @mui/utils:  5.15.14 
    @mui/x-data-grid:  7.5.1 
    @mui/x-data-grid-premium: ^7.4.0 => 7.5.1 
    @mui/x-data-grid-pro:  7.5.1 
    @mui/x-date-pickers: ^7.0.0 => 7.5.1 
    @mui/x-date-pickers-pro: ^7.0.0 => 7.5.1 
    @mui/x-license: ^7.0.0 => 7.2.1 
    @types/react: ^18.2.79 => 18.3.3 
    react: ^18.2.0 => 18.3.1 
    react-dom: ^18.2.0 => 18.3.1 
    typescript: ^5.2.2 => 5.4.5 

Search keywords: treeData children selection indeterminate
Order ID: 0332927583

@genepaul genepaul added status: waiting for maintainer These issues haven't been looked at yet by a maintainer support: commercial Support request from paid users labels Jun 5, 2024
@michelengelen michelengelen changed the title [question] Did MUI X v7 break tree data children selection? [data grid] Did MUI X v7 break tree data children selection? Jun 6, 2024
@michelengelen michelengelen added component: data grid This is the name of the generic UI component, not the React module! feature: Selection Related to the data grid Selection feature feature: Tree data Related to the data grid Tree data feature support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/ labels Jun 6, 2024
@michelengelen
Copy link
Member

michelengelen commented Jun 6, 2024

Hey @genepaul ... it does indeed not rerender on a selection model change anymore.
But you can restore that functionality with using the useGridSelector together with gridRowSelectionStateSelector.

Here is the adjusted piece of code:

const checkboxColumn = (apiRef: MutableRefObject<GridApiPremium>): GridColDef => {
  return {
    ...GRID_CHECKBOX_SELECTION_COL_DEF,
    renderHeader: (params) => {
      const children = gridFilteredSortedRowIdsSelector(
        apiRef.current.state,
        apiRef.current.instanceId,
      ).filter((id) => !id.toString().includes('auto-generated'));

      const selectionLookup = selectedIdsLookupSelector(
        apiRef.current.state,
        apiRef.current.instanceId,
      );

      const indeterminate =
        children?.some((child) => selectionLookup[child] === undefined) &&
        children?.some((child) => selectionLookup[child] !== undefined);

      const checked =
        Object.keys(selectionLookup).length > 0 &&
        children?.every((child) => selectionLookup[child] !== undefined);
      const data: GridColumnHeaderParams & {
        indeterminate?: boolean;
        checked?: boolean;
        disabled?: boolean;
        onClick?: (e: MouseEvent) => void;
      } = {
        ...params,
        onClick: (e) => {
          apiRef.current.selectRows(children, indeterminate || !checked);
          e.preventDefault();
        },
        indeterminate,
        checked,
      };
      return (
        <>
          <GridHeaderCheckbox {...data} />
        </>
      );
    },
    renderCell: (params) => {
      const { rowNode } = params;

      if (rowNode.type !== 'group') return <GridCellCheckboxRenderer {...params} />;

      const selectionLookup = useGridSelector(apiRef, gridRowSelectionStateSelector);

      const children = apiRef.current.getRowGroupChildren({
        groupId: rowNode.id,
        applyFiltering: true,
        applySorting: true,
      });

      const indeterminate =
        children?.some((child) => selectionLookup.includes(child)) &&
        children?.some((child) => !selectionLookup.includes(child));

      const checked = children?.every((child) => selectionLookup.includes(child));

      const extraData: GridRenderCellParams &
        GridRenderCellParamsPremium & {
          indeterminate?: boolean;
          checked?: boolean;
          disabled?: boolean;
          onClick?: (e: MouseEvent) => void;
        } = {
        ...params,
        disabled: false,
        onClick: (e) => {
          if (rowNode.type === 'group') {
            if (children) {
              apiRef.current.selectRows(children, indeterminate || !checked);
            }
            e.preventDefault();
          }
        },
        indeterminate,
        checked,
      };

      return <GridCellCheckboxRenderer {...extraData} />;
    },
  };
};

Hope this helps!

@michelengelen michelengelen added status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Jun 6, 2024
@genepaul
Copy link
Contributor Author

This really helped! Thank you!

Copy link

⚠️ This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue.
Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

@genepaul: How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.

@github-actions github-actions bot removed the status: waiting for author Issue with insufficient information label Jun 12, 2024
@genepaul
Copy link
Contributor Author

Side note: the header checkbox in the example code above has the same issue as the cell checkbox did - need to use useGridSelector in that one as well, for anyone stumbling on this issue in the future.

@jonathanasquier
Copy link

Hey, I've been trying many tree data children selection solutions since this morning, and this one seems the most promising.
The issue I have is that selectedIdsLookupSelector and therefore apiRef.current.state is always lagging by one.
1st check => selectionLookup is still empty, parent checkbox doesn't move.
2nt check => selectionLookup has now 1 item (the 1st check), parent checkbox display [-]
and so on...
I've been trying to use params.api and api.forceUpdate, with no success.

I'm using

const selectionLookup = selectedIdsLookupSelector(apiRef.current.state, apiRef.current.instanceId);

as I'm being prevented from using a hook in the renderCell function.

I'm desperately looking for a working codesandbox !

Thanks !

@genepaul
Copy link
Contributor Author

genepaul commented Aug 21, 2024

@jonathanasquier - what is preventing you from using a hook? My understanding is that the hooks are the way to go, as they are kept up to date. I've had similar issues with trying to use apiRef.current.state as well, and the hooks have been the answer in those scenarios.

@jonathanasquier
Copy link

React Hook "useGridSelector" is called conditionally. React Hooks must be called in the exact same order in every component render.

Solved it by moving the hook above the if(..) return

I reimplemented the grid in isolation, and then dropped it in my project, worked like a charm 🤷‍♂️

Updated version:

import {
  GRID_CHECKBOX_SELECTION_COL_DEF,
  GridApiPro,
  GridCellCheckboxRenderer,
  GridColDef,
  GridColumnHeaderParams,
  gridFilteredSortedRowIdsSelector,
  GridHeaderCheckbox,
  GridRenderCellParams,
  gridRowSelectionStateSelector,
  useGridSelector,
} from "@mui/x-data-grid-pro";
import { MutableRefObject } from "react";

const checkboxColumn = (apiRef: MutableRefObject<GridApiPro>): GridColDef => {
  return {
    ...GRID_CHECKBOX_SELECTION_COL_DEF,
    renderHeader: function RenderHeader(params) {
      const children = gridFilteredSortedRowIdsSelector(
        apiRef.current.state,
        apiRef.current.instanceId
      ).filter((id) => !id.toString().includes("auto-generated"));

      const selectionLookup = useGridSelector(
        apiRef,
        gridRowSelectionStateSelector
      );

      const indeterminate =
        children?.some((child) => selectionLookup.includes(child)) &&
        children?.some((child) => !selectionLookup.includes(child));

      const checked = children?.every((child) =>
        selectionLookup.includes(child)
      );

      const data: GridColumnHeaderParams & {
        indeterminate?: boolean;
        checked?: boolean;
        disabled?: boolean;
        onClick?: (e: MouseEvent) => void;
      } = {
        ...params,
        onClick: (e) => {
          apiRef.current.selectRows([...children], indeterminate || !checked);
          e.preventDefault();
        },
        indeterminate,
        checked,
      };
      return (
        <>
          <GridHeaderCheckbox {...data} />
        </>
      );
    },
    renderCell: function RenderCell(params) {
      const { rowNode } = params;

      const selectionLookup = useGridSelector(
        apiRef,
        gridRowSelectionStateSelector
      );

      if (rowNode.type !== "group") {
        return <GridCellCheckboxRenderer {...params} />;
      }

      const children = apiRef.current.getRowGroupChildren({
        groupId: rowNode.id,
        applyFiltering: true,
        applySorting: true,
      });

      const indeterminate =
        children?.some((child) => selectionLookup.includes(child)) &&
        children?.some((child) => !selectionLookup.includes(child));

      const checked = children?.every((child) =>
        selectionLookup.includes(child)
      );

      const extraData: GridRenderCellParams & {
        indeterminate?: boolean;
        checked?: boolean;
        disabled?: boolean;
        onClick?: (e: MouseEvent) => void;
      } = {
        ...params,
        disabled: false,
        onClick: (e) => {
          if (rowNode.type === "group") {
            if (children) {
              apiRef.current.selectRows(
                [rowNode.id, ...children],
                indeterminate || !checked
              );
            }
            e.preventDefault();
          }
        },
        indeterminate,
        checked,
      };

      return <GridCellCheckboxRenderer {...extraData} />;
    },
  };
};

export default checkboxColumn;

Thank you !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: data grid This is the name of the generic UI component, not the React module! feature: Selection Related to the data grid Selection feature feature: Tree data Related to the data grid Tree data feature support: commercial Support request from paid users support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/
Projects
None yet
Development

No branches or pull requests

3 participants