or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-entry-points.mddata-loading-hooks.mddata-response-utilities.mddocument-components.mdforms-and-navigation.mdindex.mdreact-router-integration.mdtype-definitions.md

data-loading-hooks.mddocs/

0

# Data Loading Hooks

1

2

Hooks for accessing server-loaded data, handling form submissions, and managing client-server communication in Remix applications.

3

4

## Capabilities

5

6

### useLoaderData

7

8

Access data loaded by the current route's loader function.

9

10

```typescript { .api }

11

/**

12

* Returns data loaded by the current route's loader function

13

* Data is automatically serialized and type-safe with proper TypeScript inference

14

* @returns The data returned from the route's loader function

15

*/

16

function useLoaderData<T = unknown>(): SerializeFrom<T>;

17

```

18

19

**Usage Examples:**

20

21

```typescript

22

import { useLoaderData } from "@remix-run/react";

23

import type { LoaderFunctionArgs } from "@remix-run/node";

24

25

// Loader function

26

export async function loader({ params }: LoaderFunctionArgs) {

27

const user = await getUser(params.userId);

28

const posts = await getUserPosts(params.userId);

29

30

return {

31

user,

32

posts,

33

lastLogin: new Date(),

34

};

35

}

36

37

// Component using loader data

38

export default function UserProfile() {

39

const { user, posts, lastLogin } = useLoaderData<typeof loader>();

40

41

return (

42

<div>

43

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

44

<p>Last login: {new Date(lastLogin).toLocaleDateString()}</p>

45

<ul>

46

{posts.map(post => (

47

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

48

))}

49

</ul>

50

</div>

51

);

52

}

53

```

54

55

### useActionData

56

57

Access data returned by the current route's action function after form submission.

58

59

```typescript { .api }

60

/**

61

* Returns data from the current route's action function

62

* Only available after a form submission, returns undefined otherwise

63

* @returns The data returned from the route's action function or undefined

64

*/

65

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

66

```

67

68

**Usage Examples:**

69

70

```typescript

71

import { useActionData, Form } from "@remix-run/react";

72

import type { ActionFunctionArgs } from "@remix-run/node";

73

74

// Action function

75

export async function action({ request }: ActionFunctionArgs) {

76

const formData = await request.formData();

77

const email = formData.get("email") as string;

78

79

try {

80

await subscribeToNewsletter(email);

81

return { success: true, message: "Successfully subscribed!" };

82

} catch (error) {

83

return {

84

success: false,

85

message: "Failed to subscribe",

86

errors: { email: "Invalid email address" }

87

};

88

}

89

}

90

91

// Component using action data

92

export default function Newsletter() {

93

const actionData = useActionData<typeof action>();

94

95

return (

96

<div>

97

<Form method="post">

98

<input

99

type="email"

100

name="email"

101

required

102

className={actionData?.errors?.email ? "error" : ""}

103

/>

104

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

105

{actionData?.errors?.email && (

106

<span className="error">{actionData.errors.email}</span>

107

)}

108

</Form>

109

110

{actionData?.message && (

111

<div className={actionData.success ? "success" : "error"}>

112

{actionData.message}

113

</div>

114

)}

115

</div>

116

);

117

}

118

```

119

120

### useRouteLoaderData

121

122

Access loader data from any route in the current route hierarchy by route ID.

123

124

```typescript { .api }

125

/**

126

* Returns loader data from a specific route by route ID

127

* Useful for accessing parent route data or shared data across route boundaries

128

* @param routeId - The ID of the route whose loader data to access

129

* @returns The loader data from the specified route or undefined if not found

130

*/

131

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

132

```

133

134

**Usage Examples:**

135

136

```typescript

137

import { useRouteLoaderData } from "@remix-run/react";

138

139

// In app/root.tsx

140

export async function loader() {

141

return {

142

user: await getCurrentUser(),

143

environment: process.env.NODE_ENV,

144

};

145

}

146

147

// In any child route component

148

export default function ChildRoute() {

149

// Access root loader data using route ID

150

const rootData = useRouteLoaderData<typeof loader>("root");

151

152

if (!rootData?.user) {

153

return <div>Please log in to continue</div>;

154

}

155

156

return (

157

<div>

158

<p>Welcome, {rootData.user.name}!</p>

159

<p>Environment: {rootData.environment}</p>

160

</div>

161

);

162

}

163

164

// In a nested layout accessing parent data

165

export default function DashboardLayout() {

166

// Access dashboard loader data from child routes

167

const dashboardData = useRouteLoaderData("routes/dashboard");

168

169

return (

170

<div>

171

<nav>Dashboard Navigation</nav>

172

{dashboardData && <UserStats stats={dashboardData.stats} />}

173

<Outlet />

174

</div>

175

);

176

}

177

```

178

179

### useFetcher

180

181

Create a fetcher for loading data or submitting forms without causing navigation.

182

183

```typescript { .api }

184

/**

185

* Creates a fetcher for data loading and form submission without navigation

186

* Provides a way to interact with loaders and actions programmatically

187

* @param opts - Optional fetcher configuration

188

* @returns Fetcher instance with form components and submission methods

189

*/

190

function useFetcher<T = unknown>(opts?: FetcherOptions): FetcherWithComponents<T>;

191

192

interface FetcherOptions {

193

/** Key to identify this fetcher instance for reuse */

194

key?: string;

195

}

196

197

interface FetcherWithComponents<T = unknown> {

198

/** Current state of the fetcher */

199

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

200

/** Data returned from the last successful fetch */

201

data: SerializeFrom<T> | undefined;

202

/** Form data being submitted */

203

formData: FormData | undefined;

204

/** JSON data being submitted */

205

json: any;

206

/** Text data being submitted */

207

text: string | undefined;

208

/** HTTP method of the current/last submission */

209

formMethod: string | undefined;

210

/** Action URL of the current/last submission */

211

formAction: string | undefined;

212

/** Form component for this fetcher */

213

Form: React.ComponentType<FormProps>;

214

/** Submit function for programmatic submissions */

215

submit: SubmitFunction;

216

/** Load function for programmatic data loading */

217

load: (href: string) => void;

218

}

219

```

220

221

**Usage Examples:**

222

223

```typescript

224

import { useFetcher } from "@remix-run/react";

225

226

// Simple data fetching

227

function UserSearch() {

228

const fetcher = useFetcher();

229

230

const handleSearch = (query: string) => {

231

fetcher.load(`/api/users/search?q=${encodeURIComponent(query)}`);

232

};

233

234

return (

235

<div>

236

<input

237

type="search"

238

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

239

placeholder="Search users..."

240

/>

241

242

{fetcher.state === "loading" && <div>Searching...</div>}

243

244

{fetcher.data && (

245

<ul>

246

{fetcher.data.users.map(user => (

247

<li key={user.id}>{user.name}</li>

248

))}

249

</ul>

250

)}

251

</div>

252

);

253

}

254

255

// Form submission without navigation

256

function QuickAddForm() {

257

const fetcher = useFetcher();

258

259

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

260

const isSuccess = fetcher.data?.success;

261

262

return (

263

<fetcher.Form method="post" action="/api/quick-add">

264

<input name="title" placeholder="Quick add..." required />

265

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

266

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

267

</button>

268

269

{isSuccess && <div>Added successfully!</div>}

270

</fetcher.Form>

271

);

272

}

273

274

// Programmatic form submission

275

function LikeButton({ postId }: { postId: string }) {

276

const fetcher = useFetcher();

277

278

const handleLike = () => {

279

fetcher.submit(

280

{ postId, action: "like" },

281

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

282

);

283

};

284

285

return (

286

<button

287

onClick={handleLike}

288

disabled={fetcher.state === "submitting"}

289

>

290

{fetcher.state === "submitting" ? "Liking..." : "Like"}

291

</button>

292

);

293

}

294

```

295

296

### useMatches

297

298

Access information about all matched routes in the current route hierarchy.

299

300

```typescript { .api }

301

/**

302

* Returns information about all matched routes in the current hierarchy

303

* Includes route IDs, pathnames, data, and route-specific metadata

304

* @returns Array of matched route information

305

*/

306

function useMatches(): UIMatch[];

307

308

interface UIMatch {

309

/** Unique identifier for the route */

310

id: string;

311

/** Path pattern that matched */

312

pathname: string;

313

/** Route parameters */

314

params: Params;

315

/** Data from the route's loader */

316

data: unknown;

317

/** Custom route handle data */

318

handle: RouteHandle | undefined;

319

}

320

```

321

322

**Usage Examples:**

323

324

```typescript

325

import { useMatches } from "@remix-run/react";

326

327

// Breadcrumb navigation

328

function Breadcrumbs() {

329

const matches = useMatches();

330

331

const breadcrumbs = matches.filter(match =>

332

match.handle?.breadcrumb

333

).map(match => ({

334

label: match.handle.breadcrumb(match),

335

pathname: match.pathname,

336

}));

337

338

return (

339

<nav>

340

{breadcrumbs.map((crumb, index) => (

341

<span key={crumb.pathname}>

342

{index > 0 && " > "}

343

<Link to={crumb.pathname}>{crumb.label}</Link>

344

</span>

345

))}

346

</nav>

347

);

348

}

349

350

// Page title based on route hierarchy

351

function PageTitle() {

352

const matches = useMatches();

353

354

const titles = matches

355

.map(match => match.handle?.title?.(match))

356

.filter(Boolean);

357

358

const pageTitle = titles.join(" | ");

359

360

useEffect(() => {

361

document.title = pageTitle;

362

}, [pageTitle]);

363

364

return null;

365

}

366

```

367

368

## Type Definitions

369

370

### Serialization Types

371

372

```typescript { .api }

373

/**

374

* Represents the serialized form of data returned from loaders/actions

375

* Handles Date objects, nested objects, and other non-JSON types

376

*/

377

type SerializeFrom<T> = T extends (...args: any[]) => infer R

378

? SerializeFrom<R>

379

: T extends Date

380

? string

381

: T extends object

382

? { [K in keyof T]: SerializeFrom<T[K]> }

383

: T;

384

```

385

386

### Route Handle

387

388

```typescript { .api }

389

interface RouteHandle {

390

/** Function to generate breadcrumb label */

391

breadcrumb?: (match: UIMatch) => React.ReactNode;

392

/** Function to generate page title */

393

title?: (match: UIMatch) => string;

394

/** Custom metadata for the route */

395

[key: string]: any;

396

}

397

```

398

399

### Form Props for Fetcher

400

401

```typescript { .api }

402

interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {

403

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

404

action?: string;

405

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

406

replace?: boolean;

407

preventScrollReset?: boolean;

408

onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;

409

}

410

```

411

412

## Implementation Notes

413

414

- **Type Safety**: All hooks provide full TypeScript inference when used with typed loader/action functions

415

- **Automatic Revalidation**: Data is automatically revalidated when dependencies change

416

- **Error Handling**: Hooks integrate with Remix's error boundary system for proper error handling

417

- **Concurrent Safety**: Multiple fetchers can operate simultaneously without conflicts

418

- **Memory Management**: Fetcher instances are automatically cleaned up when components unmount

419

- **SSR Compatibility**: All hooks work correctly during server-side rendering and client hydration