or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-loading-hooks.mdindex.mdnavigation-components.mdnavigation-hooks.mdroute-configuration.mdrouter-components.mdrouter-creation.mdserver-side-rendering.mdutilities.md

data-loading-hooks.mddocs/

0

# Data Loading Hooks

1

2

Hooks for accessing route data including loader data, action data, and data fetching utilities with support for revalidation and optimistic updates.

3

4

## Capabilities

5

6

### useLoaderData

7

8

Returns data from the current route's loader function.

9

10

```typescript { .api }

11

/**

12

* Hook that returns data from the current route's loader

13

* @returns Data returned by the route's loader function

14

*/

15

function useLoaderData<T = any>(): T;

16

```

17

18

**Usage Examples:**

19

20

```tsx

21

import { useLoaderData } from "react-router-dom";

22

23

// Route with loader

24

<Route

25

path="/users/:id"

26

element={<UserProfile />}

27

loader={async ({ params }) => {

28

const response = await fetch(`/api/users/${params.id}`);

29

return response.json();

30

}}

31

/>

32

33

// Component using loader data

34

function UserProfile() {

35

const user = useLoaderData<User>();

36

37

return (

38

<div>

39

<h1>{user.name}</h1>

40

<p>{user.email}</p>

41

<p>Joined: {user.joinDate}</p>

42

</div>

43

);

44

}

45

46

interface User {

47

id: string;

48

name: string;

49

email: string;

50

joinDate: string;

51

}

52

```

53

54

### useActionData

55

56

Returns data from the most recent action submission.

57

58

```typescript { .api }

59

/**

60

* Hook that returns data from the most recent action submission

61

* @returns Data returned by the route's action function, or undefined

62

*/

63

function useActionData<T = any>(): T | undefined;

64

```

65

66

**Usage Examples:**

67

68

```tsx

69

import { useActionData, Form } from "react-router-dom";

70

71

// Route with action

72

<Route

73

path="/contact"

74

element={<ContactForm />}

75

action={async ({ request }) => {

76

const formData = await request.formData();

77

try {

78

await submitContact(formData);

79

return { success: true, message: "Message sent!" };

80

} catch (error) {

81

return { success: false, error: error.message };

82

}

83

}}

84

/>

85

86

// Component using action data

87

function ContactForm() {

88

const actionData = useActionData<ActionData>();

89

90

return (

91

<div>

92

<Form method="post">

93

<input name="name" type="text" required />

94

<input name="email" type="email" required />

95

<textarea name="message" required></textarea>

96

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

97

</Form>

98

99

{actionData?.success && (

100

<p className="success">{actionData.message}</p>

101

)}

102

103

{actionData?.error && (

104

<p className="error">{actionData.error}</p>

105

)}

106

</div>

107

);

108

}

109

110

interface ActionData {

111

success: boolean;

112

message?: string;

113

error?: string;

114

}

115

```

116

117

### useRouteLoaderData

118

119

Returns loader data from a specific route by ID.

120

121

```typescript { .api }

122

/**

123

* Hook that returns loader data from a specific route by ID

124

* @param routeId - ID of the route to get data from

125

* @returns Loader data from the specified route, or undefined

126

*/

127

function useRouteLoaderData<T = any>(routeId: string): T | undefined;

128

```

129

130

**Usage Examples:**

131

132

```tsx

133

import { useRouteLoaderData } from "react-router-dom";

134

135

// Route configuration with IDs

136

const routes = [

137

{

138

id: "root",

139

path: "/",

140

element: <Root />,

141

loader: rootLoader,

142

children: [

143

{

144

id: "dashboard",

145

path: "dashboard",

146

element: <Dashboard />,

147

loader: dashboardLoader,

148

children: [

149

{

150

path: "profile",

151

element: <Profile />,

152

},

153

],

154

},

155

],

156

},

157

];

158

159

// Access parent route data from child component

160

function Profile() {

161

const rootData = useRouteLoaderData<RootData>("root");

162

const dashboardData = useRouteLoaderData<DashboardData>("dashboard");

163

164

return (

165

<div>

166

<h1>Profile for {rootData?.user.name}</h1>

167

<p>Dashboard stats: {dashboardData?.stats}</p>

168

</div>

169

);

170

}

171

```

