Skip to content

Commit 73caea9

Browse files
committed
Document data loading
1 parent ed3bed2 commit 73caea9

4 files changed

Lines changed: 233 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
id: data-loading
3+
title: Data loading
4+
sidebar_label: Data loading
5+
---
6+
7+
:::warning
8+
9+
Data loading is an experimental feature. The API is intentionally minimal and will evolve based on user feedback. Please provide feedback on the [GitHub discussion](https://github.com/react-navigation/react-navigation/discussions/) to help us improve it.
10+
11+
:::
12+
13+
Loaders let you define functions that get called when you navigate to a screen. This is useful for triggering data fetching before the screen finishes rendering. They are intended to work with data fetching libraries rather than replace them.
14+
15+
## How loaders work
16+
17+
Loaders are available only with [static configuration](static-configuration.md).
18+
19+
Each screen can define a loader with the [`UNSTABLE_loader`](screen.md#unstable_loader) property. On navigation to a screen, the loader is automatically called. When navigating to a screen in a nested navigator, loaders for all intermediate screens are also called in parallel.
20+
21+
Loaders are not called on the initial render or for actions that update the current route instead of navigating to a different screen, such as updating params. The screen should handle loading data in these cases if needed.
22+
23+
This is useful to implement "render-as-you-fetch" patterns, where the screen can suspend while reading data that the loader started fetching. If the screen is lazy loaded, the loader and the lazy loading of the screen run in parallel.
24+
25+
This pattern is designed to work with React's Suspense and error boundaries. You can provide a Suspense fallback and an error boundary for your screens using [`screenLayout`](navigator.md#screen-layout) or [`layout`](screen.md#layout). If the loader throws an error, it will be caught by the nearest error boundary.
26+
27+
## Adding a loader to a screen
28+
29+
To define a loader, add an `UNSTABLE_loader` function to the screen configuration:
30+
31+
```js
32+
const RootStack = createNativeStackNavigator({
33+
screens: {
34+
Profile: createNativeStackScreen({
35+
screen: ProfileScreen,
36+
UNSTABLE_loader: async ({ params }) => {
37+
await queryClient.ensureQueryData(profileQuery(params.id));
38+
},
39+
}),
40+
},
41+
});
42+
```
43+
44+
The loader receives an object with:
45+
46+
- `name` - the name of the screen being loaded.
47+
- `params` - the params for the route.
48+
49+
The loader should use a data fetching library to prefetch data for the screen. The screen can then read the same data from the data fetching library's cache.
50+
51+
## Using with data fetching libraries
52+
53+
This setup can work with any data fetching library that lets you start a request outside a component and read it from a cache in the screen. Common examples include [TanStack Query](https://tanstack.com/query/latest/docs), [SWR](https://swr.vercel.app/docs/prefetching), [Apollo Client](https://www.apollographql.com/docs/react/api/react/preloading), [Relay](https://relay.dev/docs/api-reference/load-query/), [RTK Query](https://redux-toolkit.js.org/rtk-query/usage/prefetching), [urql](https://nearform.com/open-source/urql/docs/basics/react-preact/) etc.
54+
55+
### Using with TanStack Query
56+
57+
With [TanStack Query](https://tanstack.com/query/latest/docs), the loader can call [`queryClient.ensureQueryData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientensurequerydata) to prefetch the data, and the screen can then read it with [`useSuspenseQuery`](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery):
58+
59+
```js
60+
import * as React from 'react';
61+
import { Text, View } from 'react-native';
62+
import { Button } from '@react-navigation/elements';
63+
import {
64+
createStaticNavigation,
65+
useNavigation,
66+
} from '@react-navigation/native';
67+
import {
68+
createNativeStackNavigator,
69+
createNativeStackScreen,
70+
} from '@react-navigation/native-stack';
71+
import {
72+
QueryClient,
73+
QueryClientProvider,
74+
queryOptions,
75+
useSuspenseQuery,
76+
} from '@tanstack/react-query';
77+
78+
const queryClient = new QueryClient();
79+
80+
function HomeScreen() {
81+
const navigation = useNavigation('Home');
82+
83+
return (
84+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
85+
<Button onPress={() => navigation.navigate('Profile', { id: '42' })}>
86+
Go to Profile
87+
</Button>
88+
</View>
89+
);
90+
}
91+
92+
const profileQuery = (id) =>
93+
queryOptions({
94+
queryKey: ['profile', id],
95+
async queryFn() {
96+
const response = await fetch(`https://api.example.com/users/${id}`);
97+
98+
if (!response.ok) {
99+
throw new Error('Failed to load profile');
100+
}
101+
102+
return response.json();
103+
},
104+
});
105+
106+
// codeblock-focus-start
107+
function ProfileScreen({ route }) {
108+
// highlight-next-line
109+
const { data } = useSuspenseQuery(profileQuery(route.params.id));
110+
111+
return (
112+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
113+
<Text>{data.name}</Text>
114+
</View>
115+
);
116+
}
117+
118+
const RootStack = createNativeStackNavigator({
119+
// highlight-start
120+
screenLayout: ({ children }) => (
121+
<React.Suspense fallback={<Text>Loading...</Text>}>
122+
{children}
123+
</React.Suspense>
124+
),
125+
// highlight-end
126+
screens: {
127+
Home: HomeScreen,
128+
Profile: createNativeStackScreen({
129+
screen: ProfileScreen,
130+
linking: 'profile/:id',
131+
// highlight-start
132+
UNSTABLE_loader: async ({ params }) => {
133+
await queryClient.ensureQueryData(profileQuery(params.id));
134+
},
135+
// highlight-end
136+
}),
137+
},
138+
});
139+
// codeblock-focus-end
140+
141+
const Navigation = createStaticNavigation(RootStack);
142+
143+
export default function App() {
144+
return (
145+
<QueryClientProvider client={queryClient}>
146+
<Navigation />
147+
</QueryClientProvider>
148+
);
149+
}
150+
```
151+
152+
With this setup, React Navigation calls the loader for the `Profile` screen when navigating to it. The loader starts the query, and the screen suspends while reading the data from the cache, so React Navigation shows the Suspense fallback specified in `screenLayout` until the data is ready.
153+
154+
## Running loaders manually
155+
156+
Use `UNSTABLE_getLoaderForState` to get the loader for a screen, for example when preloading data before the initial render, or when [server rendering](server-rendering.md):
157+
158+
```js
159+
import { UNSTABLE_getLoaderForState } from '@react-navigation/native';
160+
161+
const loader = UNSTABLE_getLoaderForState(RootStack, {
162+
index: 0,
163+
routes: [
164+
{
165+
name: 'Profile',
166+
params: { id: '42' },
167+
},
168+
],
169+
});
170+
171+
await loader?.();
172+
```
173+
174+
`UNSTABLE_getLoaderForState` takes the static navigation config and the navigation state, and returns a function that returns a promise resolving once all loaders for the focused route path have resolved. It returns `undefined` if no loader is found.
175+
176+
To call loaders for a given deep link, you can use [`getStateFromPath`](navigation-container.md#linkinggetstatefrompath) to parse the URL and get the navigation state, and then call `UNSTABLE_getLoaderForState` with that state. You can also combine this with [`createPathConfigForStaticNavigation`](static-configuration.md#createpathconfigforstaticnavigation) to get the automatically generated path config for the static navigation config.

versioned_docs/version-8.x/screen.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,29 @@ To specify a layout for all multiple screens, you can use `screenLayout` in a [g
384384
</TabItem>
385385
</ConfigTabs>
386386

387+
### Loaders
388+
389+
Loader functions can be used to start loading data when navigating to a screen. This is only available with [static configuration](static-configuration.md).
390+
391+
The loader receives the screen's `name` and `params`, and should return a promise:
392+
393+
```js
394+
const Stack = createNativeStackNavigator({
395+
screens: {
396+
Profile: createNativeStackScreen({
397+
screen: ProfileScreen,
398+
// highlight-start
399+
UNSTABLE_loader: async ({ params }) => {
400+
await queryClient.ensureQueryData(profileQuery(params.id));
401+
},
402+
// highlight-end
403+
}),
404+
},
405+
});
406+
```
407+
408+
See [Data loading](data-loading.md) for more details and examples.
409+
387410
### Navigation key
388411

389412
A navigation key is an optional key for this screen. This doesn't need to be unique. If the key changes, existing screens with this name will be removed (if used in a stack navigator) or reset (if used in a tab or drawer navigator).

versioned_docs/version-8.x/upgrading-from-7.x.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,18 @@ See the [`server rendering guide`](server-rendering.md) for a detailed guide and
11201120

11211121
## New features
11221122

1123+
### Navigations are now suspense compatible
1124+
1125+
This navigation state updates are now compatible with [concurrent rendering](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react) and navigation actions are wrapped in [transitions](https://react.dev/reference/react/startTransition) when possible.
1126+
1127+
Navigations using transitions:
1128+
1129+
- **Can be interrupted** - users can navigate away without waiting for the re-render to complete.
1130+
- **Avoid unwanted loading indicators** - if a navigation suspends, React can keep the currently shown UI visible instead of immediately replacing it with a fallback.
1131+
- **Wait for pending actions** - React can wait for pending transition actions to complete before showing the new screen.
1132+
1133+
User-initiated navigations using [`useNavigation`](use-navigation.md), [`useLinkProps`](use-link-props.md) or [`ref`](navigation-container.md#ref) always use transitions. Some navigations such as switching tabs in native tab navigators, the native back action in native stack, gesture-driven navigations, etc. don't use transitions. When writing a custom navigator, you can decide whether you use transitions for navigations initiated by your navigator.
1134+
11231135
### `createXScreen` let's you create screen config with proper types
11241136

11251137
One of the limitations of the static config API is that the type of `route` object can't be inferred in screen callback, listeners callback etc. This made it difficult to use route params in these callbacks.
@@ -1490,6 +1502,27 @@ This can be useful in various scenarios:
14901502

14911503
See [`retain`](stack-actions.md#retain) for more details.
14921504

1505+
### Screens can now define a loader to fetch data on navigation
1506+
1507+
Screens can now define a loader that runs when you navigate to them, so you can start fetching data before the screen renders. This makes it possible to implement "render-as-you-fetch" patterns where the screen suspends while reading data that the loader started fetching:
1508+
1509+
```js
1510+
const RootStack = createNativeStackNavigator({
1511+
screens: {
1512+
Profile: createNativeStackScreen({
1513+
screen: ProfileScreen,
1514+
UNSTABLE_loader: async ({ params }) => {
1515+
await queryClient.ensureQueryData(profileQuery(params.id));
1516+
},
1517+
}),
1518+
},
1519+
});
1520+
```
1521+
1522+
This is an experimental feature and is only available with [static configuration](static-configuration.md).
1523+
1524+
See [Data loading](data-loading.md) for more details.
1525+
14931526
### `Header` from `@react-navigation/elements` has been reworked
14941527

14951528
The `Header` component from `@react-navigation/elements` has been reworked with various improvements:

versioned_sidebars/version-8.x-sidebars.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"themes",
4141
"icons",
4242
"state-persistence",
43+
"data-loading",
4344
"web-support",
4445
"server-rendering"
4546
]

0 commit comments

Comments
 (0)