TanStack provides a large range of tools that can be used in vanilla JS/TS or React framework.
TanStack Virtual is a headless UI utility for virtualizing long lists of elements.
To install:
npm install @tanstack/react-virtualimport { useVirtualizer } from "@tanstack/react-virtual";
function Component() {
// The scrollable element for your list
const parentRef = React.useRef(null);
// The virtualizer
const rowVirtualizer = useVirtualizer({
count: 10000, // total number of items
getScrollElement: () => parentRef.current, // reference to the container of the list
estimateSize: () => 35, // approximate size of each individual item in the list
horizontal: false, // decides the direction of the list. For horizontal lists, width and X transformation will be used in the following code
});
return (
<>
{/* The scrollable element for your list */}
<div
ref={parentRef}
style={{
height: `400px`,
overflow: "auto", // Make it scroll!
}}
>
{/* The large inner element to hold all of the items */}
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
{/* Only the visible items in the virtualizer, manually positioned to be in view */}
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
Row {virtualItem.index}
</div>
))}
</div>
</div>
</>
);
}import React from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
function Component({ rows }: { rows: Array<number> }) {
const parentRef = React.useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current, // reference to the container
estimateSize: (i) => rowsHeigh[i], // predefined row heights for the ith element
overscan: 5, // the number of items to render above and below the visible area
});
return (
<>
<div
ref={parentRef}
className="List"
style={{
height: `200px`,
width: `400px`,
overflow: "auto",
}}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.index}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${rows[virtualRow.index]}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
Row {virtualRow.index}
</div>
))}
</div>
</div>
</>
);
}import React from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
function Component({ rows }: { rows: Array<number> }) {
const parentRef = React.useRef < HTMLDivElement > null;
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current, // reference to the container
estimateSize: (i) => rowsHeigh[i], // predefined row heights for ith element
overscan: 5,
});
return (
<>
<div
ref={parentRef}
className="List"
style={{
height: `200px`,
width: `400px`,
overflow: "auto",
}}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
transform: `translateY(${rowVirtualizer[0]?.start ?? 0}px)`,
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow, index) => (
<div key={virtualRow.index} data-index={index}>
Row {virtualRow.index}
</div>
))}
</div>
</div>
</div>
</>
);
}import * as React from "react";
import * as ReactDOM from "react-dom/client";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
function Example() {
const listRef = React.useRef<HTMLDivElement | null>(null);
const virtualizer = useWindowVirtualizer({
count: 10000,
estimateSize: () => 35,
overscan: 5,
scrollMargin: listRef.current?.offsetTop ?? 0, // this value represents the space between the beginning of the scrolling element and the start of the list.
});
return (
<>
<div ref={listRef} className="List">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
{virtualizer.getVirtualItems().map((item) => (
<div
key={item.key}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${item.size}px`,
transform: `translateY(${
item.start - virtualizer.options.scrollMargin
}px)`,
}}
>
Row {item.index}
</div>
))}
</div>
</div>
</>
);
}TanStack Pacer is a library focused on providing high-quality utilities for controlling function execution timing in your applications.
-
To install:
npm install @tanstack/react-pacer npm -D install @tanstack/react-devtools @tanstack/react-pacer-devtools
-
Add DevTools to top level parent:
import { TanStackDevtools } from "@tanstack/react-devtools";
import { pacerDevtoolsPlugin } from "@tanstack/react-pacer-devtools";
function App() {
return (
<div>
{/* Your app content */}
<TanStackDevtools
eventBusConfig={{
debug: false,
}}
plugins={[pacerDevtoolsPlugin()]}
/>
</div>
);
}Debouncer executes a function after a period of inactivity while rejecting other calls during activity. It comes in both sync and async versions.
import { useDebouncedCallback } from '@tanstack/pacer'
// Debounce a search handler
const handleSearch = useDebouncedCallback((query: string) => {
fetchSearchResults(query); // function to debounce
}, {
wait: 500 // Wait 500ms between executions
}); // creates a debounced version of a callback function
// Use in an input
<input
type="search"
onChange={(e) => handleSearch(e.target.value)}
/>To get more control, use useDebouncer hook:
import { useDebouncer } from '@tanstack/pacer'
// Debounce a search handler
const handleSearch = useDebouncer((query: string) => {
fetchSearchResults(query); // function to debounce
}, {
wait: 500 // Wait 500ms between executions
}); // creates a debouncer object
handleSearch.cancel() // Cancel pending execution
handleSearch.flush() // Flush pending execution immediately
handleSearch.setOptions({ wait: 1000 }) // Increase wait time
// Use in an input
<input
type="search"
onChange={(e) => handleSearch.maybeExecute(e.target.value)}
/>Throttler executes a function at regular intervals while rejecting all but one call during each interval.
import { useThrottledState } from '@tanstack/pacer'
// throttle a state update
const [
value, // throttled state value
setValue, // throttled setter function that respects the configured wait time
throttler // throttler instance for additional control
] = useThrottledState(initialState,
{
wait: 500 // Wait 500ms between state updates
leading: true, // Update immediately on first change
trailing: false // Skip trailing edge updates
},
(state) => ({
isPending: state.isPending,
})
); // creates a throttled state value that updates at most once within a specified time window
// ...
// Access throttler methods if needed
const handleReset = () => {
setValue(0);
throttler.cancel(); // Cancel any pending updates
};
// Access the selected throttler state (will be empty object {} unless selector provided)
const { isPending } = throttler.state;If updating the value is the only required function, useThrottledValue can be used.
Rate Limiter prevents a function from being called too frequently while rejects calls when the limit is reached.
// Rate limit API calls to maximum 5 calls per minute with a sliding window
const makeApiCall = useRateLimitedCallback(
async (data: ApiData) => {
return await fetch('/api/endpoint', { method: 'POST', body: JSON.stringify(data) });
},
{
limit: 5, // maximum number of executions allowed within the time window.
window: 60000, // 1 minute time window
windowType: 'sliding', // allows executions as old ones expire; `fixed` resets after the window period
onReject: () => {
console.warn('API rate limit reached. Please wait before trying again.');
}
}
); // creates a rate-limited version of a callback functionQueuer processes all calls to a function in order while only rejects calls if the queue is full.
import { queue } from '@tanstack/pacer'
// Create a queue that processes items every second
const processItems = queue<number>(
(item: number) => {
// Process each item
console.log('Processing:', item)
},
{
wait: 1000, // Wait 1 second between processing items
maxSize: 10, // Optional: limit queue size to prevent memory or time issues. If not specified, queue will not get full
onItemsChange: (queuer) => {
console.log('Current queue:', queuer.peekAllItems())
} // callback fired whenever an item is added or removed from the queuer
}
)
// Add items to be processed
processItems(1) // Processed immediately
processItems(2) // Processed after 1 second
processItems(3) // Processed after 2 secondsBatcher groups multiple function calls into a single batch with No rejections. Unlike Queuing, which ensures every operation is processed individually, batching collects items and processes them in configurable groups, improving efficiency and reducing overhead.
Batching can be triggered by:
- Reaching a maximum batch size
- Waiting a maximum amount of time
- Custom logic (e.g., a special item or condition)
import { batch } from '@tanstack/pacer'
// Create a batcher that processes up to 3 items or every 2 seconds
const processBatch = batch<number>(
(items) => {
// Process the batch
console.log('Processing batch:', items)
},
{
maxSize: 3, // Process when 3 items are collected
wait: 2000, // Or after 2 seconds, whichever comes first
onItemsChange: (batcher) => {
console.log('Current batch:', batcher.peekAllItems())
}
}
)
// Add items to be batched
processBatch(1)
processBatch(2)
processBatch(3) // Triggers batch processing
processBatch(4)
// Or wait 2 seconds for the next batch to process