The never-ending scroll feature in Vue.js

For the component of cards in my project, I am trying to implement infinite scrolling with 3 cards per row. Upon reaching the end of the page, I intend to make an API call for the next page and display the subsequent set of cards. Below is my implementation code for infinite scrolling, which seems to be malfunctioning:

<template>
  <div
    class="container"
    ref="scrollContainer"
    @dragover.prevent="handleDragOverContainer"
    @drop.prevent="handleDrop"
  >
    <div class="card-container">
      <div class="card">
        <AddCourseCard />
      </div>
      <div
        class="card"
        v-for="(element, index) in products"
        :key="index"
        :draggable="true"
        @dragstart="handleDragStart(index)"
        @dragover.prevent="handleDragOverCard(index)"
        @dragend="handleDragEnd"
      >
        <CoursesCard
          :title="element.title"
          :text="element.text"
          :imageSrc="element.imageSrc"
          :productId="element.id"
        />
      </div>
    </div>
  </div>
</template>

<script>
import AddCourseCard from './AddCourseCard.vue'
import CoursesCard from './CourseCard.vue'
import { OfferService } from '../../service/index'
import { GroupState } from '../../composition/groups'
import Draggable from 'vuedraggable'

export default {
  name: 'Learning',
  components: {
    CoursesCard,
    AddCourseCard,
    draggable: Draggable,
  },
  data() {
    return {
      products: [],
      pageNo: 1, // Initialize pageNo
      limit: 10, // Set your desired limit per page
      loading: false,
      isGroupAdmin: GroupState.state.isGroupAdmin,
      dragData: null,
      draggedIndex: null,
      dropTargetIndex: null,
      reachedEnd: false,
      // Flag to prevent multiple requests while loading
      dragOptions: {
        // Specify any options for Vue.Draggable here if needed
        group: 'cards', // Add a group to allow cards to be dragged between different lists
        animation: 200, // Animation duration when items are reordered
      },
      dummyData: [
        {
          title: 'Card 1',
          text: 'Some quick example text for Card 1',
          imageSrc: 'https://picsum.photos/1600/1176/?image=25',
        },
        {
          title: 'Card 2',
          text: 'Some quick example text for Card 2',
          imageSrc: 'https://picsum.photos/1600/1176/?image=26',
        },
        {
          title: 'Card 3',
          text: 'Some quick example text for Card 3',
          imageSrc: 'https://picsum.photos/1600/1176/?image=27',
        },
        {
          title: 'Card 4',
          text: 'Some quick example text for Card 1',
          imageSrc: 'https://picsum.photos/1600/1176/?image=25',
        },
        {
          title: 'Card 5',
          text: 'Some quick example text for Card 2',
          imageSrc: 'https://picsum.photos/1600/1176/?image=26',
        },
        {
          title: 'Card 6',
          text: 'Some quick example text for Card 3',
          imageSrc: 'https://picsum.photos/1600/1176/?image=27',
        },
        // Add more dummy data items as needed
      ],
    }
  },
  mounted() {
    // Call the initial API request when the component is mounted

    this.fetchAllProducts()
    const container = this.$refs.scrollContainer
    if (container) {
      container.addEventListener('scroll', this.handleScroll)
    }
  },
  methods: {
    handleScroll() {
      const container = this.$refs.scrollContainer
      if (!container) return

      // Calculate when to trigger fetching more data based on scroll position
      const scrollPosition = container.scrollTop + container.clientHeight
      const maxScroll = container.scrollHeight

      if (
        scrollPosition >= maxScroll - 100 &&
        !this.loading &&
        !this.reachedEnd
      ) {
        // You can adjust the threshold (100 in this example) to your needs
        this.fetchNextPage()
      }
    },
    async fetchNextPage() {
      // Your existing code to fetch the next page of data
      // ...

      if (this.pagesEnd) {
        this.reachedEnd = true // Set the flag to indicate the end of data
      }
    },
    async fetchAllProducts() {
      if (!this.isGroupAdmin) {
        this.fetchDataFromAPIForGroup()
      } else {
        this.fetchDataFromAPI()
      }
    },
    async fetchDataFromAPI() {
      if (this.loading) return // Prevent multiple requests while loading

      this.loading = true
      try {
        const response = await OfferService.getOfferProductForUser({
          pageNo: this.pageNo,
          limit: this.limit,
        })

        // Append the new data to the existing products array
        this.products = [...this.products, ...response]

        // Increment the pageNo for the next request
        this.pageNo++
      } catch (error) {
        console.error('Error fetching data from API:', error)
      } finally {
        this.loading = false
      }
    },
    async fetchDataFromAPIForGroup() {
      if (this.loading) return // Prevent multiple requests while loading

      this.loading = true
      try {
        const response = await OfferService.getOfferProductForGroup({
          pageNo: this.pageNo,
          limit: this.limit,
        })

        // Append the new data to the existing products array
        this.products = [...this.products, ...response]

        // Increment the pageNo for the next request
        this.pageNo++
      } catch (error) {
        console.error('Error fetching data from API:', error)
      } finally {
        this.loading = false
      }
    },

    handleDragStart(index) {
      this.draggedIndex = index
    },
    handleDragOverCard(index) {
      this.dropTargetIndex = index
    },
    handleDragOverContainer(event) {
      // Prevent the default behavior, which is to not allow dropping
      event.preventDefault()
    },
    handleDrop() {
      if (this.draggedIndex !== null && this.dropTargetIndex !== null) {
        // Splice the dragged card out of the dummyData array
        const draggedCard = this.products.splice(this.draggedIndex, 1)[0]

        // Insert the dragged card at the drop target index
        this.products.splice(this.dropTargetIndex, 0, draggedCard)

        // Reset the dragged and drop target indices
        this.draggedIndex = null
        this.dropTargetIndex = null
      }
    },

    // Handle drag over
    handleDragOver(index) {
      if (!this.dragData) return

      // Prevent the default behavior, which is to not allow dropping
      event.preventDefault()

      // Swap the positions of the dragged item and the drop target
      const temp = this.dummyData[index]
      this.dummyData[index] = this.dragData
      this.dragData = temp
      console.log(this.dummyData)
    },

    // Handle drag end
    handleDragEnd() {
      this.dragData = null
    },
  },
}
</script>

