Last active
March 10, 2023 21:35
-
-
Save vincicat/261c79e3ccf75f8f272e0493f250303f to your computer and use it in GitHub Desktop.
Single-File JavaScript Example: react-query in React Native (0.70)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable react-native/no-inline-styles */ | |
import * as React from 'react'; | |
import movies from './data/movies.json'; | |
import { | |
AppStateStatus, | |
Platform, | |
AppState, | |
TouchableHighlight, | |
ActivityIndicator, | |
View, | |
Text, | |
FlatList, | |
RefreshControl, | |
} from 'react-native'; | |
import {NavigationContainer} from '@react-navigation/native'; | |
import { | |
QueryClient, | |
QueryClientProvider, | |
focusManager, | |
} from '@tanstack/react-query'; | |
import {useFocusEffect} from '@react-navigation/native'; | |
import {useEffect} from 'react'; | |
import {createNativeStackNavigator as createStackNavigator} from '@react-navigation/native-stack'; | |
import NetInfo from '@react-native-community/netinfo'; | |
import {onlineManager, useQuery} from '@tanstack/react-query'; | |
// hook | |
export function useAppState(onChange) { | |
useEffect(() => { | |
AppState.addEventListener('change', onChange); | |
return () => { | |
AppState.removeEventListener('change', onChange); | |
}; | |
}, [onChange]); | |
} | |
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 && | |
state.isInternetReachable, | |
); | |
}); | |
} | |
}, []); | |
} | |
export function useRefreshByUser(refetch) { | |
const [isRefetchingByUser, setIsRefetchingByUser] = React.useState(false); | |
async function refetchByUser() { | |
setIsRefetchingByUser(true); | |
try { | |
await refetch(); | |
} finally { | |
setIsRefetchingByUser(false); | |
} | |
} | |
return { | |
isRefetchingByUser, | |
refetchByUser, | |
}; | |
} | |
export function useRefreshOnFocus(refetch) { | |
const enabledRef = React.useRef(false); | |
useFocusEffect( | |
React.useCallback(() => { | |
if (enabledRef.current) { | |
refetch(); | |
} else { | |
enabledRef.current = true; | |
} | |
}, [refetch]), | |
); | |
} | |
// api | |
function delay(t) { | |
return new Promise(function (resolve) { | |
setTimeout(resolve, t); | |
}); | |
} | |
async function fetchMovies() { | |
console.log('fetchMovies'); | |
await delay(200 + Math.floor(Math.random() * 2000)); | |
return movies | |
.slice(0, 100) | |
.map(movie => ({title: movie.title, year: movie.year})); | |
} | |
async function fetchMovie(title) { | |
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 result[0]; //promise | |
} | |
// router | |
const Stack = createStackNavigator(); | |
function MoviesListScreen({navigation}) { | |
const {isLoading, error, data, refetch} = useQuery(['movies'], fetchMovies); | |
const {isRefetchingByUser, refetchByUser} = useRefreshByUser(refetch); | |
useRefreshOnFocus(refetch); | |
const onListItemPress = React.useCallback( | |
movie => { | |
navigation.navigate('MovieDetails', { | |
movie, | |
}); | |
}, | |
[navigation], | |
); | |
const renderItem = React.useCallback( | |
({item}) => { | |
return ( | |
<> | |
<TouchableHighlight onPress={onListItemPress}> | |
<View> | |
<Text>{JSON.stringify(item)}</Text> | |
</View> | |
</TouchableHighlight> | |
</> | |
); | |
}, | |
[onListItemPress], | |
); | |
if (isLoading) { | |
return <ActivityIndicator />; | |
} | |
if (error) { | |
return <Text>{error.message}</Text>; | |
} | |
return ( | |
<FlatList | |
data={data} | |
renderItem={renderItem} | |
keyExtractor={item => item.title} | |
ItemSeparatorComponent={() => ( | |
<View style={{borderBottomWidth: 1, borderBottomColor: '#bbb'}} /> | |
)} | |
refreshControl={ | |
<RefreshControl | |
refreshing={isRefetchingByUser} | |
onRefresh={refetchByUser} | |
/> | |
} | |
/> | |
); | |
} | |
function MovieDetailsScreen(props) { | |
return ( | |
<View> | |
<Text>{JSON.stringify(props)}</Text> | |
</View> | |
); | |
} | |
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> | |
); | |
} | |
function onAppStateChange(status) { | |
// 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> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment