snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
16
examples/react/react-native/.eslintrc
Normal file
16
examples/react/react-native/.eslintrc
Normal 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
91
examples/react/react-native/.gitignore
vendored
Normal 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
|
||||
37
examples/react/react-native/App.tsx
Normal file
37
examples/react/react-native/App.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
examples/react/react-native/README.md
Normal file
7
examples/react/react-native/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Example
|
||||
|
||||
To run this example:
|
||||
|
||||
- Install expo CLI if needed `npm install --global expo-cli`
|
||||
- `pnpm install`
|
||||
- `pnpm start`
|
||||
31
examples/react/react-native/app.json
Normal file
31
examples/react/react-native/app.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
examples/react/react-native/assets/adaptive-icon.png
Normal file
BIN
examples/react/react-native/assets/adaptive-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
examples/react/react-native/assets/favicon.png
Normal file
BIN
examples/react/react-native/assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/react/react-native/assets/icon.png
Normal file
BIN
examples/react/react-native/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
examples/react/react-native/assets/splash.png
Normal file
BIN
examples/react/react-native/assets/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
6
examples/react/react-native/babel.config.js
Normal file
6
examples/react/react-native/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true)
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
}
|
||||
}
|
||||
36
examples/react/react-native/package.json
Normal file
36
examples/react/react-native/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
13
examples/react/react-native/src/components/Divider.tsx
Normal file
13
examples/react/react-native/src/components/Divider.tsx
Normal 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,
|
||||
},
|
||||
})
|
||||
22
examples/react/react-native/src/components/ErrorMessage.tsx
Normal file
22
examples/react/react-native/src/components/ErrorMessage.tsx
Normal 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',
|
||||
},
|
||||
})
|
||||
42
examples/react/react-native/src/components/ListItem.tsx
Normal file
42
examples/react/react-native/src/components/ListItem.tsx
Normal 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' },
|
||||
})
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
3471
examples/react/react-native/src/data/movies.json
Normal file
3471
examples/react/react-native/src/data/movies.json
Normal file
File diff suppressed because it is too large
Load Diff
12
examples/react/react-native/src/hooks/useAppState.ts
Normal file
12
examples/react/react-native/src/hooks/useAppState.ts
Normal 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])
|
||||
}
|
||||
19
examples/react/react-native/src/hooks/useOnlineManager.ts
Normal file
19
examples/react/react-native/src/hooks/useOnlineManager.ts
Normal 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),
|
||||
)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
20
examples/react/react-native/src/hooks/useRefreshByUser.ts
Normal file
20
examples/react/react-native/src/hooks/useRefreshByUser.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
16
examples/react/react-native/src/hooks/useRefreshOnFocus.ts
Normal file
16
examples/react/react-native/src/hooks/useRefreshOnFocus.ts
Normal 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]),
|
||||
)
|
||||
}
|
||||
44
examples/react/react-native/src/lib/api.ts
Normal file
44
examples/react/react-native/src/lib/api.ts
Normal 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)
|
||||
}
|
||||
29
examples/react/react-native/src/navigation/MoviesStack.tsx
Normal file
29
examples/react/react-native/src/navigation/MoviesStack.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
6
examples/react/react-native/src/navigation/types.ts
Normal file
6
examples/react/react-native/src/navigation/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Movie } from '../lib/api'
|
||||
|
||||
export type MoviesStackNavigator = {
|
||||
MoviesList: undefined
|
||||
MovieDetails: { movie: Movie }
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
65
examples/react/react-native/src/screens/MoviesListScreen.tsx
Normal file
65
examples/react/react-native/src/screens/MoviesListScreen.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
6
examples/react/react-native/tsconfig.json
Normal file
6
examples/react/react-native/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user