diff --git a/src/components/ChatModal.tsx b/src/components/ChatModal.tsx index 0f589a43..8525afc2 100644 --- a/src/components/ChatModal.tsx +++ b/src/components/ChatModal.tsx @@ -527,12 +527,12 @@ export function ChatModal({ onClose }: { onClose: () => void }) { return out; }, [messages]); - const typingLabel = useMemo(() => { - const names = typingOthers.map((u) => u.username); - if (names.length === 1) return t`${names[0]} is typing…`; - if (names.length === 2) return t`${names[0]} and ${names[1]} are typing…`; - return t`Several people are typing…`; - }, [typingOthers]); + const typingNames = typingOthers.map((u) => u.username); + const typingLabel = typingNames.length === 1 + ? t`${typingNames[0]} is typing…` + : typingNames.length === 2 + ? t`${typingNames[0]} and ${typingNames[1]} are typing…` + : t`Several people are typing…`; return ( diff --git a/src/components/DumpCreateModal.tsx b/src/components/DumpCreateModal.tsx index 4bfcadd1..87c25ab5 100644 --- a/src/components/DumpCreateModal.tsx +++ b/src/components/DumpCreateModal.tsx @@ -333,7 +333,8 @@ export function DumpCreateModal( const toggleCategory = (id: string) => setSelectedCategoryIds((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) next.delete(id); + else next.add(id); return next; }); diff --git a/src/components/Thumbnail.tsx b/src/components/Thumbnail.tsx index 2d691da4..9ce0a9a6 100644 --- a/src/components/Thumbnail.tsx +++ b/src/components/Thumbnail.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { type ComponentProps, useState } from "react"; import { canProxyImage, proxyImageUrl } from "../utils/proxyImage.ts"; import ThumbnailPlaceholder from "./ThumbnailPlaceholder.tsx"; @@ -9,12 +9,7 @@ interface ThumbnailProps { className?: string; loading?: "lazy" | "eager"; /** Passed through to the placeholder when there's nothing to show. */ - placeholder: { - url?: string; - accentColor?: string; - faviconUrl?: string; - siteName?: string; - }; + placeholder: Omit, "className">; /** Extra class for the placeholder element only. */ placeholderClassName?: string; } diff --git a/src/contexts/CategoriesProvider.tsx b/src/contexts/CategoriesProvider.tsx index 3a82cc93..484e4e85 100644 --- a/src/contexts/CategoriesProvider.tsx +++ b/src/contexts/CategoriesProvider.tsx @@ -10,22 +10,30 @@ import { type RawCategory, } from "../model.ts"; +async function fetchCategories( + signal?: AbortSignal, +): Promise { + const res = await fetch(`${API_URL}/api/categories`, { signal }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const body = await res.json(); + if (!body.success) return null; + return (body.data as RawCategory[]).map(deserializeCategory); +} + export function CategoriesProvider({ children }: { children: ReactNode }) { const [categories, setCategories] = useState([]); const [isLoaded, setIsLoaded] = useState(false); - const load = useCallback(async (signal?: AbortSignal) => { - try { - const res = await fetch(`${API_URL}/api/categories`, { signal }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - const body = await res.json(); - if (!body.success) return; - setCategories((body.data as RawCategory[]).map(deserializeCategory)); - setIsLoaded(true); - } catch (err) { - if ((err as Error).name !== "AbortError") setIsLoaded(true); - } - }, []); + const load = useCallback((signal?: AbortSignal) => + fetchCategories(signal) + .then((data) => { + if (!data) return; + setCategories(data); + setIsLoaded(true); + }) + .catch((err) => { + if ((err as Error).name !== "AbortError") setIsLoaded(true); + }), []); useEffect(() => { const controller = new AbortController(); diff --git a/src/pages/DumpEdit.tsx b/src/pages/DumpEdit.tsx index 5b2357c3..e619a212 100644 --- a/src/pages/DumpEdit.tsx +++ b/src/pages/DumpEdit.tsx @@ -304,7 +304,8 @@ function DumpEditForm({ const toggleCategory = (id: string) => setSelectedCategoryIds((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) next.delete(id); + else next.add(id); return next; }); const form = useApiForm({ diff --git a/src/pages/index/FollowedFeed.tsx b/src/pages/index/FollowedFeed.tsx index 4a69c5b1..4d562e28 100644 --- a/src/pages/index/FollowedFeed.tsx +++ b/src/pages/index/FollowedFeed.tsx @@ -241,7 +241,7 @@ export function FollowedFeed({ }) ); } - }, [token, usersState.status, playlistsState.status]); + }, [token, usersState.status, playlistsState.status, catQuery]); // Scroll save useScrollSave( @@ -298,7 +298,7 @@ export function FollowedFeed({ s.status === "loaded" ? { ...s, loadingMore: false } : s ) ); - }, [usersState, token]); + }, [usersState, token, catQuery]); const loadMorePlaylists = useCallback(() => { if ( @@ -333,7 +333,7 @@ export function FollowedFeed({ s.status === "loaded" ? { ...s, loadingMore: false } : s ) ); - }, [playlistsState, token]); + }, [playlistsState, token, catQuery]); return (