172

173

### useFetcher

174

175

Returns a fetcher object for loading data and submitting forms without navigation.

176

177

```typescript { .api }

178

/**

179

* Hook that returns a fetcher for data loading and form submission

180

* @returns Fetcher object with load, submit, and Form components

181

*/

182

function useFetcher<T = any>(): FetcherWithComponents<T>;

183

184

interface FetcherWithComponents<T> {

185

/** Form component bound to this fetcher */

186

Form: React.ComponentType<FetcherFormProps>;

187

/** Function to submit form data */

188

submit: FetcherSubmitFunction;

189

/** Function to load data from a URL */

190

load: (href: string, opts?: { unstable_flushSync?: boolean }) => void;

191

/** Data returned from the fetcher request */

192

data: T;

193

/** Current form data being submitted */

194

formData?: FormData;

195

/** Current JSON data being submitted */

196

json?: unknown;

197

/** Current text data being submitted */

198

text?: string;

199

/** Current form action URL */

200

formAction?: string;

201

/** Current form method */

202

formMethod?: string;

203

/** Current form encoding type */

204

formEncType?: string;

205

/** Current fetcher state */

206

state: "idle" | "loading" | "submitting";

207

}

208

209

interface FetcherFormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'action' | 'method'> {

210

action?: string;

211

method?: "get" | "post" | "put" | "patch" | "delete";

212

encType?: "application/x-www-form-urlencoded" | "multipart/form-data";

213

replace?: boolean;

214

preventScrollReset?: boolean;

215

}

216

217

type FetcherSubmitFunction = (

218

target: SubmitTarget,

219

options?: FetcherSubmitOptions

220

) => void;

221

222

type SubmitTarget =

223

| HTMLFormElement

224

| FormData

225

| URLSearchParams

226

| { [name: string]: string | File | (string | File)[] };

227

228

interface FetcherSubmitOptions {

229

action?: string;

230

method?: "get" | "post" | "put" | "patch" | "delete";

231

encType?: "application/x-www-form-urlencoded" | "multipart/form-data";

232

replace?: boolean;

233

preventScrollReset?: boolean;

234

unstable_flushSync?: boolean;

235

}

236

```

237

238

**Usage Examples:**

239

240

```tsx

241

import { useFetcher } from "react-router-dom";

242

243

// Load data without navigation

244

function ProductQuickView({ productId }) {

245

const fetcher = useFetcher<Product>();

246

247

const loadProduct = () => {

248

fetcher.load(`/api/products/${productId}`);

249

};

250

251

return (

252

<div>

253

<button onClick={loadProduct}>Load Product</button>

254

255

{fetcher.state === "loading" && <p>Loading...</p>}

256

257

{fetcher.data && (

258

<div>

259

<h3>{fetcher.data.name}</h3>

260

<p>${fetcher.data.price}</p>

261

</div>

262

)}

263

</div>

264

);

265

}

266

267

// Submit form without navigation

268

function AddToCart({ productId }) {

269

const fetcher = useFetcher();

270

271

const isAdding = fetcher.state === "submitting";

272

273

return (

274

<fetcher.Form method="post" action="/api/cart">

275

<input type="hidden" name="productId" value={productId} />

276

<input type="number" name="quantity" defaultValue="1" />

277

<button type="submit" disabled={isAdding}>

278

{isAdding ? "Adding..." : "Add to Cart"}

279

</button>

280

</fetcher.Form>

281

);

282

}

283

284

// Programmatic submission

285

function QuickActions() {

286

const fetcher = useFetcher();

287

288

const likePost = (postId: string) => {

289

fetcher.submit(

290

{ postId, action: "like" },

291

{ method: "post", action: "/api/posts/like" }

292

);

293

};

294

295

return (

296

<button onClick={() => likePost("123")}>

297

Like Post

298

</button>

299

);

300

}

301

```

302

303

### useFetchers

304

305

