Skip to content

Commit

Permalink
fix: rect and scroll ref tracking now support un/remounting
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed May 10, 2020
1 parent c8f8af5 commit 87312bc
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 141 deletions.
91 changes: 48 additions & 43 deletions examples/sandbox/src/Dynamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,58 +159,63 @@ function GridVirtualizerDynamic({ rows, columns }) {
overscan: 5
});

const [show, setShow] = React.useState(true);

return (
<>
<div
ref={parentRef}
className="List"
style={{
height: `400px`,
width: `500px`,
overflow: "auto"
}}
>
{/* <button onClick={() => setShow(old => !old)}>Toggle</button> */}
{show ? (
<div
ref={parentRef}
className="List"
style={{
height: `${rowVirtualizer.totalSize}px`,
width: `${columnVirtualizer.totalSize}px`,
position: "relative"
height: `400px`,
width: `500px`,
overflow: "auto"
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => (
<React.Fragment key={virtualRow.index}>
{columnVirtualizer.virtualItems.map(virtualColumn => (
<div
key={virtualColumn.index}
ref={el => {
virtualRow.measureRef(el);
virtualColumn.measureRef(el);
}}
className={
virtualColumn.index % 2
? virtualRow.index % 2 === 0
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
width: `${columnVirtualizer.totalSize}px`,
position: "relative"
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => (
<React.Fragment key={virtualRow.index}>
{columnVirtualizer.virtualItems.map(virtualColumn => (
<div
key={virtualColumn.index}
ref={el => {
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}
</div>
))}
</React.Fragment>
))}
}
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}
</div>
))}
</React.Fragment>
))}
</div>
</div>
</div>
) : null}
</>
);
}
96 changes: 53 additions & 43 deletions examples/sandbox/src/InfiniteScroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
Expand All @@ -68,53 +78,53 @@ export default () => {
<p>Loading...</p>
) : status === "error" ? (
<span>Error: {error.message}</span>
) : null}

<div
ref={parentRef}
className="List"
style={{
height: `500px`,
width: `100%`,
overflow: "auto"
}}
>
) : (
<div
ref={parentRef}
className="List"
style={{
height: `${rowVirtualizer.totalSize}px`,
width: "100%",
position: "relative"
height: `500px`,
width: `100%`,
overflow: "auto"
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => {
const isLoaderRow = virtualRow.index > flatPosts.length - 1;
const post = flatPosts[virtualRow.index];
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
width: "100%",
position: "relative"
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => {
const isLoaderRow = virtualRow.index > flatPosts.length - 1;
const post = flatPosts[virtualRow.index];

return (
<div
key={virtualRow.index}
className={
virtualRow.index % 2 ? "ListItemOdd" : "ListItemEven"
}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
{isLoaderRow
? canFetchMore
? "Loading more..."
: "Nothing more to load"
: post.title}
</div>
);
})}
return (
<div
key={virtualRow.index}
className={
virtualRow.index % 2 ? "ListItemOdd" : "ListItemEven"
}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
{isLoaderRow
? canFetchMore
? "Loading more..."
: "Nothing more to load"
: post.title}
</div>
);
})}
</div>
</div>
</div>
)}
<div>
{isFetching && !isFetchingMore ? "Background Updating..." : null}
</div>
Expand Down
81 changes: 52 additions & 29 deletions examples/sandbox/src/SmoothScroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -52,13 +63,25 @@ export default function() {
<br />
<br />

<button
onClick={() =>
rowVirtualizer.scrollToIndex(Math.floor(Math.random() * 10000))
}
>
Scroll To Random Index
</button>
<label>
Animation Type:
<select
value={animationType}
onChange={e => setAnimationType(e.target.value)}
>
<option value="setTimeout">setTimeout</option>
<option value="raf">requestAnimationFram</option>
</select>
</label>
<div>
<button
onClick={() =>
rowVirtualizer.scrollToIndex(Math.floor(Math.random() * 10000))
}
>
Scroll To Random Index
</button>
</div>

<br />
<br />
Expand Down
34 changes: 15 additions & 19 deletions src/useRect.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,37 @@ 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()

return () => {
observer && observer.unobserve()
}
}, [element, observe, onChange])
}, [element])

return rect
}
Loading

0 comments on commit 87312bc

Please sign in to comment.