@codyswann/lisa 2.111.0 → 2.113.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdk/package-lisa/package.lisa.json +1 -0
- package/dist/core/lisa.d.ts +42 -0
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +67 -0
- package/dist/core/lisa.js.map +1 -1
- package/expo/package-lisa/package.lisa.json +1 -0
- package/nestjs/package-lisa/package.lisa.json +1 -0
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.mcp.json +3 -3
- package/plugins/lisa-expo/THIRD-PARTY-NOTICES.md +57 -0
- package/plugins/lisa-expo/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/lisa-expo/skills/add-app-clip/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/lisa-expo/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/lisa-expo/skills/building-native-ui/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/media.md +198 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/search.md +248 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/lisa-expo/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/lisa-expo/skills/eas-update-insights/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/lisa-expo/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/lisa-expo/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/lisa-expo/skills/expo-api-routes/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/lisa-expo/skills/expo-brownfield/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/lisa-expo/skills/expo-deployment/SKILL.md +190 -0
- package/plugins/lisa-expo/skills/expo-deployment/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/play-store.md +246 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/lisa-expo/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/lisa-expo/skills/expo-dev-client/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-module/SKILL.md +141 -0
- package/plugins/lisa-expo/skills/expo-module/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/lisa-expo/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/lisa-expo/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/lisa-expo/skills/expo-module/references/module-config.md +48 -0
- package/plugins/lisa-expo/skills/expo-module/references/native-module.md +286 -0
- package/plugins/lisa-expo/skills/expo-module/references/native-view.md +171 -0
- package/plugins/lisa-expo/skills/expo-tailwind-setup/SKILL.md +480 -0
- package/plugins/lisa-expo/skills/expo-tailwind-setup/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/lisa-expo/skills/expo-ui-jetpack-compose/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/lisa-expo/skills/expo-ui-swift-ui/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/lisa-expo/skills/native-data-fetching/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/lisa-expo/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/lisa-expo/skills/upgrading-expo/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/lisa-expo/skills/use-dom/SKILL.md +417 -0
- package/plugins/lisa-expo/skills/use-dom/agents/openai.yaml +4 -0
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/src/expo/.mcp.json +3 -3
- package/plugins/src/expo/THIRD-PARTY-NOTICES.md +57 -0
- package/plugins/src/expo/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/src/expo/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/src/expo/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/src/expo/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/src/expo/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/src/expo/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/src/expo/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/src/expo/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/src/expo/skills/building-native-ui/references/media.md +198 -0
- package/plugins/src/expo/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/src/expo/skills/building-native-ui/references/search.md +248 -0
- package/plugins/src/expo/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/src/expo/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/src/expo/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/src/expo/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/src/expo/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/src/expo/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/src/expo/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/src/expo/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/src/expo/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/src/expo/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/src/expo/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/src/expo/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/src/expo/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/src/expo/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/src/expo/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/src/expo/skills/expo-deployment/SKILL.md +190 -0
- package/plugins/src/expo/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/src/expo/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/src/expo/skills/expo-deployment/references/play-store.md +246 -0
- package/plugins/src/expo/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/src/expo/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/src/expo/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/src/expo/skills/expo-module/SKILL.md +141 -0
- package/plugins/src/expo/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/src/expo/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/src/expo/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/src/expo/skills/expo-module/references/module-config.md +48 -0
- package/plugins/src/expo/skills/expo-module/references/native-module.md +286 -0
- package/plugins/src/expo/skills/expo-module/references/native-view.md +171 -0
- package/plugins/src/expo/skills/expo-tailwind-setup/SKILL.md +480 -0
- package/plugins/src/expo/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/src/expo/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/src/expo/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/src/expo/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/src/expo/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/src/expo/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/src/expo/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/src/expo/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/src/expo/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/src/expo/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/src/expo/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/src/expo/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/src/expo/skills/use-dom/SKILL.md +417 -0
- package/scripts/generate-codex-plugin-artifacts.mjs +7 -2
- package/scripts/install-claude-plugins.sh +23 -7
- package/typescript/package-lisa/package.lisa.json +2 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Expo Router Data Loaders
|
|
2
|
+
|
|
3
|
+
Route-level data loading for web apps using Expo SDK 55+. Loaders are async functions exported from route files that load data before the route renders, following the Remix/React Router loader model.
|
|
4
|
+
|
|
5
|
+
**Dual execution model:**
|
|
6
|
+
|
|
7
|
+
- **Initial page load (SSR):** The loader runs server-side. Its return value is serialized as JSON and embedded in the HTML response.
|
|
8
|
+
- **Client-side navigation:** The browser fetches the loader data from the server via HTTP. The route renders once the data arrives.
|
|
9
|
+
|
|
10
|
+
You write one function and the framework manages when and how it executes.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
**Requirements:** Expo SDK 55+, web output mode (`npx expo serve` or `npx expo export --platform web`) set in `app.json` or `app.config.js`.
|
|
15
|
+
|
|
16
|
+
**Server rendering:**
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"expo": {
|
|
21
|
+
"web": {
|
|
22
|
+
"output": "server"
|
|
23
|
+
},
|
|
24
|
+
"plugins": [
|
|
25
|
+
["expo-router", {
|
|
26
|
+
"unstable_useServerDataLoaders": true,
|
|
27
|
+
"unstable_useServerRendering": true
|
|
28
|
+
}]
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Static/SSG:**
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"expo": {
|
|
39
|
+
"web": {
|
|
40
|
+
"output": "static"
|
|
41
|
+
},
|
|
42
|
+
"plugins": [
|
|
43
|
+
["expo-router", {
|
|
44
|
+
"unstable_useServerDataLoaders": true
|
|
45
|
+
}]
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| | `"server"` | `"static"` |
|
|
52
|
+
|---|-----------|------------|
|
|
53
|
+
| `unstable_useServerDataLoaders` | Required | Required |
|
|
54
|
+
| `unstable_useServerRendering` | Required | Not required |
|
|
55
|
+
| Loader runs on | Live server (every request) | Build time (static generation) |
|
|
56
|
+
| `request` object | Full access (headers, cookies) | Not available |
|
|
57
|
+
| Hosting | Node.js server (EAS Hosting) | Any static host (Netlify, Vercel, S3) |
|
|
58
|
+
|
|
59
|
+
## Imports
|
|
60
|
+
|
|
61
|
+
Loaders use two packages:
|
|
62
|
+
|
|
63
|
+
- **`expo-router`** — `useLoaderData` hook
|
|
64
|
+
- **`expo-server`** — `LoaderFunction` type, `StatusError`, `setResponseHeaders`. Always available (dependency of `expo-router`), no install needed.
|
|
65
|
+
|
|
66
|
+
## Basic Loader
|
|
67
|
+
|
|
68
|
+
For loaders without params, a plain async function works:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// app/posts/index.tsx
|
|
72
|
+
import { Suspense } from "react";
|
|
73
|
+
import { useLoaderData } from "expo-router";
|
|
74
|
+
import { ActivityIndicator, View, Text } from "react-native";
|
|
75
|
+
|
|
76
|
+
export async function loader() {
|
|
77
|
+
const response = await fetch("https://api.example.com/posts");
|
|
78
|
+
const posts = await response.json();
|
|
79
|
+
return { posts };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function PostList() {
|
|
83
|
+
const { posts } = useLoaderData<typeof loader>();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<View>
|
|
87
|
+
{posts.map((post) => (
|
|
88
|
+
<Text key={post.id}>{post.title}</Text>
|
|
89
|
+
))}
|
|
90
|
+
</View>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default function Posts() {
|
|
95
|
+
return (
|
|
96
|
+
<Suspense fallback={<ActivityIndicator size="large" />}>
|
|
97
|
+
<PostList />
|
|
98
|
+
</Suspense>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`useLoaderData` is typed via `typeof loader` — the generic parameter infers the return type.
|
|
104
|
+
|
|
105
|
+
## Dynamic Routes
|
|
106
|
+
|
|
107
|
+
For loaders with params, use the `LoaderFunction<T>` type from `expo-server`. The first argument is the request (an immutable `Request`-like object, or `undefined` in static mode). The second is `params` (`Record<string, string | string[]>`), which contains **path parameters only**. Access individual params with a cast like `params.id as string`. For query parameters, use `new URL(request.url).searchParams`:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// app/posts/[id].tsx
|
|
111
|
+
import { Suspense } from "react";
|
|
112
|
+
import { useLoaderData } from "expo-router";
|
|
113
|
+
import { StatusError, type LoaderFunction } from "expo-server";
|
|
114
|
+
import { ActivityIndicator, View, Text } from "react-native";
|
|
115
|
+
|
|
116
|
+
type Post = {
|
|
117
|
+
id: number;
|
|
118
|
+
title: string;
|
|
119
|
+
body: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const loader: LoaderFunction<{ post: Post }> = async (
|
|
123
|
+
request,
|
|
124
|
+
params,
|
|
125
|
+
) => {
|
|
126
|
+
const id = params.id as string;
|
|
127
|
+
const response = await fetch(`https://api.example.com/posts/${id}`);
|
|
128
|
+
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw new StatusError(404, `Post ${id} not found`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const post: Post = await response.json();
|
|
134
|
+
return { post };
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function PostContent() {
|
|
138
|
+
const { post } = useLoaderData<typeof loader>();
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<View>
|
|
142
|
+
<Text>{post.title}</Text>
|
|
143
|
+
<Text>{post.body}</Text>
|
|
144
|
+
</View>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default function PostDetail() {
|
|
149
|
+
return (
|
|
150
|
+
<Suspense fallback={<ActivityIndicator size="large" />}>
|
|
151
|
+
<PostContent />
|
|
152
|
+
</Suspense>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Catch-all routes access `params.slug` the same way:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// app/docs/[...slug].tsx
|
|
161
|
+
import { type LoaderFunction } from "expo-server";
|
|
162
|
+
|
|
163
|
+
type Doc = { title: string; content: string };
|
|
164
|
+
|
|
165
|
+
export const loader: LoaderFunction<{ doc: Doc }> = async (request, params) => {
|
|
166
|
+
const slug = params.slug as string[];
|
|
167
|
+
const path = slug.join("/");
|
|
168
|
+
const doc = await fetchDoc(path);
|
|
169
|
+
return { doc };
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Query parameters are available via the `request` object (server output mode only):
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
// app/search.tsx
|
|
177
|
+
import { type LoaderFunction } from "expo-server";
|
|
178
|
+
|
|
179
|
+
export const loader: LoaderFunction<{ results: any[]; query: string }> = async (request) => {
|
|
180
|
+
if (!request) {
|
|
181
|
+
return { results: [], query: "" };
|
|
182
|
+
}
|
|
183
|
+
// Assuming request.url is `/search?q=expo&page=2`
|
|
184
|
+
const url = new URL(request.url);
|
|
185
|
+
const query = url.searchParams.get("q") ?? "";
|
|
186
|
+
const page = Number(url.searchParams.get("page") ?? "1");
|
|
187
|
+
|
|
188
|
+
const results = await fetchSearchResults(query, page);
|
|
189
|
+
return { results, query };
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Server-Side Secrets & Request Access
|
|
194
|
+
|
|
195
|
+
Loaders run on the server, so you can access secrets and server-only resources directly:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// app/dashboard.tsx
|
|
199
|
+
import { type LoaderFunction } from "expo-server";
|
|
200
|
+
|
|
201
|
+
export const loader: LoaderFunction<{ balance: any; isAuthenticated: boolean }> = async (
|
|
202
|
+
request,
|
|
203
|
+
params,
|
|
204
|
+
) => {
|
|
205
|
+
const data = await fetch("https://api.stripe.com/v1/balance", {
|
|
206
|
+
headers: {
|
|
207
|
+
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const sessionToken = request?.headers.get("cookie")?.match(/session=([^;]+)/)?.[1];
|
|
212
|
+
|
|
213
|
+
const balance = await data.json();
|
|
214
|
+
return { balance, isAuthenticated: !!sessionToken };
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The `request` object is available in server output mode. In static output mode, `request` is always `undefined`.
|
|
219
|
+
|
|
220
|
+
## Response Utilities
|
|
221
|
+
|
|
222
|
+
### Setting Response Headers
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
// app/products.tsx
|
|
226
|
+
import { setResponseHeaders } from "expo-server";
|
|
227
|
+
|
|
228
|
+
export async function loader() {
|
|
229
|
+
setResponseHeaders({
|
|
230
|
+
"Cache-Control": "public, max-age=300",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const products = await fetchProducts();
|
|
234
|
+
return { products };
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Throwing HTTP Errors
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
// app/products/[id].tsx
|
|
242
|
+
import { StatusError, type LoaderFunction } from "expo-server";
|
|
243
|
+
|
|
244
|
+
export const loader: LoaderFunction<{ product: Product }> = async (request, params) => {
|
|
245
|
+
const id = params.id as string;
|
|
246
|
+
const product = await fetchProduct(id);
|
|
247
|
+
|
|
248
|
+
if (!product) {
|
|
249
|
+
throw new StatusError(404, "Product not found");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { product };
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Suspense & Error Boundaries
|
|
257
|
+
|
|
258
|
+
### Loading States with Suspense
|
|
259
|
+
|
|
260
|
+
`useLoaderData()` suspends during client-side navigation. Push it into a child component and wrap with `<Suspense>`:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// app/posts/index.tsx
|
|
264
|
+
import { Suspense } from "react";
|
|
265
|
+
import { useLoaderData } from "expo-router";
|
|
266
|
+
import { ActivityIndicator, View, Text } from "react-native";
|
|
267
|
+
|
|
268
|
+
export async function loader() {
|
|
269
|
+
const response = await fetch("https://api.example.com/posts");
|
|
270
|
+
return { posts: await response.json() };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function PostList() {
|
|
274
|
+
const { posts } = useLoaderData<typeof loader>();
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<View>
|
|
278
|
+
{posts.map((post) => (
|
|
279
|
+
<Text key={post.id}>{post.title}</Text>
|
|
280
|
+
))}
|
|
281
|
+
</View>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export default function Posts() {
|
|
286
|
+
return (
|
|
287
|
+
<Suspense
|
|
288
|
+
fallback={
|
|
289
|
+
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
290
|
+
<ActivityIndicator size="large" />
|
|
291
|
+
</View>
|
|
292
|
+
}
|
|
293
|
+
>
|
|
294
|
+
<PostList />
|
|
295
|
+
</Suspense>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The `<Suspense>` boundary must be above the component calling `useLoaderData()`. On initial page load the data is already in the HTML, suspension only occurs during client-side navigation.
|
|
301
|
+
|
|
302
|
+
### Error Boundaries
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
// app/posts/[id].tsx
|
|
306
|
+
export function ErrorBoundary({ error }: { error: Error }) {
|
|
307
|
+
return (
|
|
308
|
+
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
309
|
+
<Text>Error: {error.message}</Text>
|
|
310
|
+
</View>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
When a loader throws (including `StatusError`), the nearest `ErrorBoundary` catches it.
|
|
316
|
+
|
|
317
|
+
## Static vs Server Rendering
|
|
318
|
+
|
|
319
|
+
| | Server (`"server"`) | Static (`"static"`) |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| **When loader runs** | Every request (live) | At build time (`npx expo export`) |
|
|
322
|
+
| **Data freshness** | Fresh on initial server request | Stale until next build |
|
|
323
|
+
| **`request` object** | Full access | Not available |
|
|
324
|
+
| **Hosting** | Node.js server (EAS Hosting) | Any static host |
|
|
325
|
+
| **Use case** | Personalized/dynamic content | Marketing pages, blogs, docs |
|
|
326
|
+
|
|
327
|
+
**Choose server** when data changes frequently or content is personalized (cookies, auth, headers).
|
|
328
|
+
|
|
329
|
+
**Choose static** when content is the same for all users and changes infrequently.
|
|
330
|
+
|
|
331
|
+
## Best Practices
|
|
332
|
+
|
|
333
|
+
- Loaders are web-only; use client-side fetching (React Query, fetch) for native
|
|
334
|
+
- Loaders cannot be used in `_layout` files — only in route files
|
|
335
|
+
- Use `LoaderFunction<T>` from `expo-server` to type loaders that use params
|
|
336
|
+
- The request object is immutable — use optional chaining (`request?.headers`) as it may be `undefined` in static mode
|
|
337
|
+
- Return only JSON-serializable values (no `Date`, `Map`, `Set`, class instances, functions)
|
|
338
|
+
- Use non-prefixed `process.env` vars for secrets in loaders, not `EXPO_PUBLIC_` (which is embedded in the client bundle)
|
|
339
|
+
- Use `StatusError` from `expo-server` for HTTP error responses
|
|
340
|
+
- Use `setResponseHeaders` from `expo-server` to set headers
|
|
341
|
+
- Export `ErrorBoundary` from route files to handle loader failures gracefully
|
|
342
|
+
- Validate and sanitize user input (params, query strings) before using in database queries or API calls
|
|
343
|
+
- Handle errors gracefully with try/catch; log server-side for debugging
|
|
344
|
+
- Loader data is currently cached for the session. This is a known limitation that will be lifted in a future release
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrading-expo
|
|
3
|
+
description: Guidelines for upgrading Expo SDK versions and fixing dependency issues
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## References
|
|
9
|
+
|
|
10
|
+
- ./references/react-19.md -- SDK +54: React 19 changes (useContext → use, Context.Provider → Context, forwardRef removal)
|
|
11
|
+
- ./references/new-architecture.md -- SDK +53: New Architecture migration guide
|
|
12
|
+
- ./references/react-compiler.md -- SDK +54: React Compiler setup and migration guide
|
|
13
|
+
- ./references/native-tabs.md -- SDK +55: Native tabs changes (Icon/Label/Badge now accessed via NativeTabs.Trigger.\*)
|
|
14
|
+
- ./references/expo-av-to-audio.md -- SDK +55: Migrate audio playback and recording from expo-av to expo-audio
|
|
15
|
+
- ./references/expo-av-to-video.md -- SDK +55: Migrate video playback from expo-av to expo-video
|
|
16
|
+
- ./references/react-navigation-to-expo-router.md -- SDK +56: Migrate `@react-navigation/*` imports to `expo-router` entry points (codemod + manual mapping)
|
|
17
|
+
|
|
18
|
+
## Beta/Preview Releases
|
|
19
|
+
|
|
20
|
+
Beta versions use `.preview` suffix (e.g., `55.0.0-preview.2`), published under `@next` tag.
|
|
21
|
+
|
|
22
|
+
Check if latest is beta: https://exp.host/--/api/v2/versions (look for `-preview` in `expoVersion`)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx expo install expo@next --fix # install beta
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Step-by-Step Upgrade Process
|
|
29
|
+
|
|
30
|
+
1. Upgrade Expo and dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx expo install expo@latest
|
|
34
|
+
npx expo install --fix
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
2. Run diagnostics: `npx expo-doctor`
|
|
38
|
+
|
|
39
|
+
3. Clear caches and reinstall
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx expo export -p ios --clear
|
|
43
|
+
rm -rf node_modules .expo
|
|
44
|
+
watchman watch-del-all
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Breaking Changes Checklist
|
|
48
|
+
|
|
49
|
+
- Check for removed APIs in release notes
|
|
50
|
+
- Update import paths for moved modules
|
|
51
|
+
- Review native module changes requiring prebuild
|
|
52
|
+
- Test all camera, audio, and video features
|
|
53
|
+
- Verify navigation still works correctly
|
|
54
|
+
|
|
55
|
+
## Prebuild for Native Changes
|
|
56
|
+
|
|
57
|
+
**First check if `ios/` and `android/` directories exist in the project.** If neither directory exists, the project uses Continuous Native Generation (CNG) and native projects are regenerated at build time — skip this section and "Clear caches for bare workflow" entirely.
|
|
58
|
+
|
|
59
|
+
If upgrading requires native changes:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx expo prebuild --clean
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This regenerates the `ios` and `android` directories. Ensure the project is not a bare workflow app before running this command.
|
|
66
|
+
|
|
67
|
+
## Clear caches for bare workflow
|
|
68
|
+
|
|
69
|
+
These steps only apply when `ios/` and/or `android/` directories exist in the project:
|
|
70
|
+
|
|
71
|
+
- Clear the cocoapods cache for iOS: `cd ios && pod install --repo-update`
|
|
72
|
+
- Clear derived data for Xcode: `npx expo run:ios --no-build-cache`
|
|
73
|
+
- Clear the Gradle cache for Android: `cd android && ./gradlew clean`
|
|
74
|
+
|
|
75
|
+
## Housekeeping
|
|
76
|
+
|
|
77
|
+
- Review release notes for the target SDK version at https://expo.dev/changelog
|
|
78
|
+
- If using Expo SDK 54 or later, ensure react-native-worklets is installed — this is required for react-native-reanimated to work.
|
|
79
|
+
- Enable React Compiler in SDK 54+ by adding `"experiments": { "reactCompiler": true }` to app.json — it's stable and recommended
|
|
80
|
+
- Delete sdkVersion from `app.json` to let Expo manage it automatically
|
|
81
|
+
- Remove implicit packages from `package.json`: `@babel/core`, `babel-preset-expo`, `expo-constants`.
|
|
82
|
+
- If the babel.config.js only contains 'babel-preset-expo', delete the file
|
|
83
|
+
- If the metro.config.js only contains expo defaults, delete the file
|
|
84
|
+
|
|
85
|
+
## Deprecated Packages
|
|
86
|
+
|
|
87
|
+
| Old Package | Replacement |
|
|
88
|
+
| -------------------- | ---------------------------------------------------- |
|
|
89
|
+
| `expo-av` | `expo-audio` and `expo-video` |
|
|
90
|
+
| `expo-permissions` | Individual package permission APIs |
|
|
91
|
+
| `@expo/vector-icons` | `expo-symbols` (for SF Symbols) |
|
|
92
|
+
| `AsyncStorage` | `expo-sqlite/localStorage/install` |
|
|
93
|
+
| `expo-app-loading` | `expo-splash-screen` |
|
|
94
|
+
| expo-linear-gradient | experimental_backgroundImage + CSS gradients in View |
|
|
95
|
+
|
|
96
|
+
When migrating deprecated packages, update all code usage before removing the old package. For expo-av, consult the migration references to convert Audio.Sound to useAudioPlayer, Audio.Recording to useAudioRecorder, and Video components to VideoView with useVideoPlayer.
|
|
97
|
+
|
|
98
|
+
## expo.install.exclude
|
|
99
|
+
|
|
100
|
+
Check if package.json has excluded packages:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"expo": { "install": { "exclude": ["react-native-reanimated"] } }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Exclusions are often workarounds that may no longer be needed after upgrading. Review each one.
|
|
109
|
+
## Removing patches
|
|
110
|
+
|
|
111
|
+
Check if there are any outdated patches in the `patches/` directory. Remove them if they are no longer needed.
|
|
112
|
+
|
|
113
|
+
## Postcss
|
|
114
|
+
|
|
115
|
+
- `autoprefixer` isn't needed in SDK +53. Remove it from dependencies and check `postcss.config.js` or `postcss.config.mjs` to remove it from the plugins list.
|
|
116
|
+
- Use `postcss.config.mjs` in SDK +53.
|
|
117
|
+
|
|
118
|
+
## Metro
|
|
119
|
+
|
|
120
|
+
Remove redundant metro config options:
|
|
121
|
+
|
|
122
|
+
- resolver.unstable_enablePackageExports is enabled by default in SDK +53.
|
|
123
|
+
- `experimentalImportSupport` is enabled by default in SDK +54.
|
|
124
|
+
- `EXPO_USE_FAST_RESOLVER=1` is removed in SDK +54.
|
|
125
|
+
- cjs and mjs extensions are supported by default in SDK +50.
|
|
126
|
+
- Expo webpack is deprecated, migrate to [Expo Router and Metro web](https://docs.expo.dev/router/migrate/from-expo-webpack/).
|
|
127
|
+
|
|
128
|
+
## Hermes engine v1
|
|
129
|
+
|
|
130
|
+
Since SDK 55, users can opt-in to use Hermes engine v1 for improved runtime performance. This requires setting `useHermesV1: true` in the `expo-build-properties` config plugin, and may require a specific version of the `hermes-compiler` npm package. Hermes v1 will become a default in some future SDK release.
|
|
131
|
+
|
|
132
|
+
## New Architecture
|
|
133
|
+
|
|
134
|
+
The new architecture is enabled by default, the app.json field `"newArchEnabled": true` is no longer needed as it's the default. Expo Go only supports the new architecture as of SDK +53.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Migrating from expo-av to expo-audio
|
|
2
|
+
|
|
3
|
+
## Imports
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
// Before
|
|
7
|
+
import { Audio } from 'expo-av';
|
|
8
|
+
|
|
9
|
+
// After
|
|
10
|
+
import { useAudioPlayer, useAudioRecorder, RecordingPresets, AudioModule, setAudioModeAsync } from 'expo-audio';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Audio Playback
|
|
14
|
+
|
|
15
|
+
### Before (expo-av)
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const [sound, setSound] = useState<Audio.Sound>();
|
|
19
|
+
|
|
20
|
+
async function playSound() {
|
|
21
|
+
const { sound } = await Audio.Sound.createAsync(require('./audio.mp3'));
|
|
22
|
+
setSound(sound);
|
|
23
|
+
await sound.playAsync();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
return sound ? () => { sound.unloadAsync(); } : undefined;
|
|
28
|
+
}, [sound]);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### After (expo-audio)
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
const player = useAudioPlayer(require('./audio.mp3'));
|
|
35
|
+
|
|
36
|
+
// Play
|
|
37
|
+
player.play();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Audio Recording
|
|
41
|
+
|
|
42
|
+
### Before (expo-av)
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
const [recording, setRecording] = useState<Audio.Recording>();
|
|
46
|
+
|
|
47
|
+
async function startRecording() {
|
|
48
|
+
await Audio.requestPermissionsAsync();
|
|
49
|
+
await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true });
|
|
50
|
+
const { recording } = await Audio.Recording.createAsync(Audio.RecordingOptionsPresets.HIGH_QUALITY);
|
|
51
|
+
setRecording(recording);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function stopRecording() {
|
|
55
|
+
await recording?.stopAndUnloadAsync();
|
|
56
|
+
const uri = recording?.getURI();
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### After (expo-audio)
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
|
64
|
+
|
|
65
|
+
async function startRecording() {
|
|
66
|
+
await AudioModule.requestRecordingPermissionsAsync();
|
|
67
|
+
await recorder.prepareToRecordAsync();
|
|
68
|
+
recorder.record();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function stopRecording() {
|
|
72
|
+
await recorder.stop();
|
|
73
|
+
const uri = recorder.uri;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Audio Mode
|
|
78
|
+
|
|
79
|
+
### Before (expo-av)
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
await Audio.setAudioModeAsync({
|
|
83
|
+
allowsRecordingIOS: true,
|
|
84
|
+
playsInSilentModeIOS: true,
|
|
85
|
+
staysActiveInBackground: true,
|
|
86
|
+
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### After (expo-audio)
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
await setAudioModeAsync({
|
|
94
|
+
playsInSilentMode: true,
|
|
95
|
+
shouldPlayInBackground: true,
|
|
96
|
+
interruptionMode: 'doNotMix',
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Mapping
|
|
101
|
+
|
|
102
|
+
| expo-av | expo-audio |
|
|
103
|
+
|---------|------------|
|
|
104
|
+
| `Audio.Sound.createAsync()` | `useAudioPlayer(source)` |
|
|
105
|
+
| `sound.playAsync()` | `player.play()` |
|
|
106
|
+
| `sound.pauseAsync()` | `player.pause()` |
|
|
107
|
+
| `sound.setPositionAsync(ms)` | `player.seekTo(seconds)` |
|
|
108
|
+
| `sound.setVolumeAsync(vol)` | `player.volume = vol` |
|
|
109
|
+
| `sound.setRateAsync(rate)` | `player.playbackRate = rate` |
|
|
110
|
+
| `sound.setIsLoopingAsync(loop)` | `player.loop = loop` |
|
|
111
|
+
| `sound.unloadAsync()` | Automatic via hook |
|
|
112
|
+
| `playbackStatus.positionMillis` | `player.currentTime` (seconds) |
|
|
113
|
+
| `playbackStatus.durationMillis` | `player.duration` (seconds) |
|
|
114
|
+
| `playbackStatus.isPlaying` | `player.playing` |
|
|
115
|
+
| `Audio.Recording.createAsync()` | `useAudioRecorder(preset)` |
|
|
116
|
+
| `Audio.RecordingOptionsPresets.*` | `RecordingPresets.*` |
|
|
117
|
+
| `recording.stopAndUnloadAsync()` | `recorder.stop()` |
|
|
118
|
+
| `recording.getURI()` | `recorder.uri` |
|
|
119
|
+
| `Audio.requestPermissionsAsync()` | `AudioModule.requestRecordingPermissionsAsync()` |
|
|
120
|
+
|
|
121
|
+
## Key Differences
|
|
122
|
+
|
|
123
|
+
- **No auto-reset on finish**: After `play()` completes, the player stays paused at the end. To replay, call `player.seekTo(0)` then `play()`
|
|
124
|
+
- **Time in seconds**: expo-audio uses seconds, not milliseconds (matching web standards)
|
|
125
|
+
- **Immediate loading**: Audio loads immediately when the hook mounts—no explicit preloading needed
|
|
126
|
+
- **Automatic cleanup**: No need to call `unloadAsync()`, hooks handle resource cleanup on unmount
|
|
127
|
+
- **Multiple players**: Create multiple `useAudioPlayer` instances and store them—all load immediately
|
|
128
|
+
- **Direct property access**: Set volume, rate, loop directly on the player object (`player.volume = 0.5`)
|
|
129
|
+
|
|
130
|
+
## API Reference
|
|
131
|
+
|
|
132
|
+
https://docs.expo.dev/versions/latest/sdk/audio/
|