DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How to Build a Book App With Infinite Scrolling and Meilisearch Strapi Plugin in React
Strapi for Strapi

Posted on • Originally published at strapi.io

How to Build a Book App With Infinite Scrolling and Meilisearch Strapi Plugin in React

In this tutorial, we will learn how to fetch and render data in chunks with infinite scrolling and how to integrate and use the meilisearch plugin to search for books.

Author: @tammibriggs

Fetching large sets of data at once can cause some negative effects like making components render slowly, which creates a bad user experience for site visitors. To handle this, two patterns are commonly used among which is infinite scrolling, which we will be covering in this tutorial.

Goal

In this tutorial, we will be building a book app using Strapi. The app will focus on how to fetch and render data in chunks with infinite scrolling and how to integrate and use the meilisearch plugin to search for books.

Prerequisites

To follow along with this tutorial, you should be familiar with React and you should have Node installed in your system.

An Introduction to Strapi

Strapi is an open-source, headless Content Management System(CMS) developed using the Nodejs Javascript framework that allows designing API fast and can be accessed from any client(React, Vue, etc), giving developers the freedom of using their native tools.

Strapi includes a user-friendly Admin page that provides feature for easy management and monitoring of content. The Admin page, as well as created API, can be customized to match our use cases based on its plugin system which is one of the endearing features of Strapi.

Setting up a Strapi Project