Returns an array of all active fetchers.

306

307

```typescript { .api }

308

/**

309

* Hook that returns all active fetchers in the application

310

* @returns Array of all active fetcher objects

311

*/

312

function useFetchers(): Fetcher[];

313

314

interface Fetcher<T = any> {

315

data: T;

316

formData?: FormData;

317

json?: unknown;

318

text?: string;

319

formAction?: string;

320

formMethod?: string;

321

formEncType?: string;

322

state: "idle" | "loading" | "submitting";

323

key: string;

324

}

325

```

326

327

**Usage Example:**

328

329

```tsx

330

import { useFetchers } from "react-router-dom";

331

332

function GlobalLoadingIndicator() {

333

const fetchers = useFetchers();

334

335

const activeFetchers = fetchers.filter(

336

fetcher => fetcher.state === "loading" || fetcher.state === "submitting"

337

);

338

339

if (activeFetchers.length === 0) return null;

340

341

return (

342

<div className="loading-indicator">

343

{activeFetchers.length} active requests...

344

</div>

345

);

346

}

347

```

348

349

### useRevalidator

350

351

Returns an object for triggering data revalidation.

352

353

```typescript { .api }

354

/**

355

* Hook that returns revalidation utilities

356

* @returns Revalidator object with state and revalidate function

357

*/

358

function useRevalidator(): Revalidator;

359

360

interface Revalidator {

361

/** Trigger revalidation of all route loaders */

362

revalidate(): void;

363

/** Current revalidation state */

364

state: "idle" | "loading";

365

}

366

```

367

368

**Usage Example:**

369

370

```tsx

371

import { useRevalidator } from "react-router-dom";

372

373

function RefreshButton() {

374

const revalidator = useRevalidator();

375

376

return (

377

<button

378

onClick={() => revalidator.revalidate()}

379

disabled={revalidator.state === "loading"}

380

>

381

{revalidator.state === "loading" ? "Refreshing..." : "Refresh Data"}

382

</button>

383

);

384

}

385

```

386

387

### useNavigation

388

389

Returns the current navigation state.

390

391

```typescript { .api }

392

/**

393

* Hook that returns information about the current navigation

394

* @returns Navigation object with current navigation state

395

*/

396

function useNavigation(): Navigation;

397

398

interface Navigation {

399

/** Current navigation state */

400

state: "idle" | "loading" | "submitting";

401

/** Location being navigated to */

402

location?: Location;

403

/** Form data being submitted */

404

formData?: FormData;

405

/** JSON data being submitted */

406

json?: unknown;

407

/** Text data being submitted */

408

text?: string;

409

/** Form action URL */

410

formAction?: string;

411

/** Form method */

412

formMethod?: string;

413

/** Form encoding type */

414

formEncType?: string;

415

}

416

```

417

418

**Usage Example:**

419

420

```tsx

421

import { useNavigation } from "react-router-dom";

422

423

function GlobalLoadingBar() {

424

const navigation = useNavigation();

425

426

if (navigation.state === "idle") return null;

427

428

return (

429

<div className="loading-bar">

430

{navigation.state === "loading" && "Loading..."}

431

{navigation.state === "submitting" && "Saving..."}

432

</div>

433

);

434

}

435

```

436

437

### useAsyncValue

438

439

Hook for accessing resolved values from Await components in Suspense boundaries.

440

441

```typescript { .api }

442

/**

443

* Hook that returns the resolved value from an Await component

444

* @returns The resolved value from the nearest Await component

445

*/

446

function useAsyncValue<T = any>(): T;

447

```

448

449

**Usage Examples:**

450

451

```tsx

452

import { useAsyncValue, Await } from "react-router-dom";

453

454

// Component that displays resolved async data

455

function UserDetails() {

456

const user = useAsyncValue<User>();

457

458

return (

459

<div>

460

<h2>{user.name}</h2>

461

<p>{user.email}</p>

462

<p>Posts: {user.postCount}</p>

463

</div>

464

);

465

}

466

467

// Usage with Await component

468

function UserProfile() {

469

const data = useLoaderData<{ userPromise: Promise<User> }>();

470

471

return (

472

<div>

473

<h1>User Profile</h1>

474

<Suspense fallback={<div>Loading user...</div>}>

475

<Await resolve={data.userPromise}>

476

<UserDetails />

477

</Await>

478

</Suspense>

479

</div>

480

);

481

}

482

```

