or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

callback-debouncing.mdindex.mdthrottling.mdvalue-debouncing.md

throttling.mddocs/

0

# Throttling

1

2

Throttling with `useThrottledCallback` creates functions that execute at most once per specified interval. Unlike debouncing, which delays execution until activity stops, throttling ensures regular execution during continuous activity. This is ideal for high-frequency events like scrolling, resizing, mouse movement, or animation frames.

3

4

## Capabilities

5

6

### useThrottledCallback Hook

7

8

Creates a throttled version of a callback function that executes at most once per wait interval.

9

10

```typescript { .api }

11

/**

12

* Creates a throttled function that only invokes func at most once per

13

* every wait milliseconds (or once per browser frame).

14

*

15

* @param func - The function to throttle

16

* @param wait - The number of milliseconds to throttle invocations to

17

* @param options - Optional configuration for leading/trailing execution

18

* @returns Throttled function with control methods

19

*/

20

function useThrottledCallback<T extends (...args: any) => ReturnType<T>>(

21

func: T,

22

wait: number,

23

options?: CallOptions

24

): DebouncedState<T>;

25

```

26

27

**Parameters:**

28

29

- `func: T` - The function to throttle

30

- `wait: number` - The throttle interval in milliseconds

31

- `options?: CallOptions` - Configuration object with:

32

- `leading?: boolean` - If true, invokes the function on the leading edge (default: true)

33

- `trailing?: boolean` - If true, invokes the function on the trailing edge (default: true)

34

35

**Returns:**

36

- `DebouncedState<T>` - Throttled function with control methods (`cancel`, `flush`, `isPending`)

37

38

**Usage Examples:**

39

40

```typescript

41

import React, { useState, useEffect } from 'react';

42

import { useThrottledCallback } from 'use-debounce';

43

44

// Scroll position tracking

45

function ScrollTracker() {

46

const [scrollY, setScrollY] = useState(0);

47

48

const throttledScrollHandler = useThrottledCallback(

49

() => {

50

setScrollY(window.pageYOffset);

51

},

52

100 // Update scroll position at most once every 100ms

53

);

54

55

useEffect(() => {

56

window.addEventListener('scroll', throttledScrollHandler);

57

return () => {

58

window.removeEventListener('scroll', throttledScrollHandler);

59

};

60

}, [throttledScrollHandler]);

61

62

return (

63

<div style={{ position: 'fixed', top: 0, left: 0, background: 'white' }}>

64

Scroll Y: {scrollY}px

65

</div>

66

);

67

}

68

69

// Window resize handling

70

function ResponsiveComponent() {

71

const [windowSize, setWindowSize] = useState({

72

width: typeof window !== 'undefined' ? window.innerWidth : 0,

73

height: typeof window !== 'undefined' ? window.innerHeight : 0,

74

});

75

76

const throttledResizeHandler = useThrottledCallback(

77

() => {

78

setWindowSize({

79

width: window.innerWidth,

80

height: window.innerHeight,

81

});

82

},

83

200 // Recalculate layout at most once every 200ms

84

);

85

86

useEffect(() => {

87

window.addEventListener('resize', throttledResizeHandler);

88

return () => {

89

window.removeEventListener('resize', throttledResizeHandler);

90

};

91

}, [throttledResizeHandler]);

92

93

return (

94

<div>

95

Window size: {windowSize.width} x {windowSize.height}

96

</div>

97

);

98

}

99

100

// Mouse movement tracking

101

function MouseTracker() {

102

const [mousePos, setMousePos] = useState({ x: 0, y: 0 });

103

104

const throttledMouseMove = useThrottledCallback(

105

(event: MouseEvent) => {

106

setMousePos({ x: event.clientX, y: event.clientY });

107

},

108

50 // Update mouse position at most once every 50ms

109

);

110

111

useEffect(() => {

112

const handleMouseMove = (e: MouseEvent) => throttledMouseMove(e);

113

document.addEventListener('mousemove', handleMouseMove);

114

return () => {

115

document.removeEventListener('mousemove', handleMouseMove);

116

};

117

}, [throttledMouseMove]);

118

119

return (

120

<div>

121

Mouse: ({mousePos.x}, {mousePos.y})

122

</div>

123

);

124

}

125

126

// API requests with rate limiting

127

function RateLimitedRequests() {

128

const [data, setData] = useState(null);

129

const [requestCount, setRequestCount] = useState(0);

130

131

const throttledFetch = useThrottledCallback(

132

async (endpoint: string) => {

133

setRequestCount(prev => prev + 1);

134

const response = await fetch(endpoint);

135

const result = await response.json();

136

setData(result);

137

},

138

5000 // Maximum one request every 5 seconds

139

);

140

141

return (

142

<div>

143

<button onClick={() => throttledFetch('/api/data')}>

144

Fetch Data (Throttled)

145

</button>

146

<p>Requests made: {requestCount}</p>

147

<pre>{JSON.stringify(data, null, 2)}</pre>

148

</div>

149

);

150

}

151

152

// Leading edge only (immediate execution, then throttled)

153

function ImmediateAction() {

154

const [count, setCount] = useState(0);

155

156

const throttledIncrement = useThrottledCallback(

157

() => {

158

setCount(prev => prev + 1);

159

},

160

1000,

161

{ leading: true, trailing: false }

162

);

163

164

return (

165

<div>

166

<button onClick={throttledIncrement}>

167

Increment (Immediate, then throttled)

168

</button>

169

<p>Count: {count}</p>

170

</div>

171

);

172

}

173

174

// Trailing edge only (throttled execution only)

175

function TrailingAction() {

176

const [lastAction, setLastAction] = useState('');

177

178

const throttledLog = useThrottledCallback(

179

(action: string) => {

180

setLastAction(`${action} at ${new Date().toLocaleTimeString()}`);

181

},

182

2000,

183

{ leading: false, trailing: true }

184

);

185

186

return (

187

<div>

188

<button onClick={() => throttledLog('Button clicked')}>

189

Click Me (Trailing only)

190

</button>

191

<p>Last action: {lastAction}</p>

192

</div>

193

);

194

}

195

196

// Animation frame throttling

197

function AnimationExample() {

198

const [rotation, setRotation] = useState(0);

199

200

const throttledRotate = useThrottledCallback(

201

() => {

202

setRotation(prev => (prev + 10) % 360);

203

},

204

16 // ~60fps throttling

205

);

206

207

useEffect(() => {

208

const interval = setInterval(throttledRotate, 10);

209

return () => clearInterval(interval);

210

}, [throttledRotate]);

211

212

return (

213

<div

214

style={{

215

width: 100,

216

height: 100,

217

background: 'blue',

218

transform: `rotate(${rotation}deg)`,

219

transition: 'transform 0.1s ease',

220

}}

221

/>

222

);

223

}

224

225

// Search with throttled API calls

226

function ThrottledSearch() {

227

const [query, setQuery] = useState('');

228

const [results, setResults] = useState([]);

229

const [isSearching, setIsSearching] = useState(false);

230

231

const throttledSearch = useThrottledCallback(

232

async (searchTerm: string) => {

233

if (!searchTerm.trim()) {

234

setResults([]);

235

return;

236

}

237

238

setIsSearching(true);

239

try {

240

const response = await fetch(`/api/search?q=${searchTerm}`);

241

const data = await response.json();

242

setResults(data.results);

243

} catch (error) {

244

console.error('Search failed:', error);

245

} finally {

246

setIsSearching(false);

247

}

248

},

249

800 // Maximum one search every 800ms

250

);

251

252

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {

253

const value = e.target.value;

254

setQuery(value);

255

throttledSearch(value);

256

};

257

258

return (

259

<div>

260

<input

261

type="text"

262

value={query}

263

onChange={handleInputChange}

264

placeholder="Search (throttled)..."

265

/>

266

{isSearching && <p>Searching...</p>}

267

<ul>

268

{results.map((result: any) => (

269

<li key={result.id}>{result.title}</li>

270

))}

271

</ul>

272

</div>

273

);

274

}

275

```

276

277

### Control Functions Usage

278

279

```typescript

280

function ThrottleWithControls() {

281

const [value, setValue] = useState(0);

282

283

const throttledUpdate = useThrottledCallback(

284

(newValue: number) => {

285

setValue(newValue);

286

},

287

1000

288

);

289

290

return (

291

<div>

292

<button onClick={() => throttledUpdate(value + 1)}>

293

Increment (Throttled)

294

</button>

295

<button onClick={() => throttledUpdate.cancel()}>

296

Cancel Pending

297

</button>

298

<button onClick={() => throttledUpdate.flush()}>

299

Execute Now

300

</button>

301

<p>Value: {value}</p>

302

<p>Has Pending: {throttledUpdate.isPending() ? 'Yes' : 'No'}</p>

303

</div>

304

);

305

}

306

```

