import { createRoot, useLayoutEffect } from 'octane'
import { TanStackDevtoolsCore } from '@tanstack/devtools'
import { pacerDevtoolsPlugin } from '@tanstack/pacer-devtools'
import { useState } from 'octane'
import { useAsyncBatcher } from '@tanstack/octane-pacer/async-batcher'
import { PacerProvider } from '@tanstack/octane-pacer/provider'
const fakeProcessingTime = 1000
type Item = {
id: number
value: string
timestamp: number
}
function App() @{
const [processedBatches, setProcessedBatches] = useState<
Array<{ items: Array<Item>; result: string; timestamp: number }>
>([])
const [errors, setErrors] = useState<Array<string>>([])
async function processBatch(items: Array<Item>): Promise<string> {
console.log('Processing batch of', items.length, 'items:', items)
await new Promise((resolve) => setTimeout(resolve, fakeProcessingTime))
const result = `Processed ${items.length} items: ${items.map((item) => item.value).join(', ')}`
setProcessedBatches((prev) => [
...prev,
{ items, result, timestamp: Date.now() },
])
return result
}
const asyncBatcher = useAsyncBatcher(
processBatch,
{
key: 'useAsyncBatcher',
maxSize: 5,
wait: 4000,
getShouldExecute: (items) =>
items.some((item) => item.value.includes('urgent')),
throwOnError: false,
onSuccess: (result, batch, batcher) => {
console.log('Batch succeeded:', result)
console.log('Processed batch:', batch)
console.log(
'Total successful batches:',
batcher.store.state.successCount,
)
},
onError: (error: any, _batcher) => {
console.error('Batch failed:', error)
setErrors((prev) => [
...prev,
`Error: ${error} (${new Date().toLocaleTimeString()})`,
])
},
onSettled: (batch, batcher) => {
console.log('Batch settled:', batch)
console.log(
'Total processed items:',
batcher.store.state.totalItemsProcessed,
)
},
},
(state) => state,
)
const addItem = (isUrgent = false) => {
const nextId = Date.now()
const item: Item = {
id: nextId,
value: isUrgent ? `urgent-${nextId}` : `item-${nextId}`,
timestamp: nextId,
}
asyncBatcher.addItem(item)
}
const executeCurrentBatch = async () => {
try {
const result = await asyncBatcher.flush()
console.log('Manual execution result:', result)
} catch (error) {
console.error('Manual execution failed:', error)
}
}
<div>
<h1>TanStack Pacer useAsyncBatcher Example</h1>
<div>
<h3>Batch Status</h3>
<div>Current Batch Size: {asyncBatcher.state.size}</div>
<div>Max Batch Size: 5</div>
<div>Is Executing: {asyncBatcher.state.isExecuting ? 'Yes' : 'No'}</div>
<div>Status: {asyncBatcher.state.status}</div>
<div>Successful Batches: {asyncBatcher.state.successCount}</div>
<div>Failed Batches: {asyncBatcher.state.errorCount}</div>
<div>Total Items Processed: {asyncBatcher.state.totalItemsProcessed}</div>
</div>
<div>
<h3>Current Batch Items</h3>
<div style={{ minHeight: '100px' }}>
@if (asyncBatcher.state.items.length === 0) {
<em>No items in current batch</em>
} @else {
@for (
const item of asyncBatcher.state.items;
index index;
key item.id
) {
<div key={item.id}>
{index + 1}: {item.value} (added at{' '}
{new Date(item.timestamp).toLocaleTimeString()})
</div>
}
}
</div>
</div>
<div>
<h3>Controls</h3>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '8px',
maxWidth: '600px',
}}
>
<button onClick={() => addItem(false)}>Add Regular Item</button>
<button onClick={() => addItem(true)}>
Add Urgent Item (Processes Immediately)
</button>
<>
<button
disabled={
asyncBatcher.state.size === 0 || asyncBatcher.state.isExecuting
}
onClick={executeCurrentBatch}
>
Process Current Batch Now
</button>
<button
onClick={() => asyncBatcher.clear()}
disabled={
asyncBatcher.state.size === 0 || asyncBatcher.state.isExecuting
}
>
Clear Current Batch
</button>
</>
</div>
</div>
<div>
<h3>Processed Batches ({processedBatches.length})</h3>
<div>
@if (processedBatches.length === 0) {
<em>No batches processed yet</em>
} @else {
@for (
const batch of processedBatches;
index index;
key batch.timestamp
) {
<div key={batch.timestamp}>
<strong>Batch {index + 1}</strong> (processed at{' '}
{new Date(batch.timestamp).toLocaleTimeString()})
<div>{batch.result}</div>
</div>
}
}
</div>
</div>
@if (errors.length > 0) {
<div>
<h3>Errors ({errors.length})</h3>
<div>
@for (const error of errors; index index; key index) {
<div key={index}>{error}</div>
}
</div>
<button onClick={() => setErrors([])}>Clear Errors</button>
</div>
}
<pre style={{ marginTop: '20px' }}>
{JSON.stringify(asyncBatcher.state, null, 2)}
</pre>
</div>
}
function PacerExample() @{
useLayoutEffect(() => {
if (!import.meta.env.DEV) return
const target = document.createElement('div')
document.body.append(target)
const host = new TanStackDevtoolsCore({ plugins: [pacerDevtoolsPlugin()] })
host.mount(target)
return () => {
host.unmount()
target.remove()
}
}, [])
<div
onClick={(event) => event.stopPropagation()}
onInput={(event) => event.stopPropagation()}
>
<PacerProvider
>
<App />
</PacerProvider>
</div>
}
createRoot(document.getElementById('app')!).render(PacerExample)