refactor: removing useAsyncData for tanstack query (#5262)

* refactor: most places with useAsyncData replaced with tanstack query

* refactor report list and report view

* refactor organization page to use tanstack query

* fix types

* refactor collection page and include proper loading state

* fix followed projects proper loading state

* fix 404 handling

* fix organization loading and 404 states

* pnpm prepr

* refactor: remove useAsyncData on newsletter button

* refactor: remove useAsyncData on auth globals fetch

* refactor: settings/billing/index.vue to useQuery instead of useAsyncData

* refactor: user page to remove useAsyncData

* pnpm prepr

* fix reports pages

* fix notifications page

* fix billing page cannot read properties of null and prop warnings

* fix refresh causing 404 by removing useBaseFetch and use api-client

* fix stale data after removing organization from project

* pnpm prepr

* fix news erroring in build

* fix: project page loads header only after content

* fix: user page tanstack problems (start on migrating away from useBaseFetch)

* fix: start swapping useBaseFetch usages to api-client

* Revert "fix: start swapping useBaseFetch usages to api-client"

This reverts commit 3df3fab11d535159132b1288dd7cacc38282b553.

* fix: remove debug logging

* fix: lint

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
Truman Gao
2026-03-16 12:10:29 -07:00
committed by GitHub
parent d0c7575a23
commit 681ae5d1d8
53 changed files with 1686 additions and 1079 deletions
@@ -70,6 +70,7 @@ import {
useVIntl,
} from '@modrinth/ui'
import type { AffiliateLink } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
const createModal = useTemplateRef<typeof AffiliateLinkCreateModal>('createModal')
const revokeModal = useTemplateRef<typeof ConfirmModal>('revokeModal')
@@ -83,11 +84,12 @@ const { formatMessage } = useVIntl()
const {
data: affiliateLinks,
error,
refresh,
} = await useAsyncData(
'affiliateLinks',
() => useBaseFetch('affiliate', { method: 'GET', internal: true }) as Promise<AffiliateLink[]>,
)
refetch,
} = useQuery({
queryKey: ['affiliate'],
queryFn: () =>
useBaseFetch('affiliate', { method: 'GET', internal: true }) as Promise<AffiliateLink[]>,
})
const filterQuery = ref('')
const creatingLink = ref(false)
@@ -116,7 +118,7 @@ async function createAffiliateCode(data: { sourceName: string }) {
internal: true,
})
await refresh()
await refetch()
createModal.value?.close()
} catch (err) {
handleError(err)
@@ -145,7 +147,7 @@ async function confirmRevokeAffiliateLink() {
internal: true,
})
await refresh()
await refetch()
revokeModal.value?.hide()
revokingTitle.value = null
revokingId.value = null
@@ -5,6 +5,8 @@
</template>
<script setup>
import { useQuery } from '@tanstack/vue-query'
import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue'
definePageMeta({
@@ -18,7 +20,9 @@ useHead({
const auth = await useAuth()
const id = auth.value?.user?.id
const { data: projects } = await useAsyncData(`user/${id}/projects`, () =>
useBaseFetch(`user/${id}/projects`),
)
const { data: projects } = useQuery({
queryKey: computed(() => ['user', id, 'projects']),
queryFn: () => useBaseFetch(`user/${id}/projects`),
enabled: computed(() => !!id),
})
</script>
@@ -159,8 +159,10 @@ import {
useCompactNumber,
useVIntl,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
import { useBaseFetch } from '~/composables/fetch.js'
const { formatMessage } = useVIntl()
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
@@ -221,9 +223,10 @@ if (import.meta.client) {
const filterQuery = ref('')
const { data: collections } = await useAsyncData(`user/${auth.value.user.id}/collections`, () =>
useBaseFetch(`user/${auth.value.user.id}/collections`, { apiVersion: 3 }),
)
const { data: collections } = useQuery({
queryKey: ['user', auth.value.user.id, 'collections'],
queryFn: () => useBaseFetch(`user/${auth.value.user.id}/collections`, { apiVersion: 3 }),
})
const route = useNativeRoute()
const router = useNativeRouter()
+20 -18
View File
@@ -35,7 +35,7 @@
:auth="auth"
raised
compact
@update:notifications="() => refresh()"
@update:notifications="() => refetch()"
/>
<nuxt-link
v-if="extraNotifs > 0"
@@ -98,6 +98,7 @@
<script setup>
import { ChevronRightIcon, HistoryIcon } from '@modrinth/assets'
import { Avatar } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import NotificationItem from '~/components/ui/NotificationItem.vue'
import { fetchExtraNotificationData, groupNotifications } from '~/helpers/platform-notifications.ts'
@@ -108,11 +109,11 @@ useHead({
const auth = await useAuth()
const [{ data: projects }] = await Promise.all([
useAsyncData(`user/${auth.value.user.id}/projects`, () =>
useBaseFetch(`user/${auth.value.user.id}/projects`),
),
])
const { data: projects } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'projects']),
queryFn: async () => await useBaseFetch(`user/${auth.value?.user?.id}/projects`),
placeholderData: [],
})
const downloadsProjectCount = computed(
() => projects.value.filter((project) => project.downloads > 0).length,
@@ -121,23 +122,24 @@ const followersProjectCount = computed(
() => projects.value.filter((project) => project.followers > 0).length,
)
const { data, refresh } = await useAsyncData(async () => {
const notifications = await useBaseFetch(`user/${auth.value.user.id}/notifications`)
const { data, refetch } = useQuery({
queryKey: computed(() => ['user', auth.value?.user?.id, 'notifications']),
queryFn: async () => {
const notifications = await useBaseFetch(`user/${auth.value?.user?.id}/notifications`)
const filteredNotifications = notifications.filter((notif) => !notif.read)
const slice = filteredNotifications.slice(0, 30) // send first 30 notifs to be grouped before trimming to 3
const filteredNotifications = notifications.filter((notif) => !notif.read)
const slice = filteredNotifications.slice(0, 30)
return fetchExtraNotificationData(slice).then((notifications) => {
notifications = groupNotifications(notifications).slice(0, 3)
return { notifications, extraNotifs: filteredNotifications.length - slice.length }
})
return fetchExtraNotificationData(slice).then((notifications) => {
notifications = groupNotifications(notifications).slice(0, 3)
return { notifications, extraNotifs: filteredNotifications.length - slice.length }
})
},
enabled: computed(() => !!auth.value?.user?.id),
})
const notifications = computed(() => {
if (data.value === null) {
return []
}
return data.value.notifications
return data.value?.notifications ?? []
})
const extraNotifs = computed(() => (data.value ? data.value.extraNotifs : 0))
@@ -29,7 +29,7 @@
:format-label="(x) => (x === 'all' ? 'All' : formatProjectType(x).replace('_', ' ') + 's')"
:capitalize="false"
/>
<p v-if="pending">Loading notifications...</p>
<p v-if="isPending">Loading notifications...</p>
<template v-else-if="error">
<p>Error loading notifications:</p>
<pre>
@@ -45,7 +45,7 @@
:notification="notification"
:auth="auth"
raised
@update:notifications="() => refresh()"
@update:notifications="() => refetch()"
/>
</template>
<p v-else>You don't have any unread notifications.</p>
@@ -59,6 +59,7 @@
import { CheckCheckIcon, HistoryIcon } from '@modrinth/assets'
import { Button, Chips, Pagination } from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
import NotificationItem from '~/components/ui/NotificationItem.vue'
@@ -81,11 +82,19 @@ const selectedType = ref('all')
const page = ref(1)
const perPage = ref(50)
const { data, pending, error, refresh } = await useAsyncData(
async () => {
const { data, isPending, error, refetch } = useQuery({
queryKey: computed(() => [
'user',
auth.value?.user?.id,
'notifications',
page.value,
history.value,
selectedType.value,
]),
queryFn: async () => {
const pageNum = page.value - 1
const showRead = history.value
const notifications = await useBaseFetch(`user/${auth.value.user.id}/notifications`)
const notifications = await useBaseFetch(`user/${auth.value?.user?.id}/notifications`)
const typesInFeed = [
...new Set(notifications.filter((n) => showRead || !n.read).map((n) => n.type)),
@@ -107,8 +116,9 @@ const { data, pending, error, refresh } = await useAsyncData(
hasRead: notifications.some((n) => n.read),
}))
},
{ watch: [page, history, selectedType] },
)
enabled: computed(() => !!auth.value?.user?.id),
placeholderData: { notifications: [], notifTypes: [], pages: 1, hasRead: false },
})
const notifications = computed(() =>
data.value ? groupNotifications(data.value.notifications, history.value) : [],
@@ -130,7 +140,7 @@ async function readAll() {
])
await markAsRead(ids)
await refresh()
await refetch()
}
function changePage(newPage) {
@@ -51,6 +51,7 @@
<script setup>
import { PlusIcon, UsersIcon } from '@modrinth/assets'
import { Avatar } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
import { useAuth } from '~/composables/auth.js'
@@ -60,12 +61,13 @@ const createOrgModal = ref(null)
const auth = await useAuth()
const uid = computed(() => auth.value.user?.id || null)
const { data: orgs, error } = useAsyncData('organizations', () => {
if (!uid.value) return Promise.resolve(null)
return useBaseFetch('user/' + uid.value + '/organizations', {
apiVersion: 3,
})
const { data: orgs, error } = useQuery({
queryKey: computed(() => ['user', uid.value, 'organizations']),
queryFn: () =>
useBaseFetch('user/' + uid.value + '/organizations', {
apiVersion: 3,
}),
enabled: computed(() => !!uid.value),
})
const sortedOrgs = computed(() =>
@@ -261,6 +261,7 @@
<script setup lang="ts">
import { ArrowUpRightIcon, InProgressIcon, UnknownIcon } from '@modrinth/assets'
import { defineMessages, useFormatDateTime, useFormatMoney, useVIntl } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import { Tooltip } from 'floating-vue'
@@ -356,9 +357,9 @@ const messages = defineMessages({
},
})
const { data: userBalance, refresh: refreshUserBalance } = await useAsyncData(
`payout/balance`,
async () => {
const { data: userBalance, refetch: refreshUserBalance } = useQuery({
queryKey: ['payout', 'balance'],
queryFn: async () => {
const response = (await useBaseFetch(`payout/balance`, {
apiVersion: 3,
})) as UserBalanceResponse
@@ -370,28 +371,33 @@ const { data: userBalance, refresh: refreshUserBalance } = await useAsyncData(
pending: Number(response.pending),
}
},
)
})
const { data: payouts, refresh: refreshPayouts } = await useAsyncData(`payout/history`, () =>
useBaseFetch(`payout/history`, {
apiVersion: 3,
}),
)
const { data: payouts, refetch: refreshPayouts } = useQuery({
queryKey: ['payout', 'history'],
queryFn: () =>
useBaseFetch(`payout/history`, {
apiVersion: 3,
}),
})
const userCountry = useUserCountry()
const { data: preloadedPaymentMethods } = await useAsyncData(`payout/methods-preload`, async () => {
const defaultCountry = userCountry.value || 'US'
try {
return {
country: defaultCountry,
methods: (await useBaseFetch('payout/methods', {
apiVersion: 3,
query: { country: defaultCountry },
})) as PayoutMethod[],
const { data: preloadedPaymentMethods } = useQuery({
queryKey: computed(() => ['payout', 'methods-preload', userCountry.value]),
queryFn: async () => {
const defaultCountry = userCountry.value || 'US'
try {
return {
country: defaultCountry,
methods: (await useBaseFetch('payout/methods', {
apiVersion: 3,
query: { country: defaultCountry },
})) as PayoutMethod[],
}
} catch {
return null
}
} catch {
return null
}
},
})
const sortedPayouts = computed(() => {
@@ -67,7 +67,7 @@
v-for="transaction in transactions"
:key="transaction.id || transaction.created"
:transaction="transaction"
@cancelled="refresh"
@cancelled="refetch"
/>
</div>
</div>
@@ -97,6 +97,7 @@ import {
useVIntl,
} from '@modrinth/ui'
import { capitalizeString } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
@@ -116,11 +117,13 @@ useHead({
title: 'Transaction history - Modrinth',
})
const { data: transactions, refresh } = await useAsyncData(`payout-history`, () =>
useBaseFetch(`payout/history`, {
apiVersion: 3,
}),
)
const { data: transactions, refetch } = useQuery({
queryKey: ['payout', 'history'],
queryFn: () =>
useBaseFetch(`payout/history`, {
apiVersion: 3,
}),
})
const allTransactions = computed(() => {
if (!transactions.value) return []