or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

animation-and-transitions.mdevent-management.mdfocus-and-accessibility.mdid-and-refs.mdindex.mdlinks-and-navigation.mdmiscellaneous-utilities.mdplatform-detection.mdprops-and-events.mdscrolling-and-layout.mdshadow-dom-support.mdstate-and-effects.mdvirtual-events-and-input.md

event-management.mddocs/

0

# Event Management

1

2

Cross-platform event handling with automatic cleanup and stable function references for React components.

3

4

## Capabilities

5

6

### useEvent Hook

7

8

Attaches event listeners to elements referenced by ref with automatic cleanup.

9

10

```typescript { .api }

11

/**

12

* Attaches event listeners to elements referenced by ref

13

* @param ref - RefObject pointing to target element

14

* @param event - Event type to listen for

15

* @param handler - Event handler function (optional)

16

* @param options - addEventListener options

17

*/

18

function useEvent<K extends keyof GlobalEventHandlersEventMap>(

19

ref: RefObject<EventTarget | null>,

20

event: K,

21

handler?: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,

22

options?: AddEventListenerOptions

23

): void;

24

```

25

26

**Usage Examples:**

27

28

```typescript

29

import { useEvent } from "@react-aria/utils";

30

import { useRef } from "react";

31

32

function InteractiveElement() {

33

const elementRef = useRef<HTMLDivElement>(null);

34

35

// Attach multiple event listeners with automatic cleanup

36

useEvent(elementRef, 'mouseenter', (e) => {

37

console.log('Mouse entered', e.target);

38

});

39

40

useEvent(elementRef, 'mouseleave', (e) => {

41

console.log('Mouse left', e.target);

42

});

43

44

useEvent(elementRef, 'keydown', (e) => {

45

if (e.key === 'Enter') {

46

console.log('Enter pressed');

47

}

48

}, { passive: false });

49

50

return (

51

<div ref={elementRef} tabIndex={0}>

52

Hover me or press Enter

53

</div>

54

);

55

}

56

57

// Conditional event listeners

58

function ConditionalEvents({ isActive }) {

59

const buttonRef = useRef<HTMLButtonElement>(null);

60

61

// Handler only attached when isActive is true

62

useEvent(

63

buttonRef,

64

'click',

65

isActive ? (e) => console.log('Button clicked') : undefined

66

);

67

68

return (

69

<button ref={buttonRef}>

70

{isActive ? 'Active Button' : 'Inactive Button'}

71

</button>

72

);

73

}

74

```

75

76

### useGlobalListeners Hook

77

78

Manages global event listeners with automatic cleanup and proper handling.

79

80

```typescript { .api }

81

/**

82

* Manages global event listeners with automatic cleanup

83

* @returns Object with methods for managing global listeners

84

*/

85

function useGlobalListeners(): GlobalListeners;

86

87

interface GlobalListeners {

88

addGlobalListener<K extends keyof DocumentEventMap>(

89

el: EventTarget,

90

type: K,

91

listener: (this: Document, ev: DocumentEventMap[K]) => any,

92

options?: AddEventListenerOptions | boolean

93

): void;

94

95

removeGlobalListener<K extends keyof DocumentEventMap>(

96

el: EventTarget,

97

type: K,

98

listener: (this: Document, ev: DocumentEventMap[K]) => any,

99

options?: EventListenerOptions | boolean

100

): void;

101

102

removeAllGlobalListeners(): void;

103

}

104

```

105

106

**Usage Examples:**

107

108

