or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

callback-debouncing.mddocs/

0

# Callback Debouncing

1

2

Callback debouncing with `useDebouncedCallback` creates debounced versions of functions that delay execution until after a specified wait time has passed since the last invocation. This is ideal for optimizing expensive operations like API calls, search queries, form submissions, and event handlers.

3

4

## Capabilities

5

6

### useDebouncedCallback Hook

7

8

Creates a debounced version of a callback function with comprehensive control options.

9

10

```typescript { .api }

11

/**

12

* Creates a debounced function that delays invoking func until after wait

13

* milliseconds have elapsed since the last time the debounced function was

14

* invoked, or until the next browser frame is drawn.

15

*

16

* @param func - The function to debounce

17

* @param wait - The number of milliseconds to delay (defaults to requestAnimationFrame if 0 or omitted)

18

* @param options - Optional configuration object

19

* @returns Debounced function with control methods

20

*/

21

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

22

func: T,

23

wait?: number,

24

options?: Options

25

): DebouncedState<T>;

26

```

27

28

**Parameters:**

29

30

- `func: T` - The function to debounce

31

- `wait?: number` - Wait time in milliseconds. If 0 or omitted, uses `requestAnimationFrame` in browser environments

32

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

33

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

34

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

35

- `maxWait?: number` - Maximum time the function is allowed to be delayed before it's invoked

36

- `debounceOnServer?: boolean` - If true, enables debouncing in server-side environments (default: false)

37

38

**Returns:**

39

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

40

41

**Usage Examples:**

42

43

```typescript

44

import React, { useState } from 'react';

45

import { useDebouncedCallback } from 'use-debounce';

46

47

// Basic API call debouncing

48

function SearchComponent() {

49

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

50

const [loading, setLoading] = useState(false);

51

52

const debouncedSearch = useDebouncedCallback(

53

async (searchTerm: string) => {

54

if (!searchTerm.trim()) return;

55

56

setLoading(true);

57

try {

58

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

59

const data = await response.json();

60

setResults(data.results);

61

} catch (error) {

62

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

63

} finally {

64

setLoading(false);

65

}

66

},

67

500

68

);

69

70

return (

71

<div>

72

<input

73

type="text"

74

onChange={(e) => debouncedSearch(e.target.value)}

75

placeholder="Search..."

76

/>

77

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

78

<ul>

79

{results.map((result) => (

80

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

81

))}

82

</ul>

83

</div>

84

);

85

}

86

87

// Leading edge execution

88

function QuickAction() {

89

const debouncedAction = useDebouncedCallback(

90

(action: string) => {

91

console.log('Executing:', action);

92

// Perform action immediately on first call

93

},

94

1000,

95

{ leading: true, trailing: false }

96

);

97

98

return (

99

<button onClick={() => debouncedAction('quick-save')}>

100

Quick Save

101

</button>

102

);

103

}

104

105

// With maxWait option

106

function AutoSave() {

107

const [content, setContent] = useState('');

108

109

const debouncedSave = useDebouncedCallback(

110

async (data: string) => {

111

console.log('Auto-saving...', data);

112

await fetch('/api/save', {

113

method: 'POST',

114

body: JSON.stringify({ content: data }),

115

headers: { 'Content-Type': 'application/json' }

116

});

117

},

118

2000,

119

{ maxWait: 10000 } // Force save every 10 seconds maximum

120

);

121

122

React.useEffect(() => {

123

debouncedSave(content);

124

}, [content, debouncedSave]);

125

126

return (

127

<textarea

128

value={content}

129

onChange={(e) => setContent(e.target.value)}

130

placeholder="Type here... (auto-saves)"

131

/>

132

);

133

}

134

135

// Event handling with native listeners

136

function ScrollHandler() {

137

const [position, setPosition] = useState(0);

138

139

const debouncedScrollHandler = useDebouncedCallback(

140

() => {

141

setPosition(window.pageYOffset);

142

},

143

100

144

);

145

146

React.useEffect(() => {

147

window.addEventListener('scroll', debouncedScrollHandler);

148

return () => {

149

window.removeEventListener('scroll', debouncedScrollHandler);

150

};

151

}, [debouncedScrollHandler]);

152

153

return <div>Scroll position: {position}px</div>;

154

}

155

156

// Using control functions

157

function ControlledCallback() {

158

const [message, setMessage] = useState('');

159

160

const debouncedSubmit = useDebouncedCallback(

161

(msg: string) => {

162

console.log('Submitting:', msg);

163

// Submit message

164

},

165

1000

166

);

167

168

const handleSubmit = () => {

169

debouncedSubmit(message);

170

};

171

172

const handleCancel = () => {

173

debouncedSubmit.cancel();

174

};

175

176

const handleFlush = () => {

177

debouncedSubmit.flush();

178

};

179

180

return (

181

<div>

182

<input

183

value={message}

184

onChange={(e) => setMessage(e.target.value)}

185

/>

186

<button onClick={handleSubmit}>Submit (Debounced)</button>

187

<button onClick={handleCancel}>Cancel</button>

188

<button onClick={handleFlush}>Submit Now</button>

189

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

190

</div>

191

);

192

}

193

194

// Form validation

195

function ValidatedForm() {

196

const [form, setForm] = useState({ email: '', password: '' });

197

const [errors, setErrors] = useState({});

198

199

const debouncedValidate = useDebouncedCallback(

200

(formData: typeof form) => {

201

const newErrors: any = {};

202

203

if (!formData.email.includes('@')) {

204

newErrors.email = 'Invalid email';

205

}

206

207

if (formData.password.length < 8) {

208

newErrors.password = 'Password too short';

209

}

210

211

setErrors(newErrors);

212

},

213

300

214

);

215

216

React.useEffect(() => {

217

debouncedValidate(form);

218

}, [form, debouncedValidate]);

219

220

return (

221

<form>

222

<input

223

type="email"

224

value={form.email}

225

onChange={(e) => setForm(prev => ({ ...prev, email: e.target.value }))}

226

/>

227

{errors.email && <span>{errors.email}</span>}

228

229

<input

230

type="password"

231

value={form.password}

232

onChange={(e) => setForm(prev => ({ ...prev, password: e.target.value }))}

233

/>

234

{errors.password && <span>{errors.password}</span>}

235

</form>

236

);

237

}

238

```

