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

state-and-effects.mddocs/

0

# State & Effects

1

2

Custom React hooks for lifecycle management, state synchronization, and optimized effects for React applications.

3

4

## Capabilities

5

6

### Update-Only Effects

7

8

Hooks that run effects only on updates, skipping the initial mount.

9

10

```typescript { .api }

11

/**

12

* useEffect that only runs on updates (skips initial mount)

13

* @param effect - Effect callback function

14

* @param deps - Dependency array (same as useEffect)

15

*/

16

function useUpdateEffect(effect: EffectCallback, deps?: DependencyList): void;

17

18

/**

19

* useLayoutEffect that only runs on updates (skips initial mount)

20

* @param effect - Effect callback function

21

* @param deps - Dependency array (same as useLayoutEffect)

22

*/

23

function useUpdateLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;

24

25

type EffectCallback = () => void | (() => void | undefined);

26

type DependencyList = ReadonlyArray<any>;

27

```

28

29

**Usage Examples:**

30

31

```typescript

32

import { useUpdateEffect, useUpdateLayoutEffect } from "@react-aria/utils";

33

34

function ComponentWithUpdateEffects({ value, onValueChange }) {

35

const [internalValue, setInternalValue] = useState(value);

36

37

// Only run when value changes, not on initial mount

38

useUpdateEffect(() => {

39

console.log('Value changed from external source');

40

onValueChange(value);

41

}, [value, onValueChange]);

42

43

// Layout effect that skips initial mount

44

useUpdateLayoutEffect(() => {

45

// Adjust layout when internal value changes

46

const element = document.getElementById('value-display');

47

if (element) {

48

element.style.width = `${internalValue.toString().length * 10}px`;

49

}

50

}, [internalValue]);

51

52

return (

53

<div>

54

<div id="value-display">{internalValue}</div>

55

<input

56

value={internalValue}

57

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

58

/>

59

</div>

60

);

61

}

62

63

// Form validation that only runs on changes

64

function ValidatedInput({ value, onValidation, ...props }) {

65

const [isValid, setIsValid] = useState(true);

66

const [errorMessage, setErrorMessage] = useState('');

67

68

// Skip validation on initial mount, only validate on changes

69

useUpdateEffect(() => {

70

const validate = async () => {

71

try {

72

await validateValue(value);

73

setIsValid(true);

74

setErrorMessage('');

75

onValidation(true, '');

76

} catch (error) {

77

setIsValid(false);

78

setErrorMessage(error.message);

79

onValidation(false, error.message);

80

}

81

};

82

83

validate();

84

}, [value, onValidation]);

85

86

return (

87

<div>

88

<input

89

{...props}

90

value={value}

91

className={isValid ? '' : 'error'}

92

/>

93

{!isValid && <span className="error-message">{errorMessage}</span>}

94

</div>

95

);

96

}

97

```

98

99

### Cross-Platform Layout Effects

100

101

Hook providing consistent useLayoutEffect behavior across environments.

102

103

```typescript { .api }

104

/**

105

* Cross-platform useLayoutEffect (useEffect in SSR environments)

106

* Prevents hydration mismatches by using useEffect during SSR

107

*/

108

const useLayoutEffect: typeof React.useLayoutEffect;

109

```

110

111

**Usage Examples:**

112

113

```typescript

114

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

115

116

function ResponsiveComponent() {

117

const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

118

const elementRef = useRef<HTMLDivElement>(null);

119

120

// Safe to use in SSR - will use useEffect during server rendering

121

useLayoutEffect(() => {

122

if (elementRef.current) {

123

const { offsetWidth, offsetHeight } = elementRef.current;

124

setDimensions({ width: offsetWidth, height: offsetHeight });

125

}

126

}, []);

127

128

return (

129

<div ref={elementRef}>

130

Dimensions: {dimensions.width} x {dimensions.height}

131

</div>

132

);

133

}

134

135

// DOM measurement that needs to be synchronous

136

function SynchronousMeasurement({ children }) {

137

const containerRef = useRef<HTMLDivElement>(null);

138

const [containerHeight, setContainerHeight] = useState(0);

139

140

useLayoutEffect(() => {

141

if (containerRef.current) {

142

// This measurement happens synchronously before paint

143

const height = containerRef.current.scrollHeight;

144

setContainerHeight(height);

145

146

// Adjust other elements based on measurement

147

document.body.style.setProperty('--container-height', `${height}px`);

148

}

149

});

150

151

return (

152

<div ref={containerRef} style={{ minHeight: containerHeight }}>

153

{children}

154

</div>

155

);

156

}

157

```

158

159

### Value State Management

160

161

Hook for managing values that can transition through generator functions.

162

163

```typescript { .api }

164

/**

165

* Manages values that can transition through generator functions

166

* @param defaultValue - Initial value

167

* @returns Tuple of current value and setter function

168

*/

169

function useValueEffect<T>(defaultValue: T): [T, (value: T | (() => Generator<T>)) => void];

170

```

