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] Loss of table states on re-renders #14612

Closed
carlosg-mscope opened this issue Sep 13, 2024 · 2 comments
Closed

[data grid] Loss of table states on re-renders #14612

carlosg-mscope opened this issue Sep 13, 2024 · 2 comments
Labels
bug 🐛 Something doesn't work component: data grid This is the name of the generic UI component, not the React module! status: waiting for author Issue with insufficient information support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/

Comments

@carlosg-mscope
Copy link

carlosg-mscope commented Sep 13, 2024

Steps to reproduce

Steps:

  1. Make any changes on the table. Like filtering, reordering, and change column widths.
  2. Make a dispatch or any state change on any handler, that triggers any parent re render.
  3. The table losses the current state (filters, ordering, widths, etc) and revert to the initial state.

Current behavior

As mention, the state is lost whenever we make any changes on the parents components where the table lives.

Expected behavior

To keep the state by default. If there is no controlled table inside models.

Context

We have already try to make controlled states, with the models props, and persist them (at the localstorage for convenience) as the documentation recommends. But still some states are loss, and the Grid is erratic . For example whenever we filter by the operator "contains", if you use a handler that in the parent updates some state, then the table loss it state inmediatly and close the filter modal, even if you are still typing.

Also we are already using memo for the columns and rows props, and useCallbacks for the handlers so that they dont change reference. But still it does not work, even if we leave the dependecy array empty so it computes only once. But I think that the component should expect changes in column definition, since some applications might have complex custom components inside the renderers, that could need some state from the parent.

Also I think this has to do with some other recently issues

Here are some important parts of the code definitions:

//Columns definition:
const columns = useMemo(() => [
  {
    field: "sectorId",
    filterable: false,
  },
  {
    field: "swTop",
    filterable: false,
  },
  {
    field: "id_plataforma",
    filterable: false,
  },
  {
    field: "name",
    headerName: dictionary["companies"],
    width: smallScreen ? 180 : 360,
    type: "string",
    groupable: false,
    renderHeader: (params) => {
      return (
        <BaseBox
          sx={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <BaseTypography
            sx={{
              ...theme.typography.mid.s.regular,
              color: theme.palette.light.content.mid,
            }}
          >
            {`${params.colDef.headerName} ${companiesCount?.length}`}
          </BaseTypography>
        </BaseBox>
      );
    },
    renderCell: (params) => {
      return (
        <BaseLink
          target="_blank"
          href={`/company/${params.row.swTop}/${params.row.id_plataforma}`}
          sx={{
            cursor: "pointer",
            ...theme.typography.mid.s.regular,
            color: theme.palette.light.content.high,
            textDecoration: "none",
            "&:hover": {
              color: theme.palette.light.content.high,
              textDecoration: "underline",
              textDecorationColor: theme.palette.light.content.low,
            },
          }}
          onClick={(e) => {
            mixpanel.track(MXP_TABLE_VIEW_COPANY_DETAIL, {
              Module: "Intelligence",
              Client: clientName,
              companyPlatformId: params.row.id_plataforma,
            });
          }}
        >
          <BaseBox
            sx={{
              overflow: "hidden",
              textOverflow: "ellipsis",
              whiteSpace: "nowrap",
              width: params.colDef.computedWidth - 20,
            }}
          >
            {params.value?.toUpperCase()}
          </BaseBox>
        </BaseLink>
      );
    },
  },
  {
    field: "province",
    headerName: dictionary["province"],
    type: "singleSelect",
    valueOptions: mappedRegions,
    width: smallScreen ? 120 : 200,
    groupable: false,
    renderCell: (params) => {
      return (
        <BaseBox
          sx={{
            overflow: "hidden",
            textOverflow: "ellipsis",
            whiteSpace: "nowrap",
            width: params.colDef.computedWidth - 20,
          }}
        >
          {params.value}
        </BaseBox>
      );
    },
  },
  {
    field: "fiscalPeriod",
    headerName: dictionary["year"],
    width: smallScreen ? 50 : 100,
    type: "number",
    headerAlign: "left",
    align: "left",
    groupable: false,
  },
  {
    field: "nombreTendencia3",
    headerName: dictionary["microsector"],
    width: smallScreen ? 260 : 360,
    type: "singleSelect",
    valueOptions: mappedMicrosectors,
    groupable: false,
    renderCell: (params) => {
      const ratingLetter = params?.row.rating ? params.row.rating[0] : "";
      const sectorCategoryInfo = sectorCategories.find(
        (e) => e.sectorCategory === ratingLetter
      );
      let rotation =
        ratingLetter === "B"
          ? 90
          : ratingLetter === "C"
          ? 135
          : ratingLetter === "C"
          ? 180
          : 0;
      const cellComponent = params.value ? (
        <BaseBox
          sx={{
            display: "flex",
            alignItems: "center",
            justifyContent: "flex-start",
            height: "100%",
            width: "90%",
            maxWidth: "fit-content",
            marginLeft: "0px",
          }}
        >
          {linkEnable ? (
            <Link
              style={{ width: "100%" }}
              target="_blank"
              to={"microsegmentation/" + params.row.sectorId}
              onClick={() => {
                dispatch({
                  type: types.SEND_MIXPANEL_EVENT,
                  nameEvent: MXP_VIEW_MICROSECTOR_DETAIL,
                  propertiesEvent: {
                    ID: params.row.id_plataforma
                  },
                });
              }}
            >
              {HighlightTextWrapper({
                sectorCategoryInfo,
                sectorCategoryLetter: ratingLetter,
                description: params.value,
                rotation,
              })}
            </Link>
          ) : (
            HighlightTextWrapper({
              sectorCategoryInfo,
              sectorCategoryLetter: ratingLetter,
              description: params.value,
              rotation,
            })
          )}
        </BaseBox>
      ) : (
        <BaseTypography
          sx={{
            color: theme.palette.light.content.low,
            ...theme.typography.mid.s.regular,
          }}
        >
          {"N/A"}
        </BaseTypography>
      );
      return cellComponent;
    },
  },
  {
    field: "swSeed",
    headerName: dictionary["classifAccuracy"],
    width: 120,
    type: "singleSelect",
    valueOptions: [dictionary["verified"], dictionary["suggested"]],
    groupable: false,
    renderCell: (params) => {
      return params.value ? (
        <BaseBox
          sx={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            height: "26px",
            width: "fit-content",
            marginLeft: "0px",
            backgroundColor: theme.palette.tag[1],
            borderRadius: "4px",
          }}
        >
          <BaseTypography
            sx={{
              ...theme.typography.mid.s.regular,
              color: theme.palette.light.content.high,
              textAlign: "center",
              padding: "0 6px",
            }}
          >
            {params.value}
          </BaseTypography>
        </BaseBox>
      ) : null;
    },
  },
  {
    field: "rating",
    headerName: dictionary["score"],
    width: 58,
    maxWidth: 100,
    type: "singleSelect",
    valueOptions: mappedRatings,
    groupable: false,
    cellClassName: (params) => {
      if (params.value?.startsWith("A")) return "rating-a";
      if (params.value?.startsWith("B")) return "rating-b";
      if (params.value?.startsWith("C")) return "rating-c";
      return "";
    },
  },
  {
    field: "sales",
    headerName: `${setMonetaryUnit(dictionary["sales"], currencySymbol)}`,
    flex: 1,
    type: "number",
    valueFormatter: negativeValueFormatter,
    groupable: false,
  },
  {
    field: "cagr5YearsSales",
    headerName: `${setMonetaryUnit(dictionary["cagr5TO"], currencySymbol)}`,
    flex: 1,
    type: "number",
    valueFormatter: negativeValueFormatter,
    groupable: false,
  },
], []);


//Rows definition:
const mappedArray = useMemo(() => {
  return classifications.map((item) => ({
    id: item.cif,
    swTop: item.swTop,
    id_plataforma: item.code,
    name: item.name,
    province: item.province,
    fiscalPeriod: item.lastFiscalPeriod,
    swSeed: item.swSeed === 1 ? dictionary["verified"] : dictionary["suggested"],
    nombreTendencia3: item.NOMBRE_TENDENCIA3,
    sectorId: item.sectorId,
    rating: item.sectorCategory && item.companyType
      ? `${item.sectorCategory}${item.companyType}`
      : item.companyType 
      ? `${item.companyType}`
      : "",
    sales: item.sales,
    cagr5YearsSales: item.cagr5YearsSales,
    ebitda: item.ebitda,
    cagr5YearsEbitda: item.cagr5YearsEbitda,
    debt3A: item.debt3A,
    marginBai3A: item.marginBai3A,
    marginEbitda3A: item.marginEbitda3A,
    roa3A: item.roa3A,
    roe3A: item.roe3A,
    solvency3A: item.solvency3A,
    cirbeCuote: item.cirbeCuote,
    grossMarginMonthly: item.grossMarginMonthly,
    linkDescription: item.linkDescription ? item.linkDescription : "N/A",
    current_portfolio: item.current_portfolio === 1 ? dictionary["yes"] : dictionary["no"],
  }));
}, []);

//Example of one handler
const handleFilterModelChange = useCallback((model) => {
     //  ⚠️Here we dispatch some action, or make some state changes for this component. If we do so, the table loss all states, and revert to the initial state.
  }, []);

return (
  <BaseBox
    sx={{
      height: "100%",
      width: "100%",
      "& .MuiDataGrid-cell.rating-a": {
        color: theme?.palette.semantic.success.veryhigh,
      },
      "& .MuiDataGrid-cell.rating-c": {
        color: theme?.palette.semantic.danger.veryhigh,
      },
    }}
  >
    <BaseTable
      data={mappedArray}
      columns={columns}
      handleFilterModelChange={handleFilterModelChange}
    />
  </BaseBox>
);

And here the definition of the BaseTable component where is define the DataGrid MUI x component

const BaseTable = ({
  data = [],
  columns = [],
  columnsToHide = [],
  handleColumnOrderChange = () => {},
  handleSortModelChange = () => {},
  handlePinnedColumnsChange = () => {},
  handleGroupModelChange = () => {},
  handleFilterModelChange = () => {},
  handleRowSelectionChange = () => {},
}) => {
  const apiRef = useGridApiRef();
  const theme = useTheme();

  return (
    <DataGridPremium
      apiRef={apiRef}
      rows={data}
      columns={columns}
      pagination
      pageSizeOptions={[25, 50, 100]}
      onColumnHeaderClick={(params, event) => {
        event.defaultMuiPrevented = true;
        apiRef.current.showColumnMenu(params.field);
      }}
      onRowGroupingModelChange={(newModel) => {
        handleGroupModelChange(newModel);
      }}
      onSortModelChange={(model) => {
        handleSortModelChange(model);
      }}
      onFilterModelChange={(model) => {
        handleFilterModelChange(model);
      }}
      onColumnOrderChange={(params) => {
        handleColumnOrderChange(params);
      }}
      onPinnedColumnsChange={(model) => {
        handlePinnedColumnsChange(model);
      }}
      onRowSelectionModelChange={(model) => {
        handleRowSelectionChange(model);
      }}
      sx={{
        ".MuiDataGrid-groupingCriteriaCellToggle": {
          marginRight: "0px",
        },
        ".MuiDataGrid-columnHeader .MuiButtonBase-root.MuiIconButton-root": {
          padding: 0,
          width: 0,
          bgcolor: "transparent",
        },
        ".MuiDataGrid-columnHeader--sorted .MuiButtonBase-root.MuiIconButton-root, .MuiDataGrid-columnHeader--filtered .MuiButtonBase-root.MuiIconButton-root":
          {
            paddingLeft: "6px",
            paddingRight: "10px",
          },
        ".MuiDataGrid-columnHeader:not(.MuiDataGrid-columnHeader--sorted).MuiDataGrid-columnHeader--filtered .MuiButtonBase-root.MuiIconButton-root:has(.MuiDataGrid-sortIcon)":
          {
            padding: 0,
            width: 0,
          },
        ".MuiBadge-badge": {
          top: "-3px",
          right: "3px",
          color: theme.palette.light.content.mid,
        },
      }}
    />
  );
};

BaseTable.propTypes = {
  data: PropTypes.array.isRequired,
  columns: PropTypes.array.isRequired,
  tableName: PropTypes.string.isRequired,
};

export default BaseTable;

Your environment

   Browser: Chrome ,

    System:
    OS: Windows 11 10.0.22631
  Binaries:
    Node: 18.16.1 - C:\Program Files\nodejs\node.EXE
    npm: 9.5.1 - C:\Program Files\nodejs\npm.CMD
    pnpm: Not Found
  Browsers:
    Chrome: Not Found
    Edge: Chromium (128.0.2739.54)
  npmPackages:
    @emotion/react: ^11.11.4 => 11.11.4 
    @emotion/styled: ^11.11.5 => 11.11.5 
    @mui/base:  5.0.0-beta.40 
    @mui/core-downloads-tracker:  5.15.20 
    @mui/icons-material: ^5.15.20 => 5.15.20 
    @mui/material: ^5.15.17 => 5.15.20 
    @mui/private-theming:  5.16.1 
    @mui/styled-engine:  5.16.1
    @mui/system:  5.16.1
    @mui/types:  7.2.15
    @mui/utils:  5.16.1
    @mui/x-data-grid:  7.10.0
    @mui/x-data-grid-generator:  7.10.0
    @mui/x-data-grid-premium: ^7.7.0 => 7.10.0
    @mui/x-data-grid-pro:  7.10.0
    @mui/x-internals:  7.10.0
    @mui/x-license: ^7.6.1 => 7.10.0
    @types/react:  18.3.3
    react: ^18.3.1 => 18.3.1
    react-dom: ^18.3.1 => 18.3.1

Search keywords: Preserve State Filters Dimensions
Order ID: 92280

@carlosg-mscope carlosg-mscope added bug 🐛 Something doesn't work status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Sep 13, 2024
@github-actions github-actions bot added component: data grid This is the name of the generic UI component, not the React module! support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/ labels Sep 13, 2024
@michelengelen
Copy link
Member

To help us diagnose the issue efficiently, could you provide a stripped-down reproduction test case using the latest version? A live example would be fantastic! ✨

For your convenience, our documentation offers templates and guides on creating targeted examples: Support - Bug reproduction

Just a friendly reminder: clean, functional code with minimal dependencies is most helpful. Complex code can make it tougher to pinpoint the exact issue. Sometimes, simply going through the process of creating a minimal reproduction can even clarify the problem itself!

Thanks for your understanding! 🙇🏼

@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 Sep 16, 2024
@michelengelen michelengelen changed the title Loss of table states on re-renders. Filters, sorting, ordering, grouping, and dimensions. [data grid] Loss of table states on re-renders Sep 16, 2024
Copy link

The issue has been inactive for 7 days and has been automatically closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: data grid This is the name of the generic UI component, not the React module! status: waiting for author Issue with insufficient information 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

2 participants