Setting up a Strapi project is pretty straightforward. Just like create-react-app, Strapi has [create-strapi-app](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/installation/cli.html#creating-a-strapi-project).

Run the following commands:

    $ mkdir book-app

    $ cd book-app

    $ npx create-strapi-app@latest book-app-backend --quickstart
Enter fullscreen mode Exit fullscreen mode

The command above sets up Strapi for our app with all required dependencies and creates a new folder book-app-backend.

Once the installation is complete, the server will start up which we can view in our browser via the specified link. In our browser, we the following page:

Screenshot

Here, fill in the required details and create a user account to access the dashboard.

Screenshot

Creating the Collection Types

In the collection type, we will define the content we wish to store on Strapi. To do this, first, click on the Create your first Collection Type button.

creenshot

On this page, click on Create new collection type and we will see a prompt asking to input the Display name and other information:

Sample Screenshot

Enter Books as the display name and click on Continue. On the next prompt, we will see different field types we can create for our collection.

Sample Screenshot

Here, we will create fields for the books. Each book will have authors, description, image, previewLink, publishDate, publisher, subtitle and title. These are the fields our Books collection type will include. All the fields will be a text fields except for authors which will be of type JSON.

Sample Screenshot

Above are all the fields for the book data. After creation, click on the Save button at the top right of the page.

Building a Book App

The book app will have a homepage that will display all the books available in our Strapi collection which we will fetch in chunks using infinite scrolling. We will have a detail page to display information about a particular book, and a search page that displays the search results received from Meilisearch.

I have already created a stater repo for the book app with the template we will use and the book data to be added Strapi which I fetched from the Google book API.

Next, we need to clone the starter GitHub repo. In the terminal, cd into the book-app directory we created earlier and type in the following lines of code:

    $ git clone -b starter https://github.com/Tammibriggs/strapi-book-app.git

    $ cd strapi-book-app

    $ npm install
Enter fullscreen mode Exit fullscreen mode

Now, when we start our app with the $ npm start command, we will see this page:

Screenshot

If we click on a book, we will be taken to the detail page which looks like this:

Screenshot

Right now, we are getting our book data from the data.js file in the src directory of the cloned app. We will be moving the book data over to Strapi and fetching it from there shortly using meilisearch and implementing infinite scrolling using the Intersection Observer API.

Adding Data to Strapi

In the data.js file in the src directory, we have over fifty-four (54) book data; let’s move them over to Strapi. To do this, we need to first allow access to the Strapi collection. Navigate to the dashboard in Settings at the sidebar. Select Roles under Users and Permissions. Click on Public, select Book, and check all checkboxes.

Screenshot

Then, click on the Save button at the top-right to save these changes.

Next, in the src/pages/Home.js add the following import:

    import axios from 'axios'
Enter fullscreen mode Exit fullscreen mode

We're able to import axios here because it was included in the starter app. Next, add the following lines of code after the books state in the Home component:

    // src/pages/Home.js
    const URL = "http://localhost:1337/api/books"

    useEffect(() => {
      sendData()
    }, [])

    const sendData = async () => {
      let fetchedData;
      const fetchCol = await axios.get(URL)
      fetchedData = fetchCol.data.data
      if (!fetchedData.length) {
        console.log('done')
        try {
          books.forEach((book) => {
            axios.post(URL,{
            data: {
              authors: book.authors,
              description: book.description,
              image: book.image,
              previewLink: book.previewLink,
              publishDate: book.publishDate,
              publisher: book.publisher,
              subtitle: book.subtitle,
              title: book.title,
            }})
          })
          console.log('done')
        } catch (error) {
          console.log(error);
        }
      } else {
        console.log("data already uploadedd")
      }
    }
Enter fullscreen mode Exit fullscreen mode

The above code checks if there is any data in our Strapi collection, and if there is not, it populates our the collection with all data in the data.js file.

Now when we head over to our Strapi dashboard and click on Content Manager in the sidebar, we see fifty-four (54) entries in our Books collection.

Screenshot

Next, we will integrate and use meilisearch to get our books data from our Strapi collection and display it, and will also implement the search functionality. To search for data, meilisearch uses a query passed to it. when the query is empty it will return all the books in our collection which we will display on the home page and when the query is not empty it returns the corresponding result.

Integrating Meilisearch

To use Meilisearch locally, we will download and run an instance of it. This can be downloaded here. Opening the downloaded application shows a terminal with the Meilisearch instance hosted on local host:

Screenshot

If we navigate in the browser to the specified URL, we will see the Meilisearch interface.

Screenshot

Next, we a different terminal cd into the book-app-backend directory and install the Strapi-meilisearch plugin with the following command:

    $ npm install strapi-plugin-meilisearch
Enter fullscreen mode Exit fullscreen mode

After this, we re-run npm run develop to rebuild our Strapi application with the new meilisearch plugin. When we open the localhost URL in our browser and log in, we will be directed to the Strapi dashboard:

Screenshot

Next, let's click on the meilisearch option on the sidebar, and in the Settings tab enter the URL for the meilisearch instance.

Screenshot

Click on save. Now, add the book collection to meilisearch in the Collections section by click on the check box:

Screenshot

With this, when we refresh the meilisearch instance, we will see the entries in our Strapi collection.

Fetching Books Data from Meilisearch

To fetch our book data in our frontend, we can either use the search routes provided for us (for example, this will fetch 30 book data: http://127.0.0.1:7700/indexes/book/search?limit=30) or we can use meilisearch package. In this tutorial, we will be using the package so we will need to first install it.

  1. In the terminal, cd into strapi-book-app and type in the following command:
    $ npm install meilisearch
Enter fullscreen mode Exit fullscreen mode
  1. Next, add the following import to the Home.js file in src/pages:
    import MeiliSearch from "meilisearch";
Enter fullscreen mode Exit fullscreen mode
  1. Next, modify the book state by replacing Allbooks with an empty array. It should look like this:
    const [books, setBooks] = useState([])
Enter fullscreen mode Exit fullscreen mode
  1. Now, add the following lines of code after the books state:
    // src/pages/Home.js
    const fetchData = async () => {
      const client = new MeiliSearch({
        host: 'http://127.0.0.1:7700',
      })
      const index = await client.getIndex('book')
      const booksData = await index.search('*')
      setBooks(booksData.hits)
    }
Enter fullscreen mode Exit fullscreen mode

The above function, when called, returns our data from the Strapi book collection received through the meilisearch instance. Notice that in the search method we are passing ***** as the query. This will fetch all our data with a limit of twenty (20) which is the default. This can be customized.

To search for a particular data we just need to pass it to the search method. We will use this to implement our search functionality.

We want the above function to be called when our app renders so we will call it in a useEffect hook. In the Home component, Modify the useEffect hook with the sendData() function to now look like this:

    // src/pages/Home.js
    useEffect(() => {
      fetchData()
      sendData()
    }, [])
Enter fullscreen mode Exit fullscreen mode

With this, the data from our Strapi book collection should now be displayed in our app. Next, let’s make sure that when a book card is clicked we are getting the details of that particular book.

To do this,

  1. Head over to the src/pages/BookDetail.js and first add the following import:
    import MeiliSearch from 'meilisearch'
Enter fullscreen mode Exit fullscreen mode
  1. Next, modify the useEffect hook to look like this:
    // src/pages/BookDetail.js
    useEffect(() => {
      const fetchData = async () => {
        const client = new MeiliSearch({
          host: 'http://127.0.0.1:7700',
        })
        const index = await client.getIndex('book')
        const bookData = await index.getDocument(params.id)
        setBook(bookData)
      }
      fetchData()
    }, [])
Enter fullscreen mode Exit fullscreen mode

With this, when we click on a book we should see the book details.

Implementing the Search Functionality

For the search functionality, when we type in a query in the search bar, it will take us to the search page attaching the query to the URL. We will get that query and pass it to the search method of meilisearch which will then return the corresponding results:

To do this,

  1. Go over to src/pages/Search.js and first add the following imports:
    // src/pages/Search.js
    import MeiliSearch from 'meilisearch'
    import {useEffect, useState} from 'react'
Enter fullscreen mode Exit fullscreen mode
  1. Next, add the following lines of code after the params variable in the Search component:
    // src/pages/Search.js
    const [books, setBooks] = useState([])

    useEffect(() => {
      const fetchData = async () => {
        const client = new MeiliSearch({
          host: 'http://127.0.0.1:7700',
        })
        const index = await client.getIndex('book')
        const booksData = await index.search(params.query)
        setBooks(booksData.hits)
      }
      fetchData()
    }, [params.query])
Enter fullscreen mode Exit fullscreen mode

The above code will return all the matching results based on the search query and set it into the books state. Now let's render the fetched results.

  1. Modify the div in the return statement to now look like this:
    // src/pages/Search.js
    <div className='searchPage wrapper'>
      <div className='books'>
        {books?.map((book) => (
          <Book
            key={book.id}
            title={book.title}
            image={book.image}
            authors={book.authors}
            publisher={book.publisher}
            publishDate={book.publishedDate}
            id={book.id}
          />
        ))}
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

With this, when we search for books in the search bar we will see the results in the search page.

Implementing Infinite Scrolling with the Intersection Observer API

For the infinite scrolling functionality, we will limit the returned book from meilisearch to fifteen then when we scroll to the bottom of our page we will fetch and append another fifteen data.

To do this, we will use the Intersection Observer API to know when we have got to the bottom of our page, then to return books in chunks of fifteen from meilisearch we will use the limit and the offset parameter which can be specified in the object passed as the second parameter of the search method.

Fetching New Data Using Intersection Observer

The Intersection Observer API monitors when an observed element is visible or when it reaches a predefined position and then it fires the callback function supplied to it. To use this API we will first create an element at the bottom of our fetched data which will be the observed element. Then, when this element is visible we will call the callback function which will be responsible for getting and pending new book data.

  1. In the Home.js file add the following imports:
    import {useRef, useCallback} from 'react'
Enter fullscreen mode Exit fullscreen mode
  1. After this, add the following lines of code after the closing tag (</div>) of the div with the className of books.
    // src/pages/Home.js
    <div className='loader' ref={observerElem}>
      {books.length !== 0 &&
        <span>{hasNextPage ? 'Loading...' : 'no books left'}</span>
      }
    </div>
Enter fullscreen mode Exit fullscreen mode

Above, we created the div element we want to observe using Intersection Observers. We have added the ref attribute so we can be able to access it directly. The above div will display **Loading…* or no books left ***depending on hasNextPage which will be a boolean state that will be true or false depending on whether there is still data to be fetched.

  1. Next, add the following line of codes after URL variable:
    // src/pages/Home.js
    const observerElem = useRef(null)
    const [hasNextPage, setHasNextPage] = useState(true)
Enter fullscreen mode Exit fullscreen mode
  1. Next, add the following lines of code after the hasNextPage state:
    // src/pages/Home.js
    const handleObserver = useCallback((entries) => {
      const [target] = entries
      if(target.isIntersecting && hasNextPage) {
        console.log('intersected')
      }
    }, [hasNextPage])

    useEffect(() => {
      const element = observerElem.current
      const option = { threshold: 0 }
      const observer = new IntersectionObserver(handleObserver, option);
      observer.observe(element)
      return () => observer.unobserve(element)
    }, [hasNextPage, handleObserver])
Enter fullscreen mode Exit fullscreen mode

The above code will detect when the observed element has entered the viewport and then call the handleObserver callback function. Now when we scroll to the bottom of our page in the browse and we check in the console **intersected* will be logged.*

Next, let’s create the function that will fetch and append book data whenever we scroll to the bottom of our page. For this we will modify the fetchData function to get fifteen new books data any time is called using the limit and offset parameter, then we will append newly fetched books to the books state.

To do this,

  1. First, add the following code after the hasNextPage state:
    // src/pages/Home.js
    const [offset, setOffset] = useState(0)
    const [lastPage, setLastPage] = useState({})
Enter fullscreen mode Exit fullscreen mode
  1. Next, modify the fetchData function to now look like this:
    // src/pages/Home.js
    const fetchData = async () => {
      const client = new MeiliSearch({
        host: 'http://127.0.0.1:7700',
      })
      const index = await client.getIndex('book')
      const booksData = await index.search('*', {
        limit: 15,
        offset: offset 
      })
      setBooks([...books,  ...booksData.hits])
      setLastPage(booksData)
    }
Enter fullscreen mode Exit fullscreen mode

Next, we need to call the fetchData() in handleObserver so that when we scroll to the bottom of the page it will be called.

  1. Modify the handleObserver function to now look like this:
    // src/pages/Home.js
    const handleObserver = useCallback((entries) => {
      const [target] = entries
      if(target.isIntersecting && hasNextPage) {
        fetchData()
      }
    }, [fetchData, hasNextPage])
Enter fullscreen mode Exit fullscreen mode
  1. Finally, add the following line of code after the fetchData function:
    // src/pages/Home.js
    useEffect(() => {
      setOffset(books.length)
      if(books.length < lastPage.nbHits){
        setHasNextPage(true)
      }else{
        setHasNextPage(false)
      }
    }, [books])
Enter fullscreen mode Exit fullscreen mode

With this, we are done implementing our infinite scrolling functionality. When we scroll to the bottom of the page, new books will be displayed.

Conclusion

In this tutorial, we learnt how to implement infinite scrolling and search functionality in Strapi using meilisearch by building a book app.

References

  1. Check out the Meilisearch documentation.

  2. Click here to view the entire source code for this tutorial.

Top comments (0)

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.