307

308

## CallOptions Interface

309

310

```typescript { .api }

311

interface CallOptions {

312

/**

313

* Specify invoking on the leading edge of the timeout

314

* Default: true for useThrottledCallback

315

*/

316

leading?: boolean;

317

/**

318

* Specify invoking on the trailing edge of the timeout

319

* Default: true for useThrottledCallback

320

*/

321

trailing?: boolean;

322

}

323

```

324

325

## Throttling vs Debouncing

326

327

**Throttling** ensures a function executes at regular intervals during continuous activity:

328

- **Use case**: Scroll handlers, resize handlers, mouse movement, animation

329

- **Behavior**: Executes immediately, then at most once per interval

330

- **Example**: Update scroll position every 100ms while scrolling

331

332

**Debouncing** delays execution until activity stops:

333

- **Use case**: Search input, form validation, API calls

334

- **Behavior**: Waits for quiet period before executing

335

- **Example**: Search API call 500ms after user stops typing

336

337

```typescript

338

// Throttling - executes regularly during scrolling

339

const throttledScroll = useThrottledCallback(

340

() => console.log('Scroll position:', window.scrollY),

341

100 // Every 100ms while scrolling

342

);

343

344

// Debouncing - executes once after scrolling stops

345

const debouncedScroll = useDebouncedCallback(

346

() => console.log('Scroll ended at:', window.scrollY),

347

100 // 100ms after scrolling stops

348

);

349

```

350

351

## Common Patterns

352

353

### Performance Monitoring

354

355

```typescript

356

function PerformanceMonitor() {

357

const [fps, setFPS] = useState(0);

358

const frameCount = useRef(0);

359

const lastTime = useRef(performance.now());

360

361

const throttledFPSUpdate = useThrottledCallback(

362

() => {

363

const now = performance.now();

364

const delta = now - lastTime.current;

365

const currentFPS = Math.round((frameCount.current * 1000) / delta);

366

367

setFPS(currentFPS);

368

frameCount.current = 0;

369

lastTime.current = now;

370

},

371

1000 // Update FPS display once per second

372

);

373

374

useEffect(() => {

375

const animate = () => {

376

frameCount.current++;

377

throttledFPSUpdate();

378

requestAnimationFrame(animate);

379

};

380

381

const id = requestAnimationFrame(animate);

382

return () => cancelAnimationFrame(id);

383

}, [throttledFPSUpdate]);

384

385

return <div>FPS: {fps}</div>;

386

}

387

```

388

389

### Event Delegation with Throttling

390

391

```typescript

392

function ThrottledEventDelegation() {

393

const throttledHandler = useThrottledCallback(

394

(event: Event) => {

395

const target = event.target as HTMLElement;

396

if (target.matches('.interactive')) {

397

console.log('Interacted with:', target.textContent);

398

}

399

},

400

200

401

);

402

403

useEffect(() => {

404

document.addEventListener('click', throttledHandler);

405

return () => {

406

document.removeEventListener('click', throttledHandler);

407

};

408

}, [throttledHandler]);

409

410

return (

411

<div>

412

<div className="interactive">Button 1</div>

413

<div className="interactive">Button 2</div>

414

<div className="interactive">Button 3</div>

415

</div>

416

);

417

}

418

```

419

420

### Progressive Loading

421

422

```typescript

423

function ProgressiveLoader() {

424

const [items, setItems] = useState([]);

425

const [page, setPage] = useState(1);

426

427

const throttledLoadMore = useThrottledCallback(

428

async () => {

429

const response = await fetch(`/api/items?page=${page}`);

430

const newItems = await response.json();

431

setItems(prev => [...prev, ...newItems]);

432

setPage(prev => prev + 1);

433

},

434

2000 // Prevent rapid successive loads

435

);

436

437

const handleScroll = () => {

438

if (

439

window.innerHeight + window.scrollY >=

440

document.body.offsetHeight - 1000

441

) {

442

throttledLoadMore();

443

}

444

};

445

446

useEffect(() => {

447

window.addEventListener('scroll', handleScroll);

448

return () => window.removeEventListener('scroll', handleScroll);

449

}, []);

450

451

return (

452

<div>

453

{items.map((item: any) => (

454

<div key={item.id}>{item.title}</div>

455

))}

456

</div>

457

);

458

}

459

```