From 87312bce257fe84a9e597993e1004aeed3d22dbf Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sat, 9 May 2020 21:03:39 -0600 Subject: [PATCH] fix: rect and scroll ref tracking now support un/remounting --- examples/sandbox/src/Dynamic.js | 91 ++++++++++++------------ examples/sandbox/src/InfiniteScroll.js | 96 ++++++++++++++------------ examples/sandbox/src/SmoothScroll.js | 81 ++++++++++++++-------- src/useRect.js | 34 ++++----- src/useScroll.js | 30 ++++++-- 5 files changed, 191 insertions(+), 141 deletions(-) diff --git a/examples/sandbox/src/Dynamic.js b/examples/sandbox/src/Dynamic.js index 45aa6b40..0ce646e5 100644 --- a/examples/sandbox/src/Dynamic.js +++ b/examples/sandbox/src/Dynamic.js @@ -159,58 +159,63 @@ function GridVirtualizerDynamic({ rows, columns }) { overscan: 5 }); + const [show, setShow] = React.useState(true); + return ( <> -
+ {/* */} + {show ? (
- {rowVirtualizer.virtualItems.map(virtualRow => ( - - {columnVirtualizer.virtualItems.map(virtualColumn => ( -
{ - virtualRow.measureRef(el); - virtualColumn.measureRef(el); - }} - className={ - virtualColumn.index % 2 - ? virtualRow.index % 2 === 0 +
+ {rowVirtualizer.virtualItems.map(virtualRow => ( + + {columnVirtualizer.virtualItems.map(virtualColumn => ( +
{ + virtualRow.measureRef(el); + virtualColumn.measureRef(el); + }} + className={ + virtualColumn.index % 2 + ? virtualRow.index % 2 === 0 + ? "ListItemOdd" + : "ListItemEven" + : virtualRow.index % 2 ? "ListItemOdd" : "ListItemEven" - : virtualRow.index % 2 - ? "ListItemOdd" - : "ListItemEven" - } - style={{ - position: "absolute", - top: 0, - left: 0, - width: `${columns[virtualColumn.index]}px`, - height: `${rows[virtualRow.index]}px`, - transform: `translateX(${virtualColumn.start}px) translateY(${virtualRow.start}px)` - }} - > - Cell {virtualRow.index}, {virtualColumn.index} -
- ))} -
- ))} + } + style={{ + position: "absolute", + top: 0, + left: 0, + width: `${columns[virtualColumn.index]}px`, + height: `${rows[virtualRow.index]}px`, + transform: `translateX(${virtualColumn.start}px) translateY(${virtualRow.start}px)` + }} + > + Cell {virtualRow.index}, {virtualColumn.index} +
+ ))} + + ))} +
-
+ ) : null} ); } diff --git a/examples/sandbox/src/InfiniteScroll.js b/examples/sandbox/src/InfiniteScroll.js index 2f684955..e8e7381c 100644 --- a/examples/sandbox/src/InfiniteScroll.js +++ b/examples/sandbox/src/InfiniteScroll.js @@ -47,10 +47,20 @@ export default () => { return; } - if (lastItem.index === flatPosts.length - 1 && canFetchMore) { + if ( + lastItem.index === flatPosts.length - 1 && + canFetchMore && + !isFetchingMore + ) { fetchMore(); } - }, [canFetchMore, fetchMore, flatPosts.length, rowVirtualizer.virtualItems]); + }, [ + canFetchMore, + fetchMore, + flatPosts.length, + isFetchingMore, + rowVirtualizer.virtualItems + ]); return ( <> @@ -68,53 +78,53 @@ export default () => {

Loading...

) : status === "error" ? ( Error: {error.message} - ) : null} - -
+ ) : (
- {rowVirtualizer.virtualItems.map(virtualRow => { - const isLoaderRow = virtualRow.index > flatPosts.length - 1; - const post = flatPosts[virtualRow.index]; +
+ {rowVirtualizer.virtualItems.map(virtualRow => { + const isLoaderRow = virtualRow.index > flatPosts.length - 1; + const post = flatPosts[virtualRow.index]; - return ( -
- {isLoaderRow - ? canFetchMore - ? "Loading more..." - : "Nothing more to load" - : post.title} -
- ); - })} + return ( +
+ {isLoaderRow + ? canFetchMore + ? "Loading more..." + : "Nothing more to load" + : post.title} +
+ ); + })} +
-
+ )}
{isFetching && !isFetchingMore ? "Background Updating..." : null}
diff --git a/examples/sandbox/src/SmoothScroll.js b/examples/sandbox/src/SmoothScroll.js index e106abda..ab8ee85e 100644 --- a/examples/sandbox/src/SmoothScroll.js +++ b/examples/sandbox/src/SmoothScroll.js @@ -6,33 +6,44 @@ function easeInOutQuint(t) { return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; } +const timeout = fn => setTimeout(fn, 16); + +const raf = fn => requestAnimationFrame(fn); + export default function() { const parentRef = React.useRef(); const scrollingRef = React.useRef(); - const scrollToFn = React.useCallback(offset => { - const duration = 1000; - const start = parentRef.current.scrollTop; - const startTime = (scrollingRef.current = Date.now()); - - const run = () => { - if (scrollingRef.current !== startTime) return; - const now = Date.now(); - const elapsed = now - startTime; - const progress = easeInOutQuint(Math.min(elapsed / duration, 1)); - const interpolated = start + (offset - start) * progress; - - if (elapsed < duration) { - parentRef.current.scrollTop = interpolated; - setTimeout(run, 16); - } else { - parentRef.current.scrollTop = interpolated; - } - }; - - setTimeout(run, 16); - }, []); + const [animationType, setAnimationType] = React.useState("setTimeout"); + + const animationFn = animationType === "setTimeout" ? timeout : raf; + + const scrollToFn = React.useCallback( + offset => { + const duration = 1000; + const start = parentRef.current.scrollTop; + const startTime = (scrollingRef.current = Date.now()); + + const run = () => { + if (scrollingRef.current !== startTime) return; + const now = Date.now(); + const elapsed = now - startTime; + const progress = easeInOutQuint(Math.min(elapsed / duration, 1)); + const interpolated = start + (offset - start) * progress; + + if (elapsed < duration) { + parentRef.current.scrollTop = interpolated; + animationFn(run); + } else { + parentRef.current.scrollTop = interpolated; + } + }; + + animationFn(run); + }, + [animationFn] + ); const rowVirtualizer = useVirtual({ size: 10000, @@ -52,13 +63,25 @@ export default function() {

- + +
+ +


diff --git a/src/useRect.js b/src/useRect.js index b8572301..7519701e 100644 --- a/src/useRect.js +++ b/src/useRect.js @@ -4,34 +4,29 @@ import observeRect from '@reach/observe-rect' import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect' -export default function useRect(nodeRef, observe = true, onChange) { - const [, rerender] = React.useState() +export default function useRect(nodeRef) { + const [element, setElement] = React.useState(nodeRef.current) const [rect, setRect] = React.useState(null) const initialRectSet = React.useRef(false) - const onChangeRef = React.useRef(null) - onChangeRef.current = onChange - const element = nodeRef.current + useIsomorphicLayoutEffect(() => { + if (nodeRef.current !== element) { + setElement(nodeRef.current) + } + }) useIsomorphicLayoutEffect(() => { - if (!element) { - requestAnimationFrame(() => { - rerender({}) - }) + if (element && !initialRectSet.current) { + initialRectSet.current = true + setRect(element.getBoundingClientRect()) } + }, [element]) + React.useEffect(() => { let observer if (element) { - observer = observeRect(element, function (rect) { - onChangeRef.current && onChangeRef.current(rect) - setRect(rect) - }) - } - - if (element && !initialRectSet.current) { - initialRectSet.current = true - setRect(element.getBoundingClientRect()) + observer = observeRect(element, setRect) } observer && observer.observe() @@ -39,6 +34,7 @@ export default function useRect(nodeRef, observe = true, onChange) { return () => { observer && observer.unobserve() } - }, [element, observe, onChange]) + }, [element]) + return rect } diff --git a/src/useScroll.js b/src/useScroll.js index cb71561b..1c428dfd 100644 --- a/src/useScroll.js +++ b/src/useScroll.js @@ -1,12 +1,28 @@ import React from 'react' -export default function useScroll(ref, onChange) { +import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect' + +export default function useScroll(nodeRef, onChange) { + const [element, setElement] = React.useState(nodeRef.current) const onChangeRef = React.useRef() onChangeRef.current = onChange - React.useEffect(() => { - const el = ref.current + useIsomorphicLayoutEffect(() => { + if (nodeRef.current !== element) { + setElement(nodeRef.current) + } + }) + + useIsomorphicLayoutEffect(() => { + if (element) { + onChangeRef.current({ + scrollLeft: element.scrollLeft, + scrollTop: element.scrollTop, + }) + } + }, [element]) + React.useEffect(() => { const handler = e => { onChangeRef.current({ scrollLeft: e.target.scrollLeft, @@ -14,15 +30,15 @@ export default function useScroll(ref, onChange) { }) } - if (el) { - el.addEventListener('scroll', handler, { + if (element) { + element.addEventListener('scroll', handler, { capture: false, passive: true, }) return () => { - el.removeEventListener('scroll', handler) + element.removeEventListener('scroll', handler) } } - }, [ref]) + }, [element]) }