feat: paginate repo list with infinite scroll

Load 30 repos at a time instead of 100 at once, showing data sooner.
Adds v-infinite-scroll to RepoList.vue to fetch subsequent pages on scroll.
This commit is contained in:
Julien Calixte
2026-04-05 11:56:36 +02:00
parent 3de9eb35f6
commit 006cd63388
3 changed files with 43 additions and 23 deletions

View File

@@ -1,34 +1,47 @@
import { useAsyncState } from "@vueuse/core" import { computed, ref } from "vue"
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook" import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
import { RepoBase } from "@/modules/repo/interfaces/RepoBase" import { RepoBase } from "@/modules/repo/interfaces/RepoBase"
import { getOctokit } from "@/modules/repo/services/octo" import { getOctokit } from "@/modules/repo/services/octo"
const PER_PAGE = 30
export const useRepos = () => { export const useRepos = () => {
const { username, accessToken } = useGitHubLogin() const { username, accessToken } = useGitHubLogin()
const repos = useAsyncState<RepoBase[]>(async () => {
const repos = ref<RepoBase[]>([])
const isReady = ref(false)
const currentPage = ref(0)
const totalCount = ref(0)
const loadMore = async () => {
if (!accessToken.value || !username.value) { if (!accessToken.value || !username.value) {
return [] isReady.value = true
return
} }
const octokit = await getOctokit() const octokit = await getOctokit()
const nextPage = currentPage.value + 1
const repoList = await octokit.request("GET /search/repositories", { const repoList = await octokit.request("GET /search/repositories", {
q: `user:${username.value}`, q: `user:${username.value}`,
per_page: 100 per_page: PER_PAGE,
page: nextPage
}) })
currentPage.value = nextPage
return repoList.data.items totalCount.value = repoList.data.total_count
.map((item) => ({ const newItems = repoList.data.items.map((item) => ({
id: `${item.id}`, id: `${item.id}`,
name: item.name, name: item.name,
isPrivate: item.private isPrivate: item.private
})) }))
.sort((a, b) => (a.name < b.name ? -1 : 1)) repos.value = [...repos.value, ...newItems].sort((a, b) =>
}, []) a.name < b.name ? -1 : 1
)
return { isReady.value = true
repos: repos.state,
isReady: repos.isReady
} }
const canLoadMore = computed(() => repos.value.length < totalCount.value)
loadMore()
return { repos, isReady, canLoadMore, loadMore }
} }

View File

@@ -6,7 +6,7 @@ import { RepoBase } from "@/modules/repo/interfaces/RepoBase"
export const useRepoList = () => { export const useRepoList = () => {
const { savedFavoriteRepos, addFavorite, removeFavorite } = useFavoriteRepos() const { savedFavoriteRepos, addFavorite, removeFavorite } = useFavoriteRepos()
const { repos } = useRepos() const { repos, canLoadMore, loadMore } = useRepos()
const favoriteRepos = computed(() => { const favoriteRepos = computed(() => {
return repos.value.filter((repo) => return repos.value.filter((repo) =>
@@ -38,6 +38,8 @@ export const useRepoList = () => {
favoriteRepos, favoriteRepos,
otherRepos, otherRepos,
favoriteCheckboxes, favoriteCheckboxes,
toggleCheckbox toggleCheckbox,
canLoadMore,
loadMore
} }
} }

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { vInfiniteScroll } from "@vueuse/components"
import GoBack from "@/components/GoBack.vue" import GoBack from "@/components/GoBack.vue"
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook" import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
import { useRepos } from "@/hooks/useRepos.hook" import { useRepos } from "@/hooks/useRepos.hook"
@@ -6,12 +8,15 @@ import { useRepoList } from "@/modules/repo/hooks/useRepoList.hook"
const { username } = useGitHubLogin() const { username } = useGitHubLogin()
const { isReady } = useRepos() const { isReady } = useRepos()
const { favoriteRepos, otherRepos, favoriteCheckboxes, toggleCheckbox } = const { favoriteRepos, otherRepos, favoriteCheckboxes, toggleCheckbox, canLoadMore, loadMore } =
useRepoList() useRepoList()
</script> </script>
<template> <template>
<div class="repo-list"> <div
class="repo-list"
v-infinite-scroll="[loadMore, { canLoadMore: () => canLoadMore }]"
>
<h1 class="title is-1">Repositories</h1> <h1 class="title is-1">Repositories</h1>
<go-back /> <go-back />
<div v-if="!isReady">loading...</div> <div v-if="!isReady">loading...</div>