From d63ec4a42e15b1d77d3e602f3c096ad523dda908 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 05:21:28 +0800 Subject: [PATCH] fix(plugin): handle fetch errors, race conditions, and error states in library panel --- .../src/components/LibraryAppPanel.tsx | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index c4176b7..80d0974 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react" +import { useEffect, useState, useRef } from "react" import { PanelSection, PanelSectionRow, DropdownItem, staticClasses } from "@decky/ui" import { FaCheck, FaTimes, FaChartLine } from "react-icons/fa" import { @@ -56,16 +56,34 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: const [devices, setDevices] = useState([]) const [loading, setLoading] = useState(true) const [device, setDevice] = useState(hardwareSlug ?? "") // "" = all devices + const [fetchError, setFetchError] = useState("") + const reqIdRef = useRef(0) useEffect(() => { setPluginApiBaseUrl(baseUrl) let cancelled = false + const id = ++reqIdRef.current async function load() { setLoading(true) - const d = await fetchPluginGame(appId, device || null, 3) - if (!cancelled) { setData(d); setLoading(false) } - const devs = await fetchPluginDevices(appId) - if (!cancelled) setDevices(devs) + try { + const d = await fetchPluginGame(appId, device || null, 3) + if (cancelled || id !== reqIdRef.current) return + if (d.error && !d.game) { + setFetchError(d.error) + } else { + setFetchError("") + } + setData(d) + setLoading(false) + const devs = await fetchPluginDevices(appId) + if (!cancelled && id === reqIdRef.current) setDevices(devs) + } catch (e) { + if (!cancelled && id === reqIdRef.current) { + setData(null) + setFetchError("Request failed") + setLoading(false) + } + } } load() return () => { cancelled = true } @@ -89,6 +107,18 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: .map((d) => ({ label: `${d.name} (${d.count})`, data: d.slug })), ] + if (fetchError) { + return ( + + +
+ Could not load DeckyVault data. Please try again later. +
+
+
+ ) + } + if (!data || !data.game) { return (