This content originally appeared on Level Up Coding - Medium and was authored by Joseph Odunsi
Infinite scrolling is a technique that constantly loads data as the user scrolls down the page, removing the need for pagination.
This tutorial will use React Native to create infinite scrolling and FlashList to render the data. We'll use the Dogs API to retrieve data about dogs, and we will utilize React Query to handle data processing.
Why FlashList?
FlashList provides much-improved speed, making your lists smooth with no blank cells. It "recycles components under the hood to maximize performance." Visit their website to learn more.
Initialize a React Native Project
We'll use Expo to create a new app. So, make sure you have Expo CLI working on your development machine Expo Go app is installed on your iOS or Android physical device or emulator. Check their installation guide if you haven't.
Run the following command in your project directory
npx expo init
You'll be prompted with some options
- What would you like to name your app? Enter any name you want. I'll go with rn-infinite-scroll
- Choose a template Choose blank
Now, wait until it is done installing the dependencies, navigate to the directory and start the app.
cd rn-infinite-scroll
yarn start
Installing dependencies
Stop the app and install the following dependencies.
yarn add @tanstack/react-query
npx expo install @shopify/flash-list react-native-safe-area-context
Start the app again using yarn start
Setup React Query
We need to wrap the app with react query's provider to have access to the client
Open the App.js file and modify it as follows
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
// react query client instance
const queryClient = new QueryClient();
export default function App() {
return (
// react query provider
<QueryClientProvider client={queryClient}>
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
</QueryClientProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
});
Creating Screens
DogCard Component
Let's start by creating the home screen. In the project directory, create a new folder named components and create a new file called DogCard.js i.e./components/DogCard.js. Add the following code to it.
import React from "react";
import { Image, StyleSheet, Text, View } from "react-native";
const DogCard = ({ dog }) => {
return (
<View>
<View style={styles.row}>
<Image source={{ uri: dog.image.url }} style={styles.pic} />
<View style={styles.nameContainer}>
<Text style={styles.nameTxt}>{dog.name}</Text>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
borderColor: "#DCDCDC",
backgroundColor: "#fff",
borderBottomWidth: 1,
padding: 10,
},
pic: {
borderRadius: 30,
width: 60,
height: 60,
},
nameContainer: {
flexDirection: "row",
justifyContent: "space-between",
},
nameTxt: {
marginLeft: 15,
fontWeight: "600",
color: "#222",
fontSize: 18,
},
});
export default DogCard;
Here, we will be rendering the dog's breed name and image.
Home Component
Next, we create another folder in the root directory named screens and a new file called Home.js. Add the following to it.
import { FlashList } from "@shopify/flash-list";
import React from "react";
import { Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
const Home = () => {
return (
<SafeAreaView>
<FlashList
data={[{ name: "Dogs" }, { name: "Fido" }, { name: "Spot"}]}
renderItem={({ item }) => <Text>{item.name}</Text>}
estimatedItemSize={100}
/>
</SafeAreaView>
);
};
export default Home;
For now, we are using hardcoded data to render the list.
This is what your screen should be showing now
Fetching data
Next, we will handle fetching data from the Dogs API. Create a new folder in the root directory named hooks i.e. /hooks and a file, useFetchDogs i.e /hooks/useFetchDogs. This will be a custom hook to fetch our data. Populate the file with the following
import { useQuery } from "@tanstack/react-query";
export default function useFetchDogs() {
const getDogs = async () => {
const res = await (
await fetch(`https://api.thedogapi.com/v1/breeds`)
).json();
return res;
};
return useQuery(["dogs"], getDogs);
}
The useFetchDogs is a custom hook. We created the getDogs function to fetch all the data, no pagination yet. And then return the result of useQuery the hook, which is imported from react-query to fetch the data. This hook takes at least two arguments:
- A unique key for the query is used internally for re-fetching, caching, and sharing your queries throughout your application. In our case, the key is dogs.
- A function that returns a promise which is the getDogs function in our case.
Now, let's modify the Home.js screen to display the data we are getting from the API.
// previous code here
import DogCard from "../components/DogCard";
import useFetchDogs from "../hooks/useFetchDogs";
const Home = () => {
const { data, isLoading, isError } = useFetchDogs();
if (isLoading) return <Text>Loading...</Text>;
if (isError) return <Text>An error occurred while fetching data</Text>;
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<FlashList
keyExtractor={(item) => item.id}
data={data}
renderItem={({ item }) => <DogCard dog={item} />}
estimatedItemSize={100}
/>
</SafeAreaView>
);
};
// remaining code here
We imported our custom hook and called it. The hook returns an object and extracts the data we'll use by destructuring. data contains the data we need to render. isLoading indicates that the data is being fetched, returning a boolean. And isError indicates if there's an error and also returns a boolean.
While isLoading is true, we return a text to let the user know, and while isError is true, we'll let the user know there's an error.
Also, note we are now using the DogCard component.
Paginate with useInfiniteQuery
Right now, we are fetching all the data at once. That's not what we want. We want to fetch 10 of the data for every page.
To do that, we need to use a version of useQuery called useInfiniteQuery. We'll modify our custom hook first as follows
import { useInfiniteQuery } from "@tanstack/react-query";
export default function useFetchDogs() {
const getDogs = async ({ pageParam = 0 }) => {
const res = await (
await fetch(
`https://api.thedogapi.com/v1/breeds?limit=10&page=${pageParam}`
)
).json();
return {
data: res,
nextPage: pageParam + 1,
};
};
return useInfiniteQuery(["dogs"], getDogs, {
getNextPageParam: (lastPage) => {
if (lastPage.data.length < 10) return undefined;
return lastPage.nextPage;
},
});
}
We are now using useInfiniteQuery instead of useQuery .
The getDogs function has a parameter, pageParam which is 0 by default. This means we want to start fetching from the first page. We appended the API URL with ?limit=10&page={pageParam} to get ten items from each page. We are also now returning modified data, an object with two keys, data and nextPage. After every successful API call, we increment nextPage. useInfiniteQuery needs the data in this format.
We don't have to do this for some APIs as they already have their data returned in a similar format. But for the Dogs API, we need to return the data in the structure ourselves for the useInfiniteQuery.
Next is the useInfiniteQuery returned. Just like useQuery but a third argument is an object with various optional keys. We added the getNextPageParam option to determine if there is more data to load and information to fetch.
getNextPageParam accepts a parameter, lastPage which contains the response we returned from getDogs function and returns the next page. We also added a condition to check if we've reached the last page.
Render data for Infinite Scrolling
First, let's examine the data format returned by useInfiniteQuery. The data is now an object with two keys:
- pageParams - an array of the page number
- pages - an array of our data for each page
We need to flatten the pages to join the data for each page as one array to map through it and render them. We can utilize JavaScript flatMap.
const flattenData = data.pages.flatMap((page) => page.data)
Let's modify our list component to render the flattened data. Open up Home.js
const Home = () => {
// previous code here
const flattenData = data.pages.flatMap((page) => page.data);
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<FlashList
keyExtractor={(item) => item.id}
data={flattenData}
renderItem={({ item }) => <DogCard dog={item} />}
estimatedItemSize={100}
/>
</SafeAreaView>
);
};
export default Home;
Infinite Scroll with FlashList
We need to load extra data when the scroll position hits a certain threshold. We call a function to load more data when it hits the threshold value. The threshold value in React Native is between 0 and 1, with 0.5 being the default. That is, anytime the end of the content is within half the apparent length of the list, the function to fetch new data will be called.
useInfiniteQuery provides us with some other functions from its result when called. fetchNextPage and hasNextPage are now available. Let's create the function to load the next page's data.
const loadNextPageData = () => {
if (hasNextPage) {
fetchNextPage();
}
};
We add a new prop FlashList to call the function when the scroll position gets within onEndReachedThreshold the rendered content.
<FlashList
keyExtractor={(item) => item.id}
data={flattenData}
renderItem={({ item }) => <DogCard dog={item} />}
onEndReached={loadNextPageData}
/>
Home.js should look like this:
import { FlashList } from "@shopify/flash-list";
import React from "react";
import { Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import DogCard from "../components/DogCard";
import useFetchDogs from "../hooks/useFetchDogs";
const Home = () => {
const { data, isLoading, isError, hasNextPage, fetchNextPage } =
useFetchDogs();
if (isLoading) return <Text>Loading...</Text>;
if (isError) return <Text>An error occurred while fetching data</Text>;
const flattenData = data.pages.flatMap((page) => page.data);
const loadNext = () => {
if (hasNextPage) {
fetchNextPage();
}
};
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<FlashList
keyExtractor={(item) => item.id}
data={flattenData}
renderItem={({ item }) => <DogCard dog={item} />}
onEndReached={loadNext}
estimatedItemSize={100}
/>
</SafeAreaView>
);
};
export default Home;
Remember, the threshold value can be between 0 and 1. Change the it from the default value(0.5) to 0.2
<FlashList
keyExtractor={(item) => item.id}
data={flattenData}
renderItem={({ item }) => (
<Text style={{ height: 50 }}>{item.name}</Text>
)}
onEndReached={loadNext}
onEndReachedThreshold={0.2}
estimatedItemSize={100}
/>
Conclusion
We implement infinite scrolling with React Native, React Query's useInfiniteQuery and FlashList, consuming the data from Dogs API.
GitHub code here.
Try out yourself
- Show a loading spinner while data is being fetched
- Implement pull to refresh
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Placing developers like you at top startups and tech companies
React Native Infinite Scrolling with React Query was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Joseph Odunsi
Joseph Odunsi | Sciencx (2022-09-30T11:34:11+00:00) React Native Infinite Scrolling with React Query. Retrieved from https://www.scien.cx/2022/09/30/react-native-infinite-scrolling-with-react-query/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.