171

172

**Usage Examples:**

173

174

```typescript

175

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

176

177

function AnimatedCounter({ targetValue }) {

178

const [currentValue, setCurrentValue] = useValueEffect(0);

179

180

useEffect(() => {

181

// Use generator for smooth value transitions

182

setCurrentValue(function* () {

183

const start = currentValue;

184

const diff = targetValue - start;

185

const steps = 60; // 60 steps for smooth animation

186

187

for (let i = 0; i <= steps; i++) {

188

const progress = i / steps;

189

const easedProgress = 1 - Math.pow(1 - progress, 3); // ease-out

190

yield start + (diff * easedProgress);

191

}

192

193

return targetValue;

194

});

195

}, [targetValue, currentValue, setCurrentValue]);

196

197

return <div>Count: {Math.round(currentValue)}</div>;

198

}

199

200

// Color transition with generator

201

function ColorTransition({ targetColor }) {

202

const [currentColor, setCurrentColor] = useValueEffect({ r: 0, g: 0, b: 0 });

203

204

const transitionToColor = (newColor: { r: number; g: number; b: number }) => {

205

setCurrentColor(function* () {

206

const start = currentColor;

207

const steps = 30;

208

209

for (let i = 0; i <= steps; i++) {

210

const progress = i / steps;

211

212

yield {

213

r: Math.round(start.r + (newColor.r - start.r) * progress),

214

g: Math.round(start.g + (newColor.g - start.g) * progress),

215

b: Math.round(start.b + (newColor.b - start.b) * progress)

216

};

217

}

218

219

return newColor;

220

});

221

};

222

223

useEffect(() => {

224

transitionToColor(targetColor);

225

}, [targetColor]);

226

227

const colorString = `rgb(${currentColor.r}, ${currentColor.g}, ${currentColor.b})`;

228

229

return (

230

<div

231

style={{

232

backgroundColor: colorString,

233

width: 100,

234

height: 100,

235

transition: 'background-color 0.1s'

236

}}

237

/>

238

);

239

}

240

```

241

242

### Deep Memoization

243

244

Hook providing memoization with custom equality functions.

245

246

```typescript { .api }

247

/**

248

* Memoization with custom equality function

249

* @param value - Value to memoize

250

* @param isEqual - Function to compare values (optional)

251

* @returns Memoized value that only changes when equality check fails

252

*/

253

function useDeepMemo<T>(value: T, isEqual?: (a: T, b: T) => boolean): T;

254

```

255

256

**Usage Examples:**

257

258

```typescript

259

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

260

261

function DeepComparisonComponent({ items, options }) {

262

// Memoize complex objects with deep equality

263

const memoizedItems = useDeepMemo(items, (a, b) => {

264

if (a.length !== b.length) return false;

265

return a.every((item, index) =>

266

item.id === b[index].id && item.name === b[index].name

267

);

268

});

269

270

const memoizedOptions = useDeepMemo(options, (a, b) =>

271

JSON.stringify(a) === JSON.stringify(b)

272

);

273

274

// These will only recalculate when deep equality fails

275

const processedItems = useMemo(() =>

276

expensiveProcessing(memoizedItems)

277

, [memoizedItems]);

278

279

const computedOptions = useMemo(() =>

280

expensiveOptionProcessing(memoizedOptions)

281

, [memoizedOptions]);

282

283

return (

284

<div>

285

{processedItems.map(item => (

286

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

287

))}

288

</div>

289

);

290

}

291

292

// Custom equality for specific use cases

293

function useShallowEqual<T extends Record<string, any>>(value: T): T {

294

return useDeepMemo(value, (a, b) => {

295

const keysA = Object.keys(a);

296

const keysB = Object.keys(b);

297

298

if (keysA.length !== keysB.length) return false;

299

300

return keysA.every(key => a[key] === b[key]);

301

});

302

}

303

304

function OptimizedComponent({ config, data }) {

305

// Only re-render when top-level properties change

306

const stableConfig = useShallowEqual(config);

307

const stableData = useShallowEqual(data);

308

309

const result = useMemo(() => {

310

return processData(stableData, stableConfig);

311

}, [stableData, stableConfig]);

312

313

return <div>{result.summary}</div>;

314

}

315

```

316

317

### Form Reset Handling

318

319

Hook for handling form reset events and restoring initial values.

320

321

```typescript { .api }

322

/**

323

* Handles form reset events

324

* @param ref - RefObject to form element or form control

325

* @param initialValue - Value to reset to

326

* @param onReset - Callback when reset occurs

327

*/

328

function useFormReset<T>(

329

ref: RefObject<HTMLFormElement | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,

330

initialValue: T,

331

onReset: (value: T) => void

332

): void;

333

```

334

335

**Usage Examples:**

336

337

```typescript

338

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

339

340

function ControlledInput({ name, initialValue = '', onValueChange }) {

341

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

342

const inputRef = useRef<HTMLInputElement>(null);

343

344

// Handle form reset events

345

useFormReset(inputRef, initialValue, (resetValue) => {

346

setValue(resetValue);

347

onValueChange(resetValue);

348

});

349

350

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {

351

const newValue = e.target.value;

352

setValue(newValue);

353

onValueChange(newValue);

354

};

355

356

return (

357

<input

358

ref={inputRef}

359

name={name}

360

value={value}

361

onChange={handleChange}

362

/>

363

);

364

}

365

366

// Complex form with multiple controlled components

367

function ComplexForm() {

368

const formRef = useRef<HTMLFormElement>(null);

369

const [formData, setFormData] = useState({

370

name: '',

371

email: '',

372

preferences: { notifications: true, theme: 'light' }

373

});

374

375

const initialFormData = {

376

name: '',

377

email: '',

378

preferences: { notifications: true, theme: 'light' }

379

};

380

381

// Reset entire form state when form reset occurs

382

useFormReset(formRef, initialFormData, (resetData) => {

383

setFormData(resetData);

384

});

385

386

const updateFormData = (field: string, value: any) => {

387

setFormData(prev => ({ ...prev, [field]: value }));

388

};

389

390

return (

391

<form ref={formRef}>

392

<ControlledInput

393

name="name"

394

initialValue={initialFormData.name}

395

onValueChange={(value) => updateFormData('name', value)}

396

/>

397

398

<ControlledInput

399

name="email"

400

initialValue={initialFormData.email}

401

onValueChange={(value) => updateFormData('email', value)}

402

/>

403

404

<div>

405

<button type="submit">Submit</button>

406

<button type="reset">Reset Form</button>

407

</div>

408

</form>

409

);

410

}

411

```

412

413

### Advanced State Management Patterns

414

415

Complex scenarios combining multiple state and effect utilities:

416

417

```typescript

418

import {

419

useUpdateEffect,

420

useDeepMemo,

421

useValueEffect,

422

useFormReset

423

} from "@react-aria/utils";

424

425

function AdvancedStateManager({

426

externalData,

427

onDataChange,

428

resetTrigger

429

}) {

430

const formRef = useRef<HTMLFormElement>(null);

431

432

// Deep memo for expensive data processing

433

const processedData = useDeepMemo(externalData, (a, b) =>

434

JSON.stringify(a) === JSON.stringify(b)

435

);

436

437

// Value effect for smooth data transitions

438

const [displayData, setDisplayData] = useValueEffect(processedData);

439

440

// Update effect to sync with external changes

441

useUpdateEffect(() => {

442

// Animate to new data when external data changes

443

setDisplayData(function* () {

444

const steps = 20;

445

const start = displayData;

446

447

for (let i = 0; i <= steps; i++) {

448

const progress = i / steps;

449

// Interpolate between old and new data

450

yield interpolateData(start, processedData, progress);

451

}

452

453

return processedData;

454

});

455

}, [processedData]);

456

457

// Form reset handling

458

useFormReset(formRef, processedData, (resetData) => {

459

setDisplayData(resetData);

460

onDataChange(resetData);

461

});

462

463

return (

464

<form ref={formRef}>

465

<DataDisplay data={displayData} />

466

<button type="reset">Reset to Original</button>

467

</form>

468

);

469

}

470

471

// Performance-optimized component with multiple hooks

472

function PerformanceOptimizedComponent({

473

items,

474

filters,

475

sortOptions

476

}) {

477

// Memoize expensive computations

478

const memoizedItems = useDeepMemo(items);

479

const memoizedFilters = useDeepMemo(filters);

480

const memoizedSortOptions = useDeepMemo(sortOptions);

481

482

// Expensive filtering only when inputs actually change

483

const filteredItems = useMemo(() =>

484

filterItems(memoizedItems, memoizedFilters)

485

, [memoizedItems, memoizedFilters]);

486

487

// Sorting with smooth transitions

488

const [sortedItems, setSortedItems] = useValueEffect(filteredItems);

489

490

// Update sorted items when sort options change

491

useUpdateEffect(() => {

492

const newlySorted = sortItems(filteredItems, memoizedSortOptions);

493

494

// Animate sort changes

495

setSortedItems(function* () {

496

// Implement smooth reordering animation

497

yield* animateReorder(sortedItems, newlySorted);

498

return newlySorted;

499

});

500

}, [filteredItems, memoizedSortOptions]);

501

502

return (

503

<div>

504

{sortedItems.map(item => (

505

<ItemComponent key={item.id} item={item} />

506

))}

507

</div>

508

);

509

}

510

```

511

512

## Types

513

514

```typescript { .api }

515

type EffectCallback = () => void | (() => void | undefined);

516

type DependencyList = ReadonlyArray<any>;

517

518

interface MutableRefObject<T> {

519

current: T;

520

}

521

522

interface RefObject<T> {

523

readonly current: T | null;

524

}

525

```