239

240

### Return Value Handling

241

242

The debounced function returns the result of the last invocation. If there are no previous invocations, it returns `undefined`.

243

244

```typescript

245

function ReturnValueExample() {

246

const debouncedCalculate = useDebouncedCallback(

247

(a: number, b: number) => {

248

return a + b;

249

},

250

500

251

);

252

253

const handleCalculate = () => {

254

// First call returns undefined (no previous invocation)

255

const result = debouncedCalculate(5, 3);

256

console.log('Immediate result:', result); // undefined

257

258

// After the debounce period, subsequent calls return the last result

259

setTimeout(() => {

260

const cachedResult = debouncedCalculate(10, 7);

261

console.log('Cached result:', cachedResult); // 8 (from previous call)

262

}, 1000);

263

};

264

265

return <button onClick={handleCalculate}>Calculate</button>;

266

}

267

```

268

269

### Server-Side Rendering

270

271

By default, debouncing is disabled in server-side environments. Enable it with the `debounceOnServer` option:

272

273

```typescript

274

function SSRCompatible() {

275

const debouncedCallback = useDebouncedCallback(

276

(data: string) => {

277

console.log('Processing:', data);

278

},

279

500,

280

{ debounceOnServer: true }

281

);

282

283

// This will work in both client and server environments

284

return (

285

<button onClick={() => debouncedCallback('data')}>

286

Process

287

</button>

288

);

289

}

290

```

291

292

## Options Interface

293

294

```typescript { .api }

295

interface Options extends CallOptions {

296

/** The maximum time the given function is allowed to be delayed before it's invoked */

297

maxWait?: number;

298

/** If set to true, all debouncing and timers will happen on the server side as well */

299

debounceOnServer?: boolean;

300

}

301

302

interface CallOptions {

303

/** Controls if the function should be invoked on the leading edge of the timeout */

304

leading?: boolean;

305

/** Controls if the function should be invoked on the trailing edge of the timeout */

306

trailing?: boolean;

307

}

308

```

309

310

## Common Patterns

311

312

### API Rate Limiting

313

314

```typescript

315

function RateLimitedAPI() {

316

const debouncedRequest = useDebouncedCallback(

317

async (endpoint: string, data: any) => {

318

return fetch(endpoint, {

319

method: 'POST',

320

body: JSON.stringify(data),

321

headers: { 'Content-Type': 'application/json' }

322

});

323

},

324

1000 // Limit to one request per second

325

);

326

327

return {

328

createUser: (userData: any) => debouncedRequest('/api/users', userData),

329

updateUser: (userId: string, userData: any) =>

330

debouncedRequest(`/api/users/${userId}`, userData)

331

};

332

}

333

```

334

335

### Expensive Calculations

336

337

```typescript

338

function ExpensiveCalculation() {

339

const [input, setInput] = useState('');

340

const [result, setResult] = useState('');

341

342

const debouncedCalculate = useDebouncedCallback(

343

(value: string) => {

344

// Simulate expensive calculation

345

const processed = value

346

.split('')

347

.reverse()

348

.map(char => char.charCodeAt(0))

349

.reduce((sum, code) => sum + code, 0);

350

351

setResult(`Calculated: ${processed}`);

352

},

353

500

354

);

355

356

React.useEffect(() => {

357

if (input) {

358

debouncedCalculate(input);

359

}

360

}, [input, debouncedCalculate]);

361

362

return (

363

<div>

364

<input value={input} onChange={(e) => setInput(e.target.value)} />

365

<p>{result}</p>

366

</div>

367

);

368

}

369

```

370

371

### Cleanup on Unmount

372

373

```typescript

374

function ComponentWithCleanup() {

375

const debouncedSave = useDebouncedCallback(

376

(data: string) => {

377

// Save data

378

console.log('Saving:', data);

379

},

380

1000

381

);

382

383

React.useEffect(() => {

384

// Cleanup: flush any pending saves when component unmounts

385

return () => {

386

debouncedSave.flush();

387

};

388

}, [debouncedSave]);

389

390

return (

391

<input onChange={(e) => debouncedSave(e.target.value)} />

392

);

393

}

394

```