How to Build a Book App With Infinite Scrolling and Meilisearch Strapi Plugin in React
Tags: strapi, react, javascript, webdev
Published: 2022-08-08 09:05:00

---
canonical_url: https://strapi.io/blog/build-a-book-app-with-infinite-scrolling-and-meilisearch-strapi-plugin-in-react?utm_campaign=Strapi%20Blog&utm_source=devto&utm_medium=blog
---
_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)](https://strapi.io) 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:

```bash
    $ mkdir book-app
    
    $ cd book-app
    
    $ npx create-strapi-app@latest book-app-backend --quickstart
```
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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654624071406_welcom-page.png)


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


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654629809726_welcome-page.png)


### 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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654630682070_collectionPage.png)


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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654631264221_collectionName.png)


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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654631506946_fields.png)


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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654632454854_book-coll.png)


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](https://developers.google.com/books/docs/v1/using).

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:

```bash
    $ git clone -b starter https://github.com/Tammibriggs/strapi-book-app.git
    
    $ cd strapi-book-app
    
    $ npm install
```
Now, when we start our app with the `$ npm start` command, we will see this page:


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654640521946_book-app.png)


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


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654640667710_book-detail.png)


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](https://developer.mozilla.org/en-US/docs/Web/API/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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654647403372_permissions.png)


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:

```js
    import axios from 'axios'
```
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:

```js
    // 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")
      }
    }
```
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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654654333921_enteries.png)


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](https://docs.meilisearch.com/learn/getting_started/installation.html#download-and-launch). Opening the downloaded application shows a terminal with the Meilisearch instance hosted on local host:


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654657137393_melisearch.png)


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


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654657167250_meli-instance.png)


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

```bash
    $ npm install strapi-plugin-meilisearch
```
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](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654658096746_dashboard1.png)


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


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654660744262_settings-meli.png)


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


![Screenshot](https://paper-attachments.dropbox.com/s_6D69C744A4E8559A484EA00D45973C98B2ED1BF9C028C664DDD1454E2090979E_1654661004004_collection-melier.png)


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](https://docs.meilisearch.com/reference/api/search.html#search-in-an-index-with-post-route) 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:

```bash
    $ npm install meilisearch
```
2. Next, add the following import to the **Home.js** file in **src/pages:**

```bash
    import MeiliSearch from "meilisearch";
```
3. Next, modify the book state by replacing **Allbooks** with an empty array. It should look like this:

```js
    const [books, setBooks] = useState([])
```
4. Now, add the following lines of code after the `books` state:

```js
    // 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)
    }
```
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](https://docs.meilisearch.com/reference/api/search.html#limit).

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: 

```js
    // src/pages/Home.js
    useEffect(() => {
      fetchData()
      sendData()
    }, [])
```
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: 

```js
    import MeiliSearch from 'meilisearch'
```
2. Next, modify the `useEffect` hook to look like this:

```js
    // 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()
    }, [])
```
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:

```js
    // src/pages/Search.js
    import MeiliSearch from 'meilisearch'
    import {useEffect, useState} from 'react'
```
2. Next, add the following lines of code after the `params` variable in the `Search` component: 

```js
    // 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])
```
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. 
3. Modify the div in the return statement to now look like this:

```js
    // 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>
```
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](https://developer.mozilla.org/en-US/docs/Web/API/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](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) 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:

```js
    import {useRef, useCallback} from 'react'
```
2. After this, add the following lines of code after the closing tag (`</div>`) of the `div` with the className of **books**.

```js
    // src/pages/Home.js
    <div className='loader' ref={observerElem}>
      {books.length !== 0 &&
        <span>{hasNextPage ? 'Loading...' : 'no books left'}</span>
      }
    </div>
```
*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 **n****o** **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.*

3. Next, add the following line of codes after `URL` variable:

```js
    // src/pages/Home.js
    const observerElem = useRef(null)
    const [hasNextPage, setHasNextPage] = useState(true)
```

4. Next, add the following lines of code after the `hasNextPage` state:

```js
    // 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])
```
*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:

```js
    // src/pages/Home.js
    const [offset, setOffset] = useState(0)
    const [lastPage, setLastPage] = useState({})
```

2. Next, modify the fetchData function to now look like this:

```js
    // 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)
    }
```
Next, we need to call the `fetchData()` in `handleObserver` so that when we scroll to the bottom of the page it will be called. 

3. Modify the `handleObserver` function to now look like this:

```js
    // src/pages/Home.js
    const handleObserver = useCallback((entries) => {
      const [target] = entries
      if(target.isIntersecting && hasNextPage) {
        fetchData()
      }
    }, [fetchData, hasNextPage])
```
4. Finally, add the following line of code after the `fetchData` function:

```js
    // src/pages/Home.js
    useEffect(() => {
      setOffset(books.length)
      if(books.length < lastPage.nbHits){
        setHasNextPage(true)
      }else{
        setHasNextPage(false)
      }
    }, [books])
```
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.](https://docs.meilisearch.com/reference/api/search.html#search-in-an-index-with-post-route)

2. [Click here to view the entire source code for this tutorial.](https://github.com/Tammibriggs/strapi-book-app)