Implementing infinite scroll in React

We will see how to implement infinite scrolling using just React. For this we will be using the useCallback hook and IntersectionObserver dom api. let’s start by simply creating the app npx create-react-app infinite_scroll cd infinite_scroll yarn start After you’ve cleaned up the boilerplate code, let’s start by creating our custom hook touch src/useSearch.js import{

We will see how to implement infinite scrolling using just React. For this we will be using the useCallback hook and IntersectionObserver dom api.

let’s start by simply creating the app

npx create-react-app infinite_scroll
cd infinite_scroll
yarn start

After you’ve cleaned up the boilerplate code, let’s start by creating our custom hook

touch src/useSearch.js
import{ useEffect, useState }from"react";import axios from"axios";exportdefaultfunctionuseSearch(query, page){constURL='http://openlibrary.org/search.json'useEffect(()=>{axios({
      method:'GET',
      url:URL,
      params:{q: query, page}}).then(res=>{
      console.log(res.data)})},[query, page]);}

we will be using the OpenLibrary api for our demo, and axios to do the api calls for us.
The OpenLibrary api is paginated, so it’s perfect for our use case

In your browser, paste the url https://openlibrary.org/search.json?q=test23&page=1 to inspect the api end point

{"numFound":7,// number of results found"start":0,"numFoundExact":true,"docs":[{"key":"/works/OL8944247W","title":"MTTC French Sample Test 23","title_suggest":"MTTC French Sample Test 23","has_fulltext":true,"edition_count":1,
            ...
        },{// book 2},{// book 3},
        ....
    ],"num_found":7,"q":"test23",// search keyword"offset":null}

So, let’s modify our custom hook

import{ useEffect, useState }from"react";import axios from"axios";exportdefaultfunctionuseSearch(query, page){constURL='http://openlibrary.org/search.json'const[books, setBooks]=useState([])const[isLoading, setIsLoading]=useState(false)const[loadMore, setLoadMore]=useState(false)useEffect(()=>{setBooks([])},[query])useEffect(()=>{setIsLoading(true);axios({
      method:'GET',
      url:URL,
      params:{q: query, page}}).then(res=>{setBooks(prev=>{return[...prev,...res.data.docs.map(b=> b.title)]})setIsLoading(false)setLoadMore( res.data.docs.legth >0)})},[query, page]);return{isLoading, books, loadMore}}

When, we are getting back results from the api, we append it in our books state

setBooks(prev=>{return[...prev,...res.data.docs.map(b=> b.title)]})

this will append the data with our previous state, so if we keep calling data for the next page, it’ll get appended to our previous result, rather than getting overwritten.

we use a different useState to clear our books state when we query for different data

useEffect(()=>{setBooks([])},[query])

also, we set the isLoading key to prevent our app to keep calling the api constantly.

The last part setLoadMore is to let our app know we have reached the end of the paginated results, and stop doing api calls. Or, if we haven’t reached the end yet, and we can call for more

setLoadMore( res.data.docs.legth >0)

Finally we return the value of isLoading, books, loadMore to our app.

Now, let’s import it to our App.js

import'./App.css';import{ useState }from'react';import useSearch from'./useSearch';functionApp(){const[query, setQuery]=useState('')const[page, setPage]=useState(1)const{isLoading, books, loadMore}=useSearch(query, page)// load our custom hookreturn(<div className="App"><input onChange={handleSearch} placeholder="search"></input><div className="results"></div></div>);}exportdefault App;

Time to modify our UI, first we add the handleSearch function that will fire on onChange event of our Input, we just call to setQuery to whatever data is in the Input box

consthandleSearch=(e)=>{if(e.target.value ===''){returnnull;}setQuery(e.target.value)setPage(1)}

Next, we have our books array in state, we’ll just loop through it and render the names

constrenderBooks=()=>{return books.map((book, index)=>{return(<p key={index}>{book}</p>)})}

and we call it inside results div

<div className="results">{renderBooks()}</div>

Now, we will start our infinite scroll part. As we said earlier, we’ll be using the useCallback hook, so let’s import it

import{ useState, useRef, useCallback }from'react';const observer =useRef()

it’ll be empty in the beginnig. Let’s think how will are we gonna implement it.
imagine, we have 5 books

<div><p>book</p><p>book</p><p>book</p><p>book</p><p>book</p></div>

We wan’t our api to be called for more when we reach the last book element. Something which will observe the Book#5, when it get’s rendered in the screen, we will call for 5 more books, and the observer will then be set to Book#10 and removed from Book#5
We use the IntersectionObserver dom api to do it for us.

const lastElementRef =useCallback((node)=>{if(isLoading){returnnull;// do nothing if already an api call going}if(observer.current){
      observer.current.disconnect();// if we already have an observer.current (Book#5), then disconnect the observer from it}

    observer.current =newIntersectionObserver(elements=>{if(elements[0].isIntersecting){// isIntersecting means the browser is intersecting with this current element, ie: showing in view
        console.log("visible")}})if(node){
      observer.current.observe(node)// set the observer to the new element (Book#10)}},[isLoading])

Now, we just need to add reference to this callback to the last element when we render, let’s modify our code

constrenderBooks=()=>{return books.map((book, index)=>{if(books.length === index+1){return(<p ref={lastElementRef} key={index}>{book}</p>)// add the callback when our index is the last one}return(<p key={index}>{book}</p>)// don't add callback otherwise})}

Now, our code is almost finished. Instead of doing console.log() we just need to fire our api call.

observer.current =newIntersectionObserver(elements=>{if(elements[0].isIntersecting){setPage(prevPage=> prevPage +1)}})

That’s it.

Study material

useCallback

Intersection Observer

souurce code

https://github.com/Salekin-1169/infinite_scroll

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