-
Notifications
You must be signed in to change notification settings - Fork 2.2k
DataGrid: split scroll/scroll to position/should focus position handling into hooks #3986
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
Changes from 1 commit
7b3b169
00adf05
745f209
140c714
75f1a4a
8c44657
849d069
f708783
308178e
bbd3460
f202ca7
1f4cbff
2e42da3
fe54c01
e4c59ae
c3a1990
81bfe29
332d1dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { useCallback, useSyncExternalStore } from 'react'; | ||
|
|
||
| import { abs } from '../utils'; | ||
|
|
||
| interface ScrollState { | ||
| readonly scrollTop: number; | ||
| readonly scrollLeft: number; | ||
| } | ||
|
|
||
| const initialScrollState: ScrollState = { | ||
| scrollTop: 0, | ||
| scrollLeft: 0 | ||
| }; | ||
|
|
||
| function getServerSnapshot() { | ||
| return initialScrollState; | ||
| } | ||
|
|
||
| const scrollStateMap = new WeakMap<React.RefObject<HTMLDivElement | null>, ScrollState>(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a benefit of using a map instead of setting a local state inside the component?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you suggesting we keep using
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. function Test() {
const [resolvers, setResolvers] = useState<PromiseWithResolvers<void>>(() =>
Promise.withResolvers()
);
return (
<>
<button type="button" onClick={() => setResolvers(Promise.withResolvers())}>
New promise
</button>
<button type="button" onClick={() => resolvers.resolve()}>
Resolve
</button>
<Suspense fallback="Loading...">
<Inner promise={resolvers.promise} />
</Suspense>
</>
);
}
function Inner({ promise }: { promise: Promise<void> }) {
useState(() => {
console.log('init state');
});
use(promise);
return 'ok';
}In this example the This also happens when the suspense is triggered deeper in the tree: function Inner({ promise }: { promise: Promise<void> }) {
useState(() => {
console.log('init state');
});
// use(promise);
return (
<>
<InnerSide />
<InnerDeep promise={promise} />
</>
);
}
function subscribe() {
console.log('subscribe');
return () => {
console.log('unsubscribe');
};
}
function getSnapshot() {
console.log('get snapshot');
}
function InnerSide() {
useState(() => {
console.log('init state side');
});
useSyncExternalStore(subscribe, getSnapshot);
return 'side';
}
function InnerDeep({ promise }: { promise: Promise<void> }) {
useState(() => {
console.log('init state deep');
});
use(promise);
return 'ok';
}React only stops re-initializing states when it renders the tree under All that to say... I guess it's slightly better to have a single WeakMap?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean a single WeakMap would be more performant or prevent some edge cases?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's simple and potentially avoids states reinitialization due to suspense/activity.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure. Let's go with what we have. |
||
|
|
||
| export function useScrollState(gridRef: React.RefObject<HTMLDivElement | null>): ScrollState { | ||
| const subscribe = useCallback( | ||
| (onStoreChange: () => void) => { | ||
| if (gridRef.current === null) return () => {}; | ||
|
|
||
| const el = gridRef.current; | ||
|
|
||
| // prime the scroll state map with the initial values | ||
| setScrollState(); | ||
|
|
||
| function setScrollState() { | ||
| const { scrollTop } = el; | ||
| // scrollLeft is negative when direction is rtl | ||
| const scrollLeft = abs(el.scrollLeft); | ||
|
|
||
| const prev = scrollStateMap.get(gridRef) ?? initialScrollState; | ||
| if (prev.scrollTop === scrollTop && prev.scrollLeft === scrollLeft) { | ||
| return false; | ||
| } | ||
|
|
||
| scrollStateMap.set(gridRef, { scrollTop, scrollLeft }); | ||
| return true; | ||
| } | ||
|
|
||
| function onScroll() { | ||
| if (setScrollState()) { | ||
| onStoreChange(); | ||
| } | ||
| } | ||
|
|
||
| el.addEventListener('scroll', onScroll); | ||
|
|
||
| return () => el.removeEventListener('scroll', onScroll); | ||
| }, | ||
| [gridRef] | ||
| ); | ||
|
|
||
| const getSnapshot = useCallback((): ScrollState => { | ||
| // gridRef.current is null during initial render, suspending, or <Activity mode="hidden"> | ||
| // to avoid returning a different state in those cases, | ||
| // we key the ref object instead of the element itself | ||
| return scrollStateMap.get(gridRef) ?? initialScrollState; | ||
| }, [gridRef]); | ||
|
|
||
| return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scroll state now uses |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