In this blog post, we'll delve into implementing infinite scrolling functionality using React Query, a powerful library for managing asynchronous data fetching in React applications. Here, we'll build upon the provided code and offer insights into each line, along with suggestions for further optimization.
Understanding the Code:
1. Imports:
import { useState } from 'react';
import { useQuery } from 'react-query';
Explanation:
useState
: This React Hook is used to manage the component's state, in this case, to store the fetched posts.useQuery
: This Hook from React Query facilitates fetching data asynchronously and managing its state (loading, error, success).
2. fetchPosts
Function:
const fetchPosts = async (page = 1) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}`);
return response.json();
};
Explanation:
- This function fetches data from the JSONPlaceholder API, taking an optional
page
parameter for pagination. - It makes a
fetch
request to the API endpoint, including the page number in the query string. - The response is converted to JSON format using
response.json()
.
3. Generate
Random color
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
Explanation:
Function Declaration:
const getRandomColor = () => { ... }
: This line defines a constant function namedgetRandomColor
that uses an arrow function syntax (=>
).
Character Pool:
const letters = '0123456789ABCDEF';
: This line creates a constant variable namedletters
and assigns a string containing all valid hexadecimal characters (digits 0-9 and letters A-F).
Color String Initialization:
let color = '#';
: This line declares a variable namedcolor
and initializes it with the '#' character, which is the prefix for hexadecimal color codes.
Loop for Generating Random Characters:
for (let i = 0; i < 6; i++) { ... }
: This loop iterates six times (i < 6
).- Inside the loop:
Math.floor(Math.random() * 16)
: This expression generates a random integer between 0 and 15 (inclusive) usingMath.random()
andMath.floor()
.letters[ ... ]
: This accesses a random character from theletters
string based on the generated index.color += ...
: The randomly chosen character is appended to thecolor
string using the concatenation operator (+=
).
- Inside the loop:
Returning the Color Code:
return color;
: After the loop completes, the finalcolor
string, which is a valid hexadecimal color code, is returned from the function.
4. App
Component:
const App = () => {
const [data, setData] = useState([]);
const { error, isLoading, isFetching, isPreviousData } = useQuery('posts', () => fetchPosts(), {
staleTime: 30000, // 30 seconds
keepPreviousData: true,
onSuccess: (fetchedData) => {
setData(fetchedData);
},
});
const fetchMorePosts = async () => {
const nextPage = Math.ceil(data.length / 10) + 1;
const newData = await fetchPosts(nextPage);
setData((prevData) => [...prevData, ...newData]);
};
Explanation:
- State and
useQuery
Hook:- The component maintains state for the fetched data (
data
) usinguseState
. - The
useQuery
Hook from React Query fetches data with the key'posts'
.- The query function calls
fetchPosts
. staleTime: 30000
keeps the data fresh for 30 seconds after the last successful fetch.keepPreviousData: true
preserves previous data during subsequent fetches.- The
onSuccess
callback updates thedata
state with the fetched data.
- The query function calls
- The component maintains state for the fetched data (
fetchMorePosts
Function:- This function handles fetching new posts when the user reaches the bottom of the list.
- It calculates the next page number based on the current data length (assuming 10 posts per page).
- It calls
fetchPosts
with the next page number. - The fetched data is appended to the existing data using the callback function in
setData
.
5. Rendering and User Interaction:
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div style={{ width: '90%', margin: 'auto' }}>
<h1>Infinite Scrolling with React Query</h1>
<div>
{data.map((post, index) => {
const borderColor = getRandomColor();
return (
<div key={post.id} style={{ borderLeft: `3px solid ${borderColor}`, padding: '20px 24px 20px', marginBottom: '30px', background: `linear-gradient(45deg, ${borderColor}0C, transparent)` }}>
<h3 style={{ margin: '0', lineHeight: 1, }}>{post.title}</h3>
<p style={{ marginBottom: '0' }}>{post.body}</p>
</div>
);
})}
<div>
<button
onClick={() => {
fetchMorePosts();
}}
disabled={isFetching}
>
{isFetching || isPreviousData ? 'Loading more...' : 'Load more'}
</button>
</div>
</div>
</div>
);
Explanation:
Conditional Rendering:
- The component displays a loading message while data is being fetched (
isLoading
). - If an error occurs during data fetching (
error
is truthy), the component renders an error message displaying the error's message (error.message
). - The
data.map
function iterates through the fetched posts, rendering each post within adiv
element. - Each post includes elements for the title (
<h2>
), body (<p>
), and a potential loading indicator. - The key condition (
key={post.id}
) ensures efficient updates in React. - The conditional rendering within the
map
function handles the "Loading more..." message for the last post:- It checks two conditions:
isFetching
: This indicates if new data is being fetched.data.length === index + 1
: This identifies if the current post is the last one in the rendered list.
- If both conditions are true, it displays the "Loading more..." message, indicating that more data is being fetched specifically for the next page.
- It checks two conditions:
- The button triggers the
fetchMorePosts
function when clicked. - It's disabled (
disabled={isFetching}
) while new data is being fetched to prevent duplicate requests. - The button text conditionally changes based on the data fetching state:
- "Loading more..." shows when data is being fetched (
isFetching
) or when there's previous data to display (isPreviousData
). - "Load more" is displayed when both
isFetching
andisPreviousData
are false, indicating no active fetching and no previously displayed data.
- "Loading more..." shows when data is being fetched (
Complete Code
import { QueryClient, QueryClientProvider } from 'react-query';
const index = () => (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
export default index;
import { useState } from 'react';
import { useQuery } from 'react-query';
const fetchPosts = async (page = 1) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}`);
return response.json();
};
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const App = () => {
const [data, setData] = useState([]);
const { error, isLoading, isFetching, isPreviousData } = useQuery('posts', () => fetchPosts(), {
staleTime: Infinity,
keepPreviousData: true,
onSuccess: (fetchedData) => {
setData(fetchedData);
}
});
const fetchMorePosts = async () => {
const nextPage = Math.ceil(data.length / 10) + 1;
const newData = await fetchPosts(nextPage);
setData((prevData) => [...prevData, ...newData]);
};
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div style={{ width: '90%', margin: 'auto' }}>
<h1>Infinite Scrolling with React Query</h1>
<div>
{data.map((post, index) => {
const borderColor = getRandomColor();
return (
<div key={post.id} style={{ borderLeft: `3px solid ${borderColor}`, padding: '20px 24px 20px', marginBottom: '30px', background: `linear-gradient(45deg, ${borderColor}0C, transparent)` }}>
<h3 style={{ margin: '0', lineHeight: 1, }}>{post.title}</h3>
<p style={{ marginBottom: '0' }}>{post.body}</p>
</div>
);
})}
<div>
<button
onClick={() => {
fetchMorePosts();
}}
disabled={isFetching}
>
{isFetching || isPreviousData ? 'Loading more...' : 'Load more'}
</button>
</div>
</div>
</div>
);
};
export default App;
How to Implement Custom Hooks in React Applications
10 Useful JavaScript Snippets you should know about
About Muhaymin Bin Mehmood
Front-end Developer skilled in the MERN stack, experienced in web and mobile development. Proficient in React.js, Node.js, and Express.js, with a focus on client interactions, sales support, and high-performance applications.