Onjsdev

Share


How To Implement Infinite Scroll In Vue 3


By onjsdev

Nov 30th, 2023

Infinite scroll is a common feature used in web applications to improve the user experience of browsing through data mostly coming from an external API. In this article, we will explore how to implement infinite scroll using the Vue 3 Composition API.

Steps To Implement Infinite Scroll

To implement infinite scroll using the Composition API, we need to follow these steps:

  • Set up the component
  • Load initial data
  • Implement the scrolling logic
  • Load more data on scroll

Let's dive into each step in detail.

Set up the component

First, we need to set up the component using the Composition API. We can use the defineComponent function from Vue to create a new component. Inside the component, we can declare the reactive variables and functions that we need to implement infinite scroll.

<template>
  <div class="infinite-scroll">
    <div v-for="item in items" :key="item.id" class="item">
      {{ item.title }}
      <img :src="item.url" />
    </div>

    <div v-if="isLoading" class="loading">Loading...</div>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const items = ref([])
    const isLoading = ref(false)

    const loadItems = () => {
      // load items from API
    }

    return {
      items,
      isLoading,
      loadItems
    }
  }
})
</script>

In this example, we have declared a ref variable called items to store the list of items that we will display. We also have a ref variable called isLoading to track whether we are currently loading more data. Finally, we have a function called loadItems that will be responsible for fetching more data from the server.

Load initial data

Before we can implement infinite scroll, we need to load the initial data that will be displayed in the list. We can call the loadItems function when the component is mounted using the onMounted function from the Composition API.

<script>
import { defineComponent, ref, onMounted } from 'vue'

export default defineComponent({
  setup() {
    const items = ref([])
    const isLoading = ref(false)

    const loadItems = () => {
      // load items from API
    }

    onMounted(() => {
      loadItems()
    })

    return {
      items,
      isLoading,
      loadItems
    }
  }
})
</script>

Implement the scrolling logic

To implement infinite scroll, we need to detect when the user has scrolled to the bottom of the page. We can use the window object to get the height of the page, the current scroll position, and the height of the viewport.

<script>
import { defineComponent, ref, onMounted } from 'vue'

export default defineComponent({
  setup() {
    const items = ref([])
    const isLoading = ref(false)
    const page = ref(1)
    const pageSize = ref(10)

    const loadItems = () => {
      // load items from API
    }

    const onScroll = () => {
      const windowHeight = window.innerHeight
      const documentHeight = document.documentElement.scrollHeight
      const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0

      if (scrollTop + windowHeight >= documentHeight) {
        loadMore()
      }
    }

    onMounted(() => {
      loadItems()
      window.addEventListener('scroll', onScroll)
    })

    return {
      items,
      isLoading,
      page,
      pageSize,
      loadItems
    }
  }
})
</script>

In this example, we have added a new ref variable called page to keep track of the current page number, and pageSize to define the number of items that should be loaded on each request.

We have also added a new function called onScroll that will be triggered whenever the user scrolls the page. Inside this function, we calculate the height of the page, the current scroll position, and the height of the viewport. If the user has scrolled to the bottom of the page, we call the loadMore function to fetch more data.

Load more data on scroll

Now that we have implemented the scrolling logic, we need to add the logic to fetch more data from the server when the user scrolls to the bottom of the page.

<script>
import { defineComponent, ref, onMounted } from 'vue'

export default defineComponent({
  setup() {
    const items = ref([])
    const isLoading = ref(false)
    const page = ref(1)
    const pageSize = ref(10)

    const loadItems = async () => {
      isLoading.value = true

      try {
        const response = await fetch(`/api/items?page=${page.value}&pageSize=${pageSize.value}`)
        const newItems = await response.json()

        if (newItems.length) {
          items.value = [...items.value, ...newItems]
          page.value++
        }
      } catch (error) {
        console.error(error)
      }

      isLoading.value = false
    }

    const loadMore = () => {
      if (!isLoading.value) {
        loadItems()
      }
    }

    const onScroll = () => {
      const windowHeight = window.innerHeight
      const documentHeight = document.documentElement.scrollHeight
      const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0

      if (scrollTop + windowHeight >= documentHeight) {
        loadMore()
      }
    }

    onMounted(() => {
      loadItems()
      window.addEventListener('scroll', onScroll)
    })

    return {
      items,
      isLoading,
      page,
      pageSize,
      loadItems
    }
  }
})
</script>

In this example, we have updated the loadItems function to make an API request to fetch data from the server. We also added the isLoading variable to track whether we are currently loading data, and updated the onScroll function to call the new loadMore function when the user scrolls to the bottom of the page.

The loadMore function checks whether we are currently loading data, and if not, it calls the loadItems function to fetch more data from the server. When new data is returned from the server, we added it to the existing list of items using the spread operator and update the page variable to fetch the next page of data.

Finally, we update the isLoading variable to false when the data is loaded or an error occurs.

Bonus: An Example Of Vue Infinite Scroll With JSONPlaceHolder Fake API

In the previous example, we used a hypothetical API endpoint to fetch data. Here's an updated example that uses the JSONPlaceholder API to fetch data instead.

<template>
  <div class="infinite-scroll">
    <div v-for="item in items" :key="item.id" class="item">
      {{ item.title }}
      <img :src="item.url" />
    </div>

    <div v-if="isLoading" class="loading">Loading...</div>
  </div>
</template>

<script>
import { defineComponent, ref, onMounted } from "vue";

export default defineComponent({
  setup() {
    const items = ref([]);
    const isLoading = ref(false);
    const page = ref(1);
    const pageSize = ref(10);

    const loadItems = async () => {
      isLoading.value = true;

      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/photos?_page=${page.value}&_limit=${pageSize.value}`
        );
        const newItems = await response.json();

        console.log(newItems);

        if (newItems.length) {
          items.value = [...items.value, ...newItems];
          page.value++;
        }
      } catch (error) {
        console.error(error);
      }

      isLoading.value = false;
    };

    const loadMore = () => {
      if (!isLoading.value) {
        loadItems();
      }
    };

    const onScroll = () => {
      const windowHeight = window.innerHeight;
      const documentHeight = document.documentElement.scrollHeight;
      const scrollTop =
        window.pageYOffset ||
        document.documentElement.scrollTop ||
        document.body.scrollTop ||
        0;

      if (scrollTop + windowHeight >= documentHeight) {
        loadMore();
      }
    };

    onMounted(() => {
      loadItems();
      window.addEventListener("scroll", onScroll);
    });

    return {
      items,
      isLoading,
      page,
      pageSize,
      loadItems,
    };
  },
});
</script>

<style>
.item {
  display: flex;
  flex-direction: column;
  width: 500px;
  margin: auto;
}
</style>

In this example, we are using the fetch function to make a request to the https://jsonplaceholder.typicode.com/photos endpoint. We are passing in two query parameters, _page and _limit, to specify which page of data to fetch and how many items to fetch per page.

When the loadItems function is called, it sets the isLoading variable to true, fetches data from the API endpoint, and then sets isLoading to false when the data is loaded. If an error occurs while fetching data, it logs the error to the console.

The loadMore function is similar to the previous example, and checks whether we are currently loading data before calling the loadItems function. Finally, the onScroll function checks whether the user has scrolled to the bottom of the page and calls the loadMore function if necessary.

By using a real API, we can test our infinite scroll implementation and see how it performs with real data.

Conclusion

And that's it! We have successfully implemented infinite scroll in a Vue 3 application using Composition API. By using reactive ref variables and functions, you can create own a reusable solution that can be easily adapted to other components of your application.

Thank you for reading.