diff --git a/package-lock.json b/package-lock.json index de60774..02b1baa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "vite-react-stream-utils", "version": "0.0.0", "dependencies": { + "@tanstack/react-query": "^5.56.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -1284,6 +1285,32 @@ "win32" ] }, + "node_modules/@tanstack/query-core": { + "version": "5.56.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.56.2.tgz", + "integrity": "sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.56.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.56.2.tgz", + "integrity": "sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.56.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/package.json b/package.json index f9aa889..cc3e064 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/react-query": "^5.56.2", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/src/App.tsx b/src/App.tsx index f535ef3..f34c74e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,18 @@ -import { useContext } from "react"; +import { useContext, useMemo } from "react"; import { AuthStateContext } from "./contexts/auth-state/AuthStateContext"; import LoginButton from "./components/LoginButton"; import Chat from "./components/Chat"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; export default function App() { const authContext = useContext(AuthStateContext); + const queryClient = useMemo( + () => + new QueryClient({ + defaultOptions: { queries: { staleTime: Infinity } }, + }), + [] + ); if (!authContext) { return

Missing AuthStateContext provider?

; @@ -12,18 +20,20 @@ export default function App() { return ( <> - - {authContext.authState && ( -
-

- Hello: {authContext.authState.user.login} -

-
-

Chat Messages:

- -
-
- )} + + + {authContext.authState && ( +
+

+ Hello: {authContext.authState.user.login} +

+
+

Chat Messages:

+ +
+
+ )} +
); } diff --git a/src/components/Pronoun.tsx b/src/components/Pronoun.tsx index 6029569..6d5e997 100644 --- a/src/components/Pronoun.tsx +++ b/src/components/Pronoun.tsx @@ -8,5 +8,5 @@ export default function Pronoun({ }): ReactElement | undefined { const pronoun = usePronouns(login); - return

{pronoun}

; + return

{pronoun.data}

; } diff --git a/src/hooks/usePronouns.ts b/src/hooks/usePronouns.ts index f42cab0..10086b0 100644 --- a/src/hooks/usePronouns.ts +++ b/src/hooks/usePronouns.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; const PRONOUNS_URL = "https://api.pronouns.alejo.io/v1"; @@ -18,36 +18,27 @@ interface UserEntry { type PronounResponse = Record; +const cachedPronouns = getPronouns(); + async function getPronouns(): Promise { const response = await fetch(`${PRONOUNS_URL}/pronouns`); - - if (!response.ok) { - throw new Error( - `getUser: Bad HTTP response: ${response.status.toString()} ${ - response.statusText - }` - ); - } - return (await response.json()) as PronounResponse; } async function getUser(login: string): Promise { - const response = await fetch(`${PRONOUNS_URL}/users/${login}`); - - if (response.status === 404) { + try { + const response = await fetch(`${PRONOUNS_URL}/users/${login}`); + return (await response.json()) as UserEntry; + } catch { return null; } +} - if (!response.ok) { - throw new Error( - `getUser: Bad HTTP response: ${response.status.toString()} ${ - response.statusText - }` - ); - } - - return (await response.json()) as UserEntry; +async function getUserPronoun(login: string): Promise { + const pronouns = await cachedPronouns; + const user = await getUser(login); + const userPronoun = user && pronouns[user.pronoun_id]; + return userPronoun ? toString(userPronoun) : null; } function toString(description: PronounDescription): string { @@ -56,48 +47,9 @@ function toString(description: PronounDescription): string { : `${description.subject}/${description.object}`; } -const descriptors = new Map(); -const users = new Map(); - -// FIXME DON'T DO THIS! -(function init() { - if (descriptors.size > 0) { - return; - } - - getPronouns() - .then((pronouns) => { - Object.entries(pronouns).forEach(([pronoun, description]) => - descriptors.set(pronoun, description) - ); - }) - .catch((error: unknown) => { - console.error("PronounProvider:", error); - }); -})(); - -export function usePronouns(login: string): string | undefined { - const [pronoun, setPronoun] = useState(); - const user = useMemo(() => users.get(login), [login]); - - if (user) { - setPronoun(() => descriptors.get(user.pronoun_id)); - } - - useEffect(() => { - if (user || user === null) { - return; - } - - void getUser(login).then((entry) => { - if (entry === null) { - users.set(login, null); - return; - } - - setPronoun(() => descriptors.get(entry.pronoun_id)); - }); - }, [login, user]); - - return pronoun && toString(pronoun); +export function usePronouns(login: string) { + return useQuery({ + queryKey: ["userPronoun", login], + queryFn: () => getUserPronoun(login), + }); }