<style scoped>
.container {
  display: flex;
  justify-content: center;
  /* Added to enable scrolling */
  overflow-y: auto;
}

.card-container {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  max-width: 1216px;
  margin: 0 auto;
}

.card {
  flex: 0 0 calc(33.33% - 10px);
  max-width: calc(33.33% - 10px);
  margin-bottom: 20px;
  box-sizing: border-box;
}

@media (max-width: 768px) {
  .card {
    flex: 0 0 calc(50% - 10px);
    max-width: calc(50% - 10px);
  }
}

@media (max-width: 480px) {
  .card {
    flex: 0 0 100%;
    max-width: 100%;
  }
}
</style>

I have included the styles as well, just in case there might be a CSS-related issue causing a blockage. Please assist me in understanding the problem here as I have been stuck trying to resolve it for hours.

Answer №1

Ensure that the scrollContainer div has a specified height. The overflow-y: auto; style will only add a scrollbar if the container has either a maximum or fixed height.

<div class="container" ref="scrollContainer" style="max-height: 70vh;" @dragover.prevent="handleDragOverContainer" @drop.prevent="handleDrop">

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to represent a recursive variable using CSS?

When it comes to the html structure: <body> <div> <div> <div> ... </div> </div> </div> </body> Is there a method to create recursive variables that utilize their parent's value: body ...

CakePHP allows developers to easily include images in their links using the `$this->Html->link` method. However, instead of generating HTML code for the image, it outputs ASCII characters

I can't seem to find the solution in the CakePHP 2.3.0 manual. I am trying to use $this->Html->link with $this->Html->image to create a clickable image. However, I'm encountering an issue where the ASCII rendering of quotes is being generated ins ...

What could be causing the conditional div to malfunction in Angular?

There are three conditional div elements on a page, each meant to be displayed based on specific conditions. <div *ngIf="isAvailable=='true'"> <form> <div class="form-group"> <label for ...

Ordering an array using Typescript within React's useEffect()

Currently, I am facing a TypeScript challenge with sorting an array of movie objects set in useEffect so that they are displayed alphabetically. While my ultimate goal is to implement various sorting functionalities based on different properties in the fut ...