483

484

### useAsyncError

485

486

Hook for accessing errors from Await components in error boundaries.

487

488

```typescript { .api }

489

/**

490

* Hook that returns the error from an Await component

491

* @returns The error thrown by the nearest Await component

492

*/

493

function useAsyncError(): unknown;

494

```

495

496

**Usage Examples:**

497

498

```tsx

499

import { useAsyncError, Await } from "react-router-dom";

500

501

// Error boundary component for async errors

502

function AsyncErrorBoundary() {

503

const error = useAsyncError();

504

505

return (

506

<div className="error">

507

<h2>Failed to load data</h2>

508

<p>{error instanceof Error ? error.message : "Unknown error occurred"}</p>

509

<button onClick={() => window.location.reload()}>

510

Try Again

511

</button>

512

</div>

513

);

514

}

515

516

// Usage with Await component and error handling

517

function UserProfileWithError() {

518

const data = useLoaderData<{ userPromise: Promise<User> }>();

519

520

return (

521

<div>

522

<h1>User Profile</h1>

523

<Suspense fallback={<div>Loading...</div>}>

524

<Await

525

resolve={data.userPromise}

526

errorElement={<AsyncErrorBoundary />}

527

>

528

<UserDetails />

529

</Await>

530

</Suspense>

531

</div>

532

);

533

}

534

535

// Loader that returns deferred data

536

export const userLoader = async ({ params }) => {

537

// Return immediately with a promise for deferred loading

538

const userPromise = fetch(`/api/users/${params.id}`)

539

.then(res => {

540

if (!res.ok) throw new Error("Failed to load user");

541

return res.json();

542

});

543

544

return defer({ userPromise });

545

};

546

```

547

548

### Navigation Blocking

549

550

Hooks for blocking navigation conditionally.

551

552

```typescript { .api }

553

/**

554

* Hook that blocks navigation based on a condition

555

* @param when - Condition to block navigation

556

* @param message - Message to show when blocking (legacy browsers)

557

* @returns Blocker object with current blocking state

558

*/

559

function useBlocker(when: boolean | BlockerFunction): Blocker;

560

561

/**

562

* Hook that prompts user before navigation (unstable)

563

* @param when - Condition to show prompt

564

* @param message - Message to show in prompt

565

*/

566

function unstable_usePrompt(when: boolean, message?: string): void;

567

568

type BlockerFunction = (args: BlockerFunctionArgs) => boolean;

569

570

interface BlockerFunctionArgs {

571

currentLocation: Location;

572

nextLocation: Location;

573

historyAction: NavigationType;

574

}

575

576

interface Blocker {

577

state: "unblocked" | "blocked" | "proceeding";

578

proceed(): void;

579

reset(): void;

580

location?: Location;

581

}

582

```

583

584

**Usage Examples:**

585

586

```tsx

587

import { useBlocker, unstable_usePrompt } from "react-router-dom";

588

589

function UnsavedChangesForm() {

590

const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);

591

592

// Block navigation when there are unsaved changes

593

const blocker = useBlocker(hasUnsavedChanges);

594

595

// Legacy browser support

596

unstable_usePrompt(

597

hasUnsavedChanges,

598

"You have unsaved changes. Are you sure you want to leave?"

599

);

600

601

return (

602

<div>

603

<form onChange={() => setHasUnsavedChanges(true)}>

604

<input name="title" />

605

<textarea name="content" />

606

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

607

</form>

608

609

{blocker.state === "blocked" && (

610

<div className="modal">

611

<p>You have unsaved changes. Are you sure you want to leave?</p>

612

<button onClick={blocker.proceed}>Leave</button>

613

<button onClick={blocker.reset}>Stay</button>

614

</div>

615

)}

616

</div>

617

);

618

}

619

```