```typescript

109

import { useGlobalListeners } from "@react-aria/utils";

110

111

function GlobalEventComponent() {

112

const { addGlobalListener, removeGlobalListener } = useGlobalListeners();

113

114

useEffect(() => {

115

const handleGlobalClick = (e) => {

116

console.log('Global click detected');

117

};

118

119

const handleGlobalKeyDown = (e) => {

120

if (e.key === 'Escape') {

121

console.log('Escape pressed globally');

122

}

123

};

124

125

// Add global listeners

126

addGlobalListener(document, 'click', handleGlobalClick);

127

addGlobalListener(document, 'keydown', handleGlobalKeyDown);

128

129

// Manual cleanup (automatic cleanup happens on unmount)

130

return () => {

131

removeGlobalListener(document, 'click', handleGlobalClick);

132

removeGlobalListener(document, 'keydown', handleGlobalKeyDown);

133

};

134

}, [addGlobalListener, removeGlobalListener]);

135

136

return <div>Component with global event listeners</div>;

137

}

138

139

// Modal overlay with outside click detection

140

function ModalOverlay({ isOpen, onClose, children }) {

141

const { addGlobalListener, removeGlobalListener } = useGlobalListeners();

142

const overlayRef = useRef<HTMLDivElement>(null);

143

144

useEffect(() => {

145

if (!isOpen) return;

146

147

const handleOutsideClick = (e) => {

148

if (overlayRef.current && !overlayRef.current.contains(e.target)) {

149

onClose();

150

}

151

};

152

153

const handleEscape = (e) => {

154

if (e.key === 'Escape') {

155

onClose();

156

}

157

};

158

159

// Add listeners when modal opens

160

addGlobalListener(document, 'mousedown', handleOutsideClick);

161

addGlobalListener(document, 'keydown', handleEscape);

162

163

return () => {

164

removeGlobalListener(document, 'mousedown', handleOutsideClick);

165

removeGlobalListener(document, 'keydown', handleEscape);

166

};

167

}, [isOpen, onClose, addGlobalListener, removeGlobalListener]);

168

169

return isOpen ? (

170

<div className="modal-backdrop">

171

<div ref={overlayRef} className="modal-content">

172

{children}

173

</div>

174

</div>

175

) : null;

176

}

177

```

178

179

### useEffectEvent Hook

180

181

Creates a stable function reference that always calls the latest version, preventing unnecessary effect re-runs.

182

183

```typescript { .api }

184

/**

185

* Creates a stable function reference that always calls the latest version

186

* @param fn - Function to wrap

187

* @returns Stable function reference

188

*/

189

function useEffectEvent<T extends Function>(fn?: T): T;

190

```

191

192

**Usage Examples:**

193

194

```typescript

195

import { useEffectEvent } from "@react-aria/utils";

196

197

function SearchComponent({ query, onResults }) {

198

// Stable reference to callback that doesn't cause effect re-runs

199

const handleResults = useEffectEvent(onResults);

200

201

useEffect(() => {

202

if (!query) return;

203

204

const searchAPI = async () => {

205

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

206

const data = await results.json();

207

208

// This won't cause the effect to re-run when onResults changes

209

handleResults(data);

210

};

211

212

searchAPI();

213

}, [query]); // Only re-run when query changes, not when onResults changes

214

215

return <div>Searching for: {query}</div>;

216

}

217

218

// Event handler that accesses latest state without dependencies

219

function Timer() {

220

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

221

const [isRunning, setIsRunning] = useState(false);

222

223

// Stable reference that always accesses latest state

224

const tick = useEffectEvent(() => {

225

if (isRunning) {

226

setCount(c => c + 1);

227

}

228

});

229

230

useEffect(() => {

231

const interval = setInterval(tick, 1000);

232

return () => clearInterval(interval);

233

}, []); // No dependencies needed because tick is stable

234

235

return (

236

<div>

237

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

238

<button onClick={() => setIsRunning(!isRunning)}>

239

{isRunning ? 'Stop' : 'Start'}

240

</button>

241

</div>

242

);

243

}

244

```

245

246

### Advanced Event Management Patterns

247

248

Complex event handling scenarios with multiple listeners and cleanup:

249

250

```typescript

251

import { useGlobalListeners, useEffectEvent, useEvent } from "@react-aria/utils";

252

253

function AdvancedEventManager({ onAction, isActive }) {

254

const elementRef = useRef<HTMLDivElement>(null);

255

const { addGlobalListener, removeGlobalListener } = useGlobalListeners();

256

257

// Stable callback reference

258

const handleAction = useEffectEvent(onAction);

259

260

// Local element events

261

useEvent(elementRef, 'click', isActive ? (e) => {

262

handleAction('local-click', e);

263

} : undefined);

264

265

useEvent(elementRef, 'keydown', (e) => {

266

if (e.key === 'Enter' || e.key === ' ') {

267

e.preventDefault();

268

handleAction('local-activate', e);

269

}

270

});

271

272

// Global events with conditional handling

273

useEffect(() => {

274

if (!isActive) return;

275

276

const handleGlobalKeydown = (e) => {

277

// Handle global shortcuts

278

if (e.ctrlKey && e.key === 'k') {

279

e.preventDefault();

280

handleAction('global-search', e);

281

}

282

};

283

284

const handleGlobalWheel = (e) => {

285

// Custom scroll handling

286

if (e.deltaY > 0) {

287

handleAction('scroll-down', e);

288

} else {

289

handleAction('scroll-up', e);

290

}

291

};

292

293

addGlobalListener(document, 'keydown', handleGlobalKeydown);

294

addGlobalListener(document, 'wheel', handleGlobalWheel, { passive: true });

295

296

return () => {

297

removeGlobalListener(document, 'keydown', handleGlobalKeydown);

298

removeGlobalListener(document, 'wheel', handleGlobalWheel);

299

};

300

}, [isActive, addGlobalListener, removeGlobalListener, handleAction]);

301

302

return (

303

<div ref={elementRef} tabIndex={0}>

304

Advanced Event Manager

305

</div>

306

);

307

}

308

309

// Drag and drop with event management

310

function DragDropZone({ onDrop }) {

311

const zoneRef = useRef<HTMLDivElement>(null);

312

const { addGlobalListener, removeGlobalListener } = useGlobalListeners();

313

const [isDragging, setIsDragging] = useState(false);

314

315

const handleDrop = useEffectEvent(onDrop);

316

317

// Local drag events

318

useEvent(zoneRef, 'dragover', (e) => {

319

e.preventDefault();

320

setIsDragging(true);

321

});

322

323

useEvent(zoneRef, 'dragleave', (e) => {

324

if (!zoneRef.current?.contains(e.relatedTarget as Node)) {

325

setIsDragging(false);

326

}

327

});

328

329

useEvent(zoneRef, 'drop', (e) => {

330

e.preventDefault();

331

setIsDragging(false);

332

handleDrop(e.dataTransfer?.files);

333

});

334

335

// Global drag cleanup

336

useEffect(() => {

337

if (!isDragging) return;

338

339

const handleGlobalDragEnd = () => {

340

setIsDragging(false);

341

};

342

343

addGlobalListener(document, 'dragend', handleGlobalDragEnd);

344

return () => removeGlobalListener(document, 'dragend', handleGlobalDragEnd);

345

}, [isDragging, addGlobalListener, removeGlobalListener]);

346

347

return (

348

<div

349

ref={zoneRef}

350

className={isDragging ? 'drag-over' : ''}

351

>

352

Drop files here

353

</div>

354

);

355

}

356

```

357

358

## Types

359

360

```typescript { .api }

361

interface AddEventListenerOptions {

362

capture?: boolean;

363

once?: boolean;

364

passive?: boolean;

365

signal?: AbortSignal;

366

}

367

368

interface EventListenerOptions {

369

capture?: boolean;

370

}

371

372

interface GlobalEventHandlersEventMap {

373

click: MouseEvent;

374

keydown: KeyboardEvent;

375

keyup: KeyboardEvent;

376

mousedown: MouseEvent;

377

mouseup: MouseEvent;

378

mousemove: MouseEvent;

379

wheel: WheelEvent;

380

// ... other global event types

381

}

382

383

interface DocumentEventMap extends GlobalEventHandlersEventMap {

384

// Document-specific events

385

}

386

```