Typescript is missing Zod and tRPC types throughout all projects in the monorepo, leading to the use of 'any'

Recently, I've found myself stuck in a puzzling predicament. For the last couple of weeks, I've been trying to troubleshoot why the types are getting lost within my projects housed in a monorepo. Even though my backend exposes the necessary types ...

The webpage failed to show the hamburger menu

I'm trying to create a hamburger menu and have written the following code: <header class="header"> <nav class="flex flex-jc-sb flex-ai-c"> <a href="/" class="header__logo"> <img src="im ...

Deploy a Vue.js application using Docker containerization technique

I am looking to dockerize my VueJS application. While I can successfully install and run my application on localhost using npm install and npm run serve commands on my local machine. To containerize the application, I have created a Dockerfile as follows: ...

Steps to create a submit button that is linked to a URL and includes an image

I'm attempting to convert my submit button into an image that, when clicked, redirects to another page. Currently, the submit button is functional but lacks the desired image and href functionality. <input type="submit" name="submit" alt="add" o ...

Although VSCode is functioning properly, there seems to be a discrepancy between the VSCode and the TS compiler, resulting in an

While developing my project in VSCode, I encountered an issue with accessing req.user.name from the Request object for my Node.js Express application. VSCode was indicating that 'name' does not exist in the 'user' object. To address thi ...

Output the word 'Days', 'Months', or 'Years' depending on the value of N, which represents the number of days

I am currently utilizing moment.js in conjunction with vue 3. I've implemented a function that calculates the disparity between two dates. The functionality works as expected, but here's where my query comes in. The function is structured like so ...

Error in IONIC 3: The code is unable to read the 'nativeElement' property due to an undefined value, resulting in a TypeError

I am currently learning about IONIC 3 and working on an app that utilizes the Google Maps API. However, when I try to launch my app, I encounter the following error message: inicio.html Error: Uncaught (in promise): TypeError: Cannot read property ' ...

Is the HTML templating mechanism compatible with Angular?

Trying to implement HTML templating in my Angular application, my code looks like the following: <script type='text/html' id="sampleId">...</script> However, upon loading the HTML, I noticed that the script block was not present in ...

Removing the blue border around a button upon being clicked

Whenever I press my button, an unexpected event occurs. Is there a solution to avoid this issue? Thank you for your help! :) ...

Error in Angular 2 component when loading background images using relative URLs from an external CSS skin

In my angular2 component, I am utilizing a third-party JavaScript library. The skin CSS of the component attempts to load images using relative URL paths. Since I am following a component-based architecture, I prefer to have all component dependencies enca ...

The Kendo Grid is refusing to show up within the popup window

I am new to using Angular 2 and Kendo UI. Currently, I am attempting to include a grid inside my pop-up window. While I have successfully displayed the pop-up, adding the grid has proven challenging. The grid is not appearing as expected ...

Display a division in C# MVC 4 when a boolean value is true by using @Html.DropDownList

I have multiple divs stacked on top of each other, and I want another div to appear when a certain value is selected. I'm familiar with using JavaScript for this task, but how can I achieve it using Razor? Below is a snippet of my code: <div id=" ...

Encountering an error when trying to access this.$store.state in a basic Vuex

Encountered an error with return this.$store.state.counter: The property '$store' does not exist on the type 'CreateComponentPublicInstance<{}, {}, {}, { counter(): any; }, {}, ComponentOptionsMixin, ComponentOptionsMixin, EmitsOptions, ...

Implementing type-based validations in Vue.js 3 using Yup

Greetings! I am facing a minor issue that needs to be addressed. The scenario is as follows: I need to implement validation based on the type of member. If the member type is corporate, then the tax number should be mandatory while the phone number can be ...

vuejs mounted: Unable to assign a value to an undefined variable

When I try to run the function below upon mounted, I encounter an error: "Cannot set the property 'days' of undefined" Here is my code snippet: function getDays(date) { this.days = (new Date()).getTime() / ...

Adding colors to your Vue3 and Vuetify3 theme: A Step-by-Step Guide

Currently, I am in the process of upgrading an old Vue2 app to Vue3 and I have a specific requirement to set up 3 global colors: red, green, and gray. In order to achieve this, I am implementing the following code snippet within my vuetify.js file: // Styl ...