Skip to content

Commit

Permalink
⚗️ [useVurttle] Experiment with throttle hook (#11)
Browse files Browse the repository at this point in the history
* 🚪 [useVurtis] Expose more internals from hook

* ⚗️ [useVurttle] New timeout and throttle hooks

* 📝 [Report] Add changeset
  • Loading branch information
beefchimi authored Apr 3, 2024
1 parent ded8c40 commit 3643541
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-pots-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vurtis": patch
---

Export useTimeout() and useVurttle() hooks.
6 changes: 6 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ export * from './useResizeObserver';
export {useIsoEffect} from './useIsoEffect';
export {useMounted} from './useMounted';

export {
useTimeout,
type TimeoutCallback,
type TimeoutOptions,
} from './useTimeout';

export {
useWindowEvent,
type WindowEventName,
Expand Down
38 changes: 38 additions & 0 deletions src/hooks/useTimeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {useEffect, useRef} from 'react';
import type {TimeoutId} from 'beeftools';

import {useIsoEffect} from './useIsoEffect';

export type TimeoutCallback = (timestamp: number) => void;

export interface TimeoutOptions {
duration?: number;
disabled?: boolean;
}

export function useTimeout(
callback: TimeoutCallback,
options: TimeoutOptions = {},
): void {
const {duration = 0, disabled = false} = options;

const callbackRef = useRef<TimeoutCallback>();
const timeoutRef = useRef<TimeoutId>();

useIsoEffect(() => {
callbackRef.current = callback;
}, [callback]);

useEffect(() => {
if (!disabled) {
timeoutRef.current = setTimeout(
() => callbackRef.current?.(Date.now()),
duration,
);
}

return () => {
clearTimeout(timeoutRef.current);
};
}, [duration, disabled]);
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './hooks';

export {useVurtis, type VurtisOptions} from './useVurtis';
export {useVurttle, VURTTLE_DURATION} from './useVurttle';

export type {
VurtisListElement,
Expand Down
31 changes: 26 additions & 5 deletions src/useVurtis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export function useVurtis({
minWidth = MIN_ITEM_SIZE,
gap = 0,
}: VurtisOptions) {
// const isMounted = useMounted();
const listRef = useRef<VurtisListElement>(null);

const [columns, setColumns] = useState(1);
Expand Down Expand Up @@ -178,15 +177,37 @@ export function useVurtis({
}, [virtualItems]);

return {
// Required props:
listRef,
updateItemHeight,
listHeight,
virtualItems,
rangeStart,
rangeEnd,

// Layout strategy 1:
// Apply `listHeight` and absolutely position all items.
listHeight,

// Layout strategy 2:
// Useful for layouts that want to use a CSS grid instead of
// absolute positioning. This may be necessary for animation.
getSpaceBefore,
getSpaceAfter,

// Optionally pass `updateItemHeight` as `ref` (ideally
// only to 1 item) in order to more accurately measure
// item height across all resize operations.
updateItemHeight,

// Additional props
// None of these should be required for a functional
// virtualized list, but might be useful for debugging.
columns,
rangeStart,
rangeEnd,
listWidth,
listVisibleHeight,
itemWidth,
itemHeight,
scrollY,
documentHeight,
windowHeight,
};
}
36 changes: 36 additions & 0 deletions src/useVurttle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {useCallback, useEffect, useState} from 'react';
import {useMounted, useTimeout} from './hooks';

// This hook is an opinionated "throttle" for `vurt` changes.
// The idea is that you will feed this hook a value such as
// `listWidth` or `itemHeight`. Upon receiving a new value,
// the `pending` state will become `true` and the timer will
// begin counting down before returning to `false`. This is
// useful for when you need to perform a side-effect to
// virtual container/item changes. A common use-case for
// this throttling layout animations during resize operations.
// This may be necessary to avoid very aggresive re-renders.

export const VURTTLE_DURATION = 200;

export function useVurttle(vurtValue = 0) {
const isMounted = useMounted();
const [pending, setPending] = useState(false);

const handleReset = useCallback(() => {
setPending(false);
}, []);

useEffect(() => {
if (isMounted() && !pending && vurtValue) {
setPending(true);
}
}, [isMounted, vurtValue]);

useTimeout(handleReset, {
duration: VURTTLE_DURATION,
disabled: !isMounted() || !pending,
});

return pending;
}

0 comments on commit 3643541

Please sign in to comment.