In this article, we will explore how to create an image search app with infinite scroll using React. The app will allow users to search for images based on a given query, and as they scroll down, more images will be fetched and displayed. We will use the Unsplash API to search for and retrieve the images. By the end of this tutorial, you will have a functional image search app with an infinite scroll feature.
Let us have a look at how the final application will look like:
Prerequisites:
Approach:
We’ll use Unsplash API to fetch images based on the user’s search query. We will include a debounced search input to delay the API request until the user pauses typing. We will also implement an infinite scroll by fetching more images when the user reaches the bottom of the page. A preloader spinner will be displayed while fetching images. The use of useState, useEffect, and useRef hooks will help us to manage the state and perform necessary actions.
Steps to Create React App:
Step 1: Create the project file using the command:
npx create-react-app <<Name_of_project>>
Step 2: Navigate to the folder using the command
cd <<Name_of_project>>
Step 3: Install the following packages
npm install unsplash-js lodash react-spinners
Modules:
- unsplash-js: to interact with Unsplash this API.
- lodash: to use debounce utility function.
- react-spinners: to show spinner while fetching images.
The updated dependencies in package.json will look like this:
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"react-spinners": "^0.13.8",
"unsplash-js": "^7.0.18",
"web-vitals": "^2.1.4"
}
Project Structure
It will look like the following.
Note: Get your Unsplash API key from https://unsplash.com/
Example: Write the following code in respective files.
- App.js: This file contains the main logic.
- App.css: This file contains the styling.
Javascript
// App.js import "./App.css" ; import { useState, useEffect, useRef } from "react" ; import { createApi } from "unsplash-js" ; import { debounce } from "lodash" ; import { BounceLoader } from "react-spinners" ; const unsplash = createApi({ accessKey: << use your API Key>>, }); function App() { // State variable to store the search phrase const [phrase, setPhrase] = useState( "" ); // Ref to hold the current value of the search phrase const phraseRef = useRef(phrase); // State variable to store the fetched images const [images, setImages] = useState([]); // Ref to hold the current value of the fetched images const imagesRef = useRef(images); // State variable to indicate if images are being fetched const [fetching, setFetching] = useState( false ); // Ref to hold the current value of the fetching state const fetchingRef = useRef(fetching); function getUnsplashImages(query, page = 1) { setFetching( true ); fetchingRef.current = true ; return new Promise((resolve, reject) => { unsplash.search .getPhotos({ query, page, perPage: 5, }) .then((result) => { // Update fetching state to indicate //that images fetching is completed setFetching( false ); fetchingRef.current = false ; resolve(result.response.results.map((result) => result.urls.regular)); }); }); } useEffect(() => { phraseRef.current = phrase; if (phrase !== "" ) debounce(() => { setImages([]); getUnsplashImages(phrase, 1).then((images) => { setImages(images); }); imagesRef.current = images; }, 1000)(); }, [phrase]); function handleScroll(e) { const { scrollHeight, scrollTop, clientHeight } = e.target.scrollingElement; const isBottom = scrollHeight - scrollTop <= clientHeight; if (isBottom && !fetchingRef.current) { getUnsplashImages( phraseRef.current, imagesRef.current.length / 5 + 1 ).then((newImages) => { imagesRef.current = [...imagesRef.current, ...newImages]; setImages(imagesRef.current); }); } } useEffect(() => { document.addEventListener( "scroll" , handleScroll, { passive: true }); return () => document.removeEventListener( "scroll" , handleScroll); }, []); return ( <div> <input type= "text" value={phrase} onChange={(e) => setPhrase(e.target.value)} /> <br /> {images.length > 0 && images.map((url) => <img src={url} />)} <div> {fetching && ( <div style={{ textAlign: "center" }}> <BounceLoader speedMultiplier={5} color= "#000000" /> </div> )} </div> </div> ); } export default App; |
CSS
/* App.css */ img { max-width : 100% ; } |
Steps to Run the Application
Step 1: Type the following command in the terminal of your project directory
npm start
Step 2: Type the following URL in your web browser.
http://localhost:3000/
Output: