snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
{
"root": true,
"extends": "@callstack",
"rules": {
"react-native/no-raw-text": 0,
"promise/prefer-await-to-then": 0
},
"settings": {
"import/resolver": {
"alias": {
"map": [["@app", "./src"]],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".json"]
}
}
}
}

91
examples/react/react-native/.gitignore vendored Normal file
View File

@@ -0,0 +1,91 @@
node_modules/**/*
.expo/*
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
coverage/
# macOS
.DS_Store
.vscode/*
db_data
# @generated expo-cli sync-ba457486f18e0afcb8f6494641422f0a1150e3a9
# The following patterns were generated by expo-cli
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifacts
*.jsbundle
# CocoaPods
/ios/Pods/
# Expo
.expo/*
web-build/
# @end expo-cli
!lib

View File

@@ -0,0 +1,37 @@
import * as React from 'react'
import { AppStateStatus, Platform } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import {
QueryClient,
QueryClientProvider,
focusManager,
} from '@tanstack/react-query'
import { useAppState } from './src/hooks/useAppState'
import { MoviesStack } from './src/navigation/MoviesStack'
import { useOnlineManager } from './src/hooks/useOnlineManager'
function onAppStateChange(status: AppStateStatus) {
// React Query already supports in web browser refetch on window focus by default
if (Platform.OS !== 'web') {
focusManager.setFocused(status === 'active')
}
}
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: 2 } },
})
export default function App() {
useOnlineManager()
useAppState(onAppStateChange)
return (
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<MoviesStack />
</NavigationContainer>
</QueryClientProvider>
)
}

View File

@@ -0,0 +1,7 @@
# Example
To run this example:
- Install expo CLI if needed `npm install --global expo-cli`
- `pnpm install`
- `pnpm start`

View File

@@ -0,0 +1,31 @@
{
"expo": {
"name": "react-query-basic-example",
"slug": "react-query-basic-example",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
},
"description": "React Query basic example"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true)
return {
presets: ['babel-preset-expo'],
}
}

View File

@@ -0,0 +1,36 @@
{
"name": "@tanstack/query-example-react-react-native",
"private": true,
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"type:check": "tsc --noEmit"
},
"dependencies": {
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/stack": "^6.4.1",
"@tanstack/react-query": "^5.102.8",
"@tanstack/react-query-devtools": "^5.102.8",
"expo": "^52.0.11",
"expo-constants": "^17.0.3",
"expo-status-bar": "^2.0.0",
"react": "^19.0.0",
"react-native": "^0.76.3",
"react-native-gesture-handler": "^2.20.2",
"react-native-paper": "^5.12.5",
"react-native-reanimated": "^3.16.2",
"react-native-safe-area-context": "^4.12.0",
"react-native-screens": "^4.1.0",
"react-native-web": "^0.19.13"
},
"devDependencies": {
"@babel/core": "^7.24.9",
"@expo/config": "^10.0.5",
"typescript": "5.8.3"
}
}

View File

@@ -0,0 +1,13 @@
import * as React from 'react'
import { StyleSheet } from 'react-native'
import { Divider as PaperDivider } from 'react-native-paper'
export function Divider() {
return <PaperDivider style={styles.divider} />
}
const styles = StyleSheet.create({
divider: {
marginLeft: 10,
},
})

View File

@@ -0,0 +1,22 @@
import * as React from 'react'
import { StyleSheet, Text, View } from 'react-native'
type Props = {
message: string
}
export function ErrorMessage({ message }: Props) {
return (
<View style={styles.fill}>
<Text>{message}</Text>
</View>
)
}
const styles = StyleSheet.create({
fill: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
})

View File

@@ -0,0 +1,42 @@
import * as React from 'react'
import { StyleSheet, View } from 'react-native'
import { Paragraph, TouchableRipple } from 'react-native-paper'
import type { MovieDetails } from '../lib/api'
type Props = {
item: MovieDetails
onPress: (item: MovieDetails) => void
}
export function ListItem({ item, onPress }: Props) {
return (
<TouchableRipple onPress={() => onPress(item)} accessibilityRole="button">
<View style={styles.item}>
<View style={styles.firstRow}>
<Paragraph style={styles.title}>{item.title}</Paragraph>
</View>
<View style={styles.secondRow}>
<Paragraph>{item.year}</Paragraph>
</View>
</View>
</TouchableRipple>
)
}
const styles = StyleSheet.create({
item: {
paddingRight: 10,
paddingLeft: 30,
paddingTop: 20,
paddingBottom: 10,
marginBottom: 10,
},
firstRow: {
marginTop: 5,
marginBottom: 5,
},
secondRow: {
marginBottom: 10,
},
title: { fontWeight: 'bold' },
})

View File

@@ -0,0 +1,18 @@
import * as React from 'react'
import { ActivityIndicator, StyleSheet, View } from 'react-native'
export function LoadingIndicator() {
return (
<View style={styles.fill}>
<ActivityIndicator size="large" />
</View>
)
}
const styles = StyleSheet.create({
fill: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
import { useEffect } from 'react'
import { AppState } from 'react-native'
import type { AppStateStatus } from 'react-native'
export function useAppState(onChange: (status: AppStateStatus) => void) {
useEffect(() => {
const subscription = AppState.addEventListener('change', onChange)
return () => {
subscription.remove()
}
}, [onChange])
}

View File

@@ -0,0 +1,19 @@
import * as React from 'react'
import NetInfo from '@react-native-community/netinfo'
import { onlineManager } from '@tanstack/react-query'
import { Platform } from 'react-native'
export function useOnlineManager() {
React.useEffect(() => {
// React Query already supports on reconnect auto refetch in web browser
if (Platform.OS !== 'web') {
return NetInfo.addEventListener((state) => {
onlineManager.setOnline(
state.isConnected != null &&
state.isConnected &&
Boolean(state.isInternetReachable),
)
})
}
}, [])
}

View File

@@ -0,0 +1,20 @@
import * as React from 'react'
export function useRefreshByUser(refetch: () => Promise<unknown>) {
const [isRefetchingByUser, setIsRefetchingByUser] = React.useState(false)
async function refetchByUser() {
setIsRefetchingByUser(true)
try {
await refetch()
} finally {
setIsRefetchingByUser(false)
}
}
return {
isRefetchingByUser,
refetchByUser,
}
}

View File

@@ -0,0 +1,16 @@
import * as React from 'react'
import { useFocusEffect } from '@react-navigation/native'
export function useRefreshOnFocus(refetch: () => void) {
const enabledRef = React.useRef(false)
useFocusEffect(
React.useCallback(() => {
if (enabledRef.current) {
refetch()
} else {
enabledRef.current = true
}
}, [refetch]),
)
}

View File

@@ -0,0 +1,44 @@
import movies from '../data/movies.json'
export type Movie = {
title: string
year: number
}
export type MovieDetails = Movie & {
info: {
plot: string
actors: string[]
}
}
function delay(t: number) {
return new Promise(function (resolve) {
setTimeout(resolve, t)
})
}
export async function fetchMovies(): Promise<Movie[]> {
console.log('fetchMovies')
await delay(200 + Math.floor(Math.random() * 2000))
return Promise.resolve(
movies
.slice(0, 100)
.map((movie) => ({ title: movie.title, year: movie.year })),
)
}
export async function fetchMovie(title: string): Promise<MovieDetails> {
console.log('fetchMovie', title)
await delay(200 + Math.floor(Math.random() * 2000))
const result = movies.filter((item) => item.title === title)
if (result.length === 0) {
throw new Error('Movie not found')
}
return Promise.resolve(result[0] as MovieDetails)
}

View File

@@ -0,0 +1,29 @@
import * as React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import { MoviesListScreen } from '../screens/MoviesListScreen'
import { MovieDetailsScreen } from '../screens/MovieDetailsScreen'
import type { MoviesStackNavigator } from './types'
const Stack = createStackNavigator<MoviesStackNavigator>()
export function MoviesStack() {
return (
<Stack.Navigator initialRouteName="MoviesList">
<Stack.Screen
name="MoviesList"
component={MoviesListScreen}
options={{
headerTitle: 'Movies',
}}
/>
<Stack.Screen
name="MovieDetails"
component={MovieDetailsScreen}
options={{
headerTitle: 'Movie details',
}}
/>
</Stack.Navigator>
)
}

View File

@@ -0,0 +1,6 @@
import type { Movie } from '../lib/api'
export type MoviesStackNavigator = {
MoviesList: undefined
MovieDetails: { movie: Movie }
}

View File

@@ -0,0 +1,85 @@
import * as React from 'react'
import { RefreshControl, ScrollView, StyleSheet, View } from 'react-native'
import { Paragraph, Title } from 'react-native-paper'
import { useQuery } from '@tanstack/react-query'
import { LoadingIndicator } from '../components/LoadingIndicator'
import { ErrorMessage } from '../components/ErrorMessage'
import { useRefreshByUser } from '../hooks/useRefreshByUser'
import { fetchMovie } from '../lib/api'
import type { MovieDetails } from '../lib/api'
import type { MoviesStackNavigator } from '../navigation/types'
import type { StackNavigationProp } from '@react-navigation/stack'
import type { RouteProp } from '@react-navigation/native'
type MoviesDetailsScreenNavigationProp = StackNavigationProp<
MoviesStackNavigator,
'MovieDetails'
>
type Props = {
navigation: MoviesDetailsScreenNavigationProp
route: RouteProp<MoviesStackNavigator, 'MovieDetails'>
}
export function MovieDetailsScreen({ route }: Props) {
const { isPending, error, data, refetch } = useQuery<MovieDetails, Error>({
queryKey: ['movie', route.params.movie.title],
queryFn: () => fetchMovie(route.params.movie.title),
initialData: route.params.movie as MovieDetails,
})
const { isRefetchingByUser, refetchByUser } = useRefreshByUser(refetch)
if (isPending) return <LoadingIndicator />
if (error) return <ErrorMessage message={error.message}></ErrorMessage>
if (!data) return null
return (
<ScrollView
refreshControl={
<RefreshControl
refreshing={isRefetchingByUser}
onRefresh={refetchByUser}
/>
}
>
<View style={styles.titleRow}>
<Title>
{data.title} ({data.year})
</Title>
</View>
{data.info ? (
<>
<View style={styles.infoRow}>
<Paragraph>{data.info.plot}</Paragraph>
</View>
<View style={styles.actorsRow}>
<Paragraph>
{data.info.actors.slice(0, -1).join(', ') +
' or ' +
data.info.actors.slice(-1)}
</Paragraph>
</View>
</>
) : (
<LoadingIndicator />
)}
</ScrollView>
)
}
const styles = StyleSheet.create({
titleRow: {
flexDirection: 'row',
margin: 20,
},
infoRow: {
flexDirection: 'row',
margin: 20,
},
actorsRow: {
flexDirection: 'column',
margin: 20,
marginTop: 10,
},
})

View File

@@ -0,0 +1,65 @@
import * as React from 'react'
import { FlatList, RefreshControl } from 'react-native'
import { useQuery } from '@tanstack/react-query'
import { LoadingIndicator } from '../components/LoadingIndicator'
import { ErrorMessage } from '../components/ErrorMessage'
import { Divider } from '../components/Divider'
import { ListItem } from '../components/ListItem'
import { useRefreshByUser } from '../hooks/useRefreshByUser'
import { useRefreshOnFocus } from '../hooks/useRefreshOnFocus'
import { fetchMovies } from '../lib/api'
import type { Movie, MovieDetails } from '../lib/api'
import type { MoviesStackNavigator } from '../navigation/types'
import type { StackNavigationProp } from '@react-navigation/stack'
type MoviesListScreenNavigationProp = StackNavigationProp<
MoviesStackNavigator,
'MoviesList'
>
type Props = {
navigation: MoviesListScreenNavigationProp
}
export function MoviesListScreen({ navigation }: Props) {
const { isPending, error, data, refetch } = useQuery<Movie[], Error>({
queryKey: ['movies'],
queryFn: fetchMovies,
})
const { isRefetchingByUser, refetchByUser } = useRefreshByUser(refetch)
useRefreshOnFocus(refetch)
const onListItemPress = React.useCallback(
(movie: MovieDetails) => {
navigation.navigate('MovieDetails', {
movie,
})
},
[navigation],
)
const renderItem = React.useCallback(
({ item }: { item: MovieDetails }) => {
return <ListItem item={item} onPress={onListItemPress} />
},
[onListItemPress],
)
if (isPending) return <LoadingIndicator />
if (error) return <ErrorMessage message={error.message}></ErrorMessage>
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.title}
ItemSeparatorComponent={() => <Divider />}
refreshControl={
<RefreshControl
refreshing={isRefetchingByUser}
onRefresh={refetchByUser}
/>
}
></FlatList>
)
}

View File

@@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}