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

[DataGrid] Add 'gridReady' event to notify the user when the grid has been mounted #6568

Open
wants to merge 9 commits into
base: v6.x
Choose a base branch
from

Conversation

yaredtsy
Copy link
Contributor

@yaredtsy yaredtsy commented Oct 20, 2022

Fixes #6411

Problem

we can not use apiRef.current in useEffect. because apiRef.current.windowRef is null in the begging and we can not tell when it is ready to use. so the only solution was to use it in setTimout.

Solution

i have created an event called gridReady which will get triggered when the state initialization is finished. user can subscribe to it to get notified when the grid is ready to interact.

@mui-bot
Copy link

mui-bot commented Oct 20, 2022

Netlify deploy preview

Netlify deploy preview: https://deploy-preview-6568--material-ui-x.netlify.app/

Updated pages

No updates.

These are the results for the performance tests:

Test case Unit Min Max Median Mean σ
Filter 100k rows ms 749.5 1,475.7 749.5 1,042.06 291.541
Sort 100k rows ms 874.9 1,496.8 1,358.8 1,268.18 208.753
Select 100k rows ms 295.6 566.2 400.7 393.24 98.209
Deselect 100k rows ms 198.8 380.9 289.8 279.42 68.346

Generated by 🚫 dangerJS against cfe8eba

@DanailH DanailH changed the title [DataGrid] The 'gridReady' event has been added to notify the user when the grid has been mounted.  [DataGrid] Add 'gridReady' event to notify the user when the grid has been mounted Oct 20, 2022
@DanailH DanailH assigned DanailH and yaredtsy and unassigned DanailH Oct 20, 2022
@DanailH DanailH added bug 🐛 Something doesn't work component: data grid This is the name of the generic UI component, not the React module! labels Oct 20, 2022
Copy link
Member

@DanailH DanailH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The destination branch should be next. As far as I can see this is not a critical issue. We can actually even consider it as an enhancement.

@DanailH DanailH added enhancement This is not a bug, nor a new feature and removed bug 🐛 Something doesn't work labels Oct 20, 2022
@yaredtsy yaredtsy changed the base branch from master to next October 20, 2022 11:45
@yaredtsy yaredtsy requested review from DanailH and removed request for MBilalShafi, cherniavskii and m4theushw October 20, 2022 17:14
@yaredtsy
Copy link
Contributor Author

@DanailH done, and I have accidentally removed other reviewers. 

@m4theushw m4theushw self-requested a review October 26, 2022 12:51
Copy link
Member

@m4theushw m4theushw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! Could you fix the pipeline and provide a CodeSandbox showing that this PR solves the use case in #6411. I'm not very sure it will fix because the scrollTOp is reset in an effect when the grid is mounted:

React.useEffect(() => {
if (disableVirtualization) {
renderZoneRef.current!.style.transform = `translate3d(0px, 0px, 0px)`;
} else {
// TODO a scroll reset should not be necessary
rootRef.current!.scrollLeft = 0;
rootRef.current!.scrollTop = 0;
}
}, [disableVirtualization]);

I was expecting something like

diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
index 2b773e035..f16e2999a 100644
--- a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
+++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
@@ -205,7 +205,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
     containerWidth,
   ]);
 
-  React.useEffect(() => {
+  React.useLayoutEffect(() => {
     if (disableVirtualization) {
       renderZoneRef.current!.style.transform = `translate3d(0px, 0px, 0px)`;
     } else {
@@ -213,6 +213,10 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
       rootRef.current!.scrollLeft = 0;
       rootRef.current!.scrollTop = 0;
     }
+
+    if (onReady) {
+      onReady();
+    }
   }, [disableVirtualization]);
 
   React.useEffect(() => {

@yaredtsy
Copy link
Contributor Author

@m4theushw in setState I have modified the equality check. and it breaks 1 test.

diff --git a/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts b/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts
index 1662575f..debcf7a3 100644
--- a/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts
+++ b/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts
@@ -36,7 +36,7 @@ export const useGridStateInitialization = <Api extends GridApiCommon>(
         newState = state;
       }

-      if (apiRef.current.state === newState) {
+      if (isDeepEqual(apiRef.current.state, newState)) {
         return false;
       }

Demo: https://codesandbox.io/s/admiring-scott-chcls3?file=/demo.tsx

@yaredtsy
Copy link
Contributor Author

Hey @m4theushw
I found the problem that was breaking the test. It was columnGrouping tests.

 it('should update headers when `columnGroupingModel` is modified', () => {
    ...
      setProps({ columnGroupingModel: [{ groupId: 'col2', children: [{ field: 'col2' }] }] });
      expect(screen.queryAllByRole('row')).to.have.length(3);
}

the problem was when the states change columnGrouping was not updating in the first round.

Demo: https://codesandbox.io/s/silly-hypatia-xgs712?file=/demo.tsx you have to click the update button twice to update it.

fix

--- a/packages/grid/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts
+++ b/packages/grid/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts
@@ -174,6 +174,7 @@ export const useGridColumnGrouping = (
         },
       };
     });
+    apiRef.current.forceUpdate();
   }, [
     apiRef,
     columnFields,

this seemed to fix it should I create another PR for this
Demo: https://codesandbox.io/s/elegant-jang-dnctgf

apiRef.current.windowRef = { current: node };
const unsubscribe = apiRef.current.subscribeEvent('stateChange', () => {
setTimeout(() => {
apiRef.current.publishEvent('gridReady');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it not scroll to the index for you?
image

I add setTimeout because some states like rowMeta might not finish initializing after row data is set. I am experimenting with a way to check if all the states have finished before gridReady is triggered.

const unsubscribe = apiRef.current.subscribeEvent('stateChange', (newState) => { 
     if (newState.rowsMeta.currentPageTotalHeight > 0) {
          setTimeout(() => {
              apiRef.current.publishEvent('gridReady');
          }, 0);
          unsubscribe();
          }
        });
}

const unsubscribe = apiRef.current.subscribeEvent('stateChange', () => {
setTimeout(() => {
apiRef.current.publishEvent('gridReady');
}, 5);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the original issue #6411 specifically, would gridReady solve it if the grid is rendered in a loading state while waiting for rows data to load?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's why I add for 'stateChange' listener. if the rows data did not load it won't be fired. the downside is if you create an empty DataGrid, 'gridReady' won't be triggered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the grid is empty or in the loading state it's still "ready".

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Nov 7, 2022
@github-actions
Copy link

github-actions bot commented Nov 7, 2022

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Nov 7, 2022
@github-actions
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

packages/grid/x-data-grid/src/components/base/GridBody.tsx Outdated Show resolved Hide resolved
(node: HTMLDivElement) => {
if (node && apiRef.current.windowRef?.current == null) {
apiRef.current.windowRef = { current: node };
const triggerGridReadyEvent = debounce(() => apiRef.current.publishEvent('gridReady'), 100);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debounce added is fighting against

// TODO a scroll reset should not be necessary
rootRef.current!.scrollLeft = 0;
rootRef.current!.scrollTop = 0;

Why not try #6568 (review) that doesn't require setTimeout? We should try to avoid setTimeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not try #6568 (review) that doesn't require setTimeout? We should try to avoid setTimeout

I have tried it, but 'gridReady' is getting triggered multiple times. I could not get it to be triggered only 1 time.

diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
index 782d3db54..b5e7ccf87 100644
--- a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
+++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
@@ -1,6 +1,7 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { unstable_useForkRef as useForkRef } from '@mui/utils';
+import { debounce } from '@mui/material/utils';
 import { useGridApiContext } from '../../utils/useGridApiContext';
 import { useGridRootProps } from '../../utils/useGridRootProps';
 import { useGridSelector } from '../../utils/useGridSelector';
@@ -97,6 +98,10 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
     getRowProps,
   } = props;
 
+  const triggerGridReadyEvent = React.useMemo(
+    () => debounce(() => apiRef.current.publishEvent('gridReady'), 300),
+    [apiRef],
+  );
   const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector);
   const columnsTotalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector);
   const cellFocus = useGridSelector(apiRef, gridFocusCellSelector);
@@ -205,7 +210,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { 
     containerWidth,
   ]);
 
-  React.useEffect(() => {
+  React.useLayoutEffect(() => {
     if (disableVirtualization) {
       renderZoneRef.current!.style.transform = `translate3d(0px, 0px, 0px)`;
     } else {
@@ -213,7 +218,13 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
       rootRef.current!.scrollLeft = 0;
       rootRef.current!.scrollTop = 0;
     }
-  }, [disableVirtualization]);
+
+    apiRef.current.subscribeEvent('stateChange', (newState) => {
+      if (newState.rowsMeta.currentPageTotalHeight > 0) {
+        triggerGridReadyEvent();
+      }
+    });
+  }, [disableVirtualization, triggerGridReadyEvent, apiRef]);

this is how I tried to implement it.

Copy link
Contributor Author

@yaredtsy yaredtsy Nov 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @m4theushw

i was looking at

// TODO a scroll reset should not be necessary
rootRef.current!.scrollLeft = 0;
rootRef.current!.scrollTop = 0;

and I think they are getting called too early before the rows are populated, I do not think they have any effect. I have tried rootRef.current!.scrollTop = 100. and it does not do anything. also when I logged rowMeta its currentPageTotalHeight is 0. so scrolling at that point has no effect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this code, if you scroll to the middle of the viewport, disable virtualization with disableVirtualization=true, then re-enable it again, the scroll position will change a bit. Resetting the scroll was better than having this small scroll.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @m4theushw, I have managed useEffect to be called only once. and I have put the code in useGridVirtualScroller.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Nov 16, 2022
@DanailH
Copy link
Member

DanailH commented Nov 16, 2022

@yaredtsy can we add tests to see the track of the number of times the gridReady is called? We probably need a few to be sure it's working as expected.

  • simple grid with a couple of rows
  • loading grid without any rows
  • grid with a lot of rows, enough to trigger the virtualization
  • a grid with rows and after some time an interaction happens that needs to result in a state update which rerenders the grid
  • grid with a predefined initial state

These are off the top of my head. The first 4 IMO are 100% needed so we know what we want to achieve.

@yaredtsy
Copy link
Contributor Author

hey @DanailH
I have added the tests.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Dec 14, 2022
@github-actions
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Dec 30, 2022
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 18, 2023
@github-actions
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 15, 2023
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Sep 22, 2023
@github-actions
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@MBilalShafi MBilalShafi changed the base branch from master to v6.x March 21, 2024 02:48
@salos1982
Copy link

@yaredtsy Hi, I have the same issue that is described in the pull request. Could you please at least add gridReady event?

@flaviendelangle
Copy link
Member

@cherniavskii we should probably either close this PR or continue working on it to avoid giving false hope 👍

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! enhancement This is not a bug, nor a new feature PR: out-of-date The pull request has merge conflicts and can't be merged v6.x
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[data grid] Data Grid Pro scrollToIndexes Throws Error while testing using react-testing library
9 participants