From 3726e3f1acd8319237d3c34d220324700ceeca40 Mon Sep 17 00:00:00 2001 From: Drew Hess Date: Wed, 10 Apr 2024 15:11:43 +0100 Subject: [PATCH] chore(nodejs): upgrade to React Query v5 This version has some fairly major changes, but most of it is hidden from us thanks to Orval. We have made the following notable changes, however: * `cacheTime` has been renamed to `gcTime`. As far as I can tell, there are no behavioral changes and this is simply a rename to reduce confusion. Therefore, this change should be purely cosmetic. (See https://github.com/TanStack/query/issues/4678, https://github.com/TanStack/query/discussions/1217, and https://github.com/TanStack/query/pull/4829 for reference.) * Use object-style parameters to query methods where necessary. These changes should be purely cosmetic. * React Query v5 is written in TypeScript, so we can take advantage of better types. The one place we currently do that is in the `Edit` component's `useGetProgram` return types. Note that we also use `isPending` rather than `isLoading`. The docs are a bit confusing on this point as to whether they're equivalent; see https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5#status-loading-has-been-changed-to-status-pending-and-isloading-has-been-changed-to-ispending-and-isinitialloading-has-now-been-renamed-to-isloading. Initially I used `isLoading` when porting to v5, but TypeScript was not able to narrow the `data` value to non-nil unless I use `isPending`, so I've gone with `isPending` in the end. As far as I can tell, it works the same as before. * v5's dev tools are significantly improved, including the UI, which lets you choose via a nice drop-down menu where to locate the tool pop-up. This doesn't play well with our own "dev tools" checkbox UI, so I've temporarily disabled those `DevOptions`, as the UI for them will need to be rethought. I think adding another button to the canvas is probably the way to go, but I'll leave that work for later. FYI, the migration guide is here, and it informed most of these changes: https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5 Signed-off-by: Drew Hess --- orval.config.ts | 4 +- package.json | 4 +- pnpm-lock.yaml | 75 +++++--------------- src/App.tsx | 94 ++++---------------------- src/components/ChooseSession/index.tsx | 8 ++- src/components/Edit/index.tsx | 51 +++++++------- src/primer-api/primer-api.ts | 32 ++++----- 7 files changed, 82 insertions(+), 186 deletions(-) diff --git a/orval.config.ts b/orval.config.ts index b87a5f71..5ec9bac9 100644 --- a/orval.config.ts +++ b/orval.config.ts @@ -22,8 +22,8 @@ const useQueryPost: { }) ); -// We disable caching by default, to avoid displaying stale data. -const queryOpts: UseQueryOptions = { cacheTime: 0 }; +// gc immediately, to disable showing cached data. +const queryOpts: UseQueryOptions = { gcTime: 0 }; export default defineConfig({ "primer-api": { diff --git a/package.json b/package.json index fc7cf746..2bface69 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "@hookform/resolvers": "^3.3.4", "@neodrag/react": "^2.0.4", "@orval/core": "^6.26.0", - "@tanstack/react-query": "^4.36.1", - "@tanstack/react-query-devtools": "^4.36.1", + "@tanstack/react-query": "^5.29.0", + "@tanstack/react-query-devtools": "^5.29.0", "@types/deep-equal": "^1.0.4", "@zxch3n/tidy": "github:hackworthltd/tidy#e07fdef2ae7bf593701817113dd47b4cd56c7a97", "axios": "^1.6.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84a6703f..4e3bfb8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,11 +28,11 @@ dependencies: specifier: ^6.26.0 version: 6.26.0(openapi-types@12.1.3) '@tanstack/react-query': - specifier: ^4.36.1 - version: 4.36.1(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.29.0 + version: 5.29.0(react@18.2.0) '@tanstack/react-query-devtools': - specifier: ^4.36.1 - version: 4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.29.0 + version: 5.29.0(@tanstack/react-query@5.29.0)(react@18.2.0) '@types/deep-equal': specifier: ^1.0.4 version: 1.0.4 @@ -5268,48 +5268,32 @@ packages: tailwindcss: 3.4.3(ts-node@10.9.2) dev: true - /@tanstack/match-sorter-utils@8.8.4: - resolution: {integrity: sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==} - engines: {node: '>=12'} - dependencies: - remove-accents: 0.4.2 + /@tanstack/query-core@5.29.0: + resolution: {integrity: sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww==} dev: false - /@tanstack/query-core@4.36.1: - resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} + /@tanstack/query-devtools@5.28.10: + resolution: {integrity: sha512-5UN629fKa5/1K/2Pd26gaU7epxRrYiT1gy+V+pW5K6hnf1DeUKK3pANSb2eHKlecjIKIhTwyF7k9XdyE2gREvQ==} dev: false - /@tanstack/react-query-devtools@4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-WYku83CKP3OevnYSG8Y/QO9g0rT75v1om5IvcWUwiUZJ4LanYGLVCZ8TdFG5jfsq4Ej/lu2wwDAULEUnRIMBSw==} + /@tanstack/react-query-devtools@5.29.0(@tanstack/react-query@5.29.0)(react@18.2.0): + resolution: {integrity: sha512-WLuaU6yM4KdvBimEP1Km5lM4/p1J40cMp5I5z0Mc6a8QbBUYLK8qJcGIKelfLfDp7KmEcr59tzbRTmdH/GWvzQ==} peerDependencies: - '@tanstack/react-query': ^4.36.1 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@tanstack/react-query': ^5.29.0 + react: ^18.0.0 dependencies: - '@tanstack/match-sorter-utils': 8.8.4 - '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0) + '@tanstack/query-devtools': 5.28.10 + '@tanstack/react-query': 5.29.0(react@18.2.0) react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - superjson: 1.13.3 - use-sync-external-store: 1.2.0(react@18.2.0) dev: false - /@tanstack/react-query@4.36.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} + /@tanstack/react-query@5.29.0(react@18.2.0): + resolution: {integrity: sha512-yxlhHB73jaBla6h5B6zPaGmQjokkzAhMHN4veotkPNiQ3Ac/mCxgABRZPsJJrgCTvhpcncBZcDBFxaR2B37vug==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true + react: ^18.0.0 dependencies: - '@tanstack/query-core': 4.36.1 + '@tanstack/query-core': 5.29.0 react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - use-sync-external-store: 1.2.0(react@18.2.0) dev: false /@tanstack/react-virtual@3.2.0(react-dom@18.2.0)(react@18.2.0): @@ -7398,13 +7382,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - dependencies: - is-what: 4.1.15 - dev: false - /copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} dependencies: @@ -9783,11 +9760,6 @@ packages: call-bind: 1.0.7 get-intrinsic: 1.2.4 - /is-what@4.1.15: - resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==} - engines: {node: '>=12.13'} - dev: false - /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -12377,10 +12349,6 @@ packages: unist-util-visit: 2.0.3 dev: true - /remove-accents@0.4.2: - resolution: {integrity: sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=} - dev: false - /request-light@0.4.0: resolution: {integrity: sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA==} dependencies: @@ -13132,13 +13100,6 @@ packages: ts-interface-checker: 0.1.13 dev: true - /superjson@1.13.3: - resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==} - engines: {node: '>=10'} - dependencies: - copy-anything: 3.0.5 - dev: false - /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} diff --git a/src/App.tsx b/src/App.tsx index 2e7d08d7..d752e259 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,20 +4,19 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { CookiesProvider, useCookies } from "react-cookie"; import { CookieSetOptions } from "universal-cookie"; import { v4 as uuidv4 } from "uuid"; -import { WrenchScrewdriverIcon } from "@heroicons/react/24/outline"; -import { Resizable } from "re-resizable"; import "@/index.css"; import { ChooseSession, Edit, NoMatch } from "@/components"; -import { DevOptions } from "@/components/Edit"; // This ensures that we don't unnecessarily load the tools in production. -// https://tanstack.com/query/v4/docs/react/devtools#devtools-in-production -const ReactQueryDevtoolsPanel = lazy(() => - import("@tanstack/react-query-devtools/build/lib/index.prod.js").then( +// +// Ref: +// https://tanstack.com/query/latest/docs/framework/react/devtools#devtools-in-production +const ReactQueryDevtools = lazy(() => + import("@tanstack/react-query-devtools/build/modern/production.js").then( (d) => ({ - default: d.ReactQueryDevtoolsPanel, + default: d.ReactQueryDevtools, }) ) ); @@ -37,16 +36,7 @@ const idCookieOptions = (path: string): CookieSetOptions => { const App = (): JSX.Element => { const [cookies, setCookie] = useCookies(["id"]); - const [enableDevtools, setEnableDevtools] = useState(import.meta.env.DEV); - const [devtoolsOpen, setDevtoolsOpen] = useState(false); - - const devToolsMinHeight = 250; - const devToolsMaxHeight = 500; - const [devOpts, setDevOpts] = useState({ - showIDs: false, - inlineLabels: false, - alwaysShowLabels: true, - }); + const [showDevtools, setShowDevtools] = useState(import.meta.env.DEV); useEffect(() => { if (!cookies.id) { @@ -60,47 +50,24 @@ const App = (): JSX.Element => { useEffect(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - window.toggleDevtools = - // This comment forces a line break to limit the scope of `@ts-ignore`. - () => setEnableDevtools((old) => !old); + // @ts-expect-error + window.toggleDevtools = () => setShowDevtools((old) => !old); }, []); return ( - {enableDevtools && ( + {showDevtools && ( - - {devtoolsOpen && ( - - {}} - /> - - - )} + )} } /> } /> - } /> + } /> } /> @@ -110,41 +77,4 @@ const App = (): JSX.Element => { ); }; -const DevMenu = (p: { opts: DevOptions; set: (opts: DevOptions) => void }) => ( -
-
- p.set({ ...p.opts, showIDs: e.target.checked })} - className="mr-1" - /> - -
-
- - p.set({ ...p.opts, alwaysShowLabels: e.target.checked }) - } - className="mr-1" - /> - -
-
- p.set({ ...p.opts, inlineLabels: e.target.checked })} - className="mr-1" - /> - -
-
-); - export default App; diff --git a/src/components/ChooseSession/index.tsx b/src/components/ChooseSession/index.tsx index ed40189d..5487ae90 100644 --- a/src/components/ChooseSession/index.tsx +++ b/src/components/ChooseSession/index.tsx @@ -24,7 +24,9 @@ const ChooseSession = (): JSX.Element => { const deleteSession = useDeleteSession({ mutation: { onSuccess: () => - queryClient.invalidateQueries(getGetSessionListQueryKey()), + queryClient.invalidateQueries({ + queryKey: getGetSessionListQueryKey(), + }), }, }); const { data } = useGetSessionList({ @@ -57,7 +59,9 @@ const ChooseSession = (): JSX.Element => { const newSession = useCreateSession({ mutation: { onSuccess: (newSessionID: Uuid) => { - queryClient.invalidateQueries(getGetSessionListQueryKey()); + queryClient.invalidateQueries({ + queryKey: getGetSessionListQueryKey(), + }); navigate(`/sessions/${newSessionID}`); }, }, diff --git a/src/components/Edit/index.tsx b/src/components/Edit/index.tsx index fae6342a..aa6c165c 100644 --- a/src/components/Edit/index.tsx +++ b/src/components/Edit/index.tsx @@ -62,10 +62,17 @@ export type DevOptions = { alwaysShowLabels: boolean; }; -const Edit = (devOpts: DevOptions): JSX.Element => { +const Edit = (): JSX.Element => { const params = useParams(); const sessionId = params["sessionId"]; + // Temporary until these toggles are re-enabled. + const devOpts: DevOptions = { + showIDs: false, + inlineLabels: false, + alwaysShowLabels: false, + }; + if (!sessionId) { return ( @@ -74,33 +81,24 @@ const Edit = (devOpts: DevOptions): JSX.Element => { // This hook is *technically* conditional. // But if the condition above fails, then the app is broken anyway. // eslint-disable-next-line react-hooks/rules-of-hooks - const queryRes = useGetProgram(sessionId); + const { isPending, isError, data, error } = useGetProgram(sessionId); - if (queryRes.error) { + if (isError) { return ( - + ); - } - - // This state will appear on every load, usually only very briefly, - // and we choose to just show nothing. - if (queryRes.isLoading) { + } else if (isPending) { + // This state will appear on every load, usually only very briefly, + // and we choose to just show nothing. return <>; - } - - // At this point, we have successfully received an initial program. - return ( - - ); + } else + return ( + + ); }; const AppProg = (p: { @@ -190,7 +188,10 @@ const useInvalidateOnChange = ( useEffect( () => { (async () => - await queryClient.invalidateQueries(res.queryKey, { exact: true }))(); + await queryClient.invalidateQueries({ + queryKey: res.queryKey, + exact: true, + }))(); }, // We stringify the queryKey as a poor-man's deep equality check, // since the orval generated bindings to react-query construct a diff --git a/src/primer-api/primer-api.ts b/src/primer-api/primer-api.ts index df6848a0..f671b78d 100644 --- a/src/primer-api/primer-api.ts +++ b/src/primer-api/primer-api.ts @@ -130,7 +130,7 @@ export const getGetSessionListQueryKey = (params?: GetSessionListParams,) => { } -export const useGetSessionListQueryOptions = >>, TError = ErrorType>(params?: GetSessionListParams, options?: { query?:UseQueryOptions>>, TError, TData>, } +export const useGetSessionListQueryOptions = >>, TError = ErrorType>(params?: GetSessionListParams, options?: { query?:Partial>>, TError, TData>>, } ) => { const {query: queryOptions} = options ?? {}; @@ -145,7 +145,7 @@ const {query: queryOptions} = options ?? {}; - return { queryKey, queryFn, cacheTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } + return { queryKey, queryFn, gcTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } } export type GetSessionListQueryResult = NonNullable>>> @@ -155,7 +155,7 @@ export type GetSessionListQueryError = ErrorType * @summary Get the list of sessions */ export const useGetSessionList = >>, TError = ErrorType>( - params?: GetSessionListParams, options?: { query?:UseQueryOptions>>, TError, TData>, } + params?: GetSessionListParams, options?: { query?:Partial>>, TError, TData>>, } ): UseQueryResult & { queryKey: QueryKey } => { @@ -447,7 +447,7 @@ export const getGetAvailableActionsQueryKey = (sessionId: string, export const useGetAvailableActionsQueryOptions = >>, TError = ErrorType>(sessionId: string, selection: Selection, - params: GetAvailableActionsParams, options?: { query?:UseQueryOptions>>, TError, TData>, } + params: GetAvailableActionsParams, options?: { query?:Partial>>, TError, TData>>, } ) => { const {query: queryOptions} = options ?? {}; @@ -474,7 +474,7 @@ export type GetAvailableActionsQueryError = ErrorType export const useGetAvailableActions = >>, TError = ErrorType>( sessionId: string, selection: Selection, - params: GetAvailableActionsParams, options?: { query?:UseQueryOptions>>, TError, TData>, } + params: GetAvailableActionsParams, options?: { query?:Partial>>, TError, TData>>, } ): UseQueryResult & { queryKey: QueryKey } => { @@ -647,7 +647,7 @@ export const getEvalFullQueryKey = (sessionId: string, export const useEvalFullQueryOptions = >>, TError = ErrorType>(sessionId: string, globalName: GlobalName, - params?: EvalFullParams, options?: { query?:UseQueryOptions>>, TError, TData>, } + params?: EvalFullParams, options?: { query?:Partial>>, TError, TData>>, } ) => { const {query: queryOptions} = options ?? {}; @@ -674,7 +674,7 @@ export type EvalFullQueryError = ErrorType export const useEvalFull = >>, TError = ErrorType>( sessionId: string, globalName: GlobalName, - params?: EvalFullParams, options?: { query?:UseQueryOptions>>, TError, TData>, } + params?: EvalFullParams, options?: { query?:Partial>>, TError, TData>>, } ): UseQueryResult & { queryKey: QueryKey } => { @@ -713,7 +713,7 @@ export const getGetSessionNameQueryKey = (sessionId: string,) => { } -export const useGetSessionNameQueryOptions = >>, TError = ErrorType>(sessionId: string, options?: { query?:UseQueryOptions>>, TError, TData>, } +export const useGetSessionNameQueryOptions = >>, TError = ErrorType>(sessionId: string, options?: { query?:Partial>>, TError, TData>>, } ) => { const {query: queryOptions} = options ?? {}; @@ -728,7 +728,7 @@ const {query: queryOptions} = options ?? {}; - return { queryKey, queryFn, enabled: !!(sessionId), cacheTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } + return { queryKey, queryFn, enabled: !!(sessionId), gcTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } } export type GetSessionNameQueryResult = NonNullable>>> @@ -738,7 +738,7 @@ export type GetSessionNameQueryError = ErrorType * @summary Get the specified session's name */ export const useGetSessionName = >>, TError = ErrorType>( - sessionId: string, options?: { query?:UseQueryOptions>>, TError, TData>, } + sessionId: string, options?: { query?:Partial>>, TError, TData>>, } ): UseQueryResult & { queryKey: QueryKey } => { @@ -839,7 +839,7 @@ export const getGetProgramQueryKey = (sessionId: string,) => { } -export const useGetProgramQueryOptions = >>, TError = ErrorType>(sessionId: string, options?: { query?:UseQueryOptions>>, TError, TData>, } +export const useGetProgramQueryOptions = >>, TError = ErrorType>(sessionId: string, options?: { query?:Partial>>, TError, TData>>, } ) => { const {query: queryOptions} = options ?? {}; @@ -854,7 +854,7 @@ const {query: queryOptions} = options ?? {}; - return { queryKey, queryFn, enabled: !!(sessionId), cacheTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } + return { queryKey, queryFn, enabled: !!(sessionId), gcTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } } export type GetProgramQueryResult = NonNullable>>> @@ -864,7 +864,7 @@ export type GetProgramQueryError = ErrorType * @summary Get the current program state */ export const useGetProgram = >>, TError = ErrorType>( - sessionId: string, options?: { query?:UseQueryOptions>>, TError, TData>, } + sessionId: string, options?: { query?:Partial>>, TError, TData>>, } ): UseQueryResult & { queryKey: QueryKey } => { @@ -1141,7 +1141,7 @@ export const getGetVersionQueryKey = () => { } -export const useGetVersionQueryOptions = >>, TError = ErrorType>( options?: { query?:UseQueryOptions>>, TError, TData>, } +export const useGetVersionQueryOptions = >>, TError = ErrorType>( options?: { query?:Partial>>, TError, TData>>, } ) => { const {query: queryOptions} = options ?? {}; @@ -1156,7 +1156,7 @@ const {query: queryOptions} = options ?? {}; - return { queryKey, queryFn, cacheTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } + return { queryKey, queryFn, gcTime: 0, ...queryOptions} as UseQueryOptions>>, TError, TData> & { queryKey: QueryKey } } export type GetVersionQueryResult = NonNullable>>> @@ -1166,7 +1166,7 @@ export type GetVersionQueryError = ErrorType * @summary Get the current server version */ export const useGetVersion = >>, TError = ErrorType>( - options?: { query?:UseQueryOptions>>, TError, TData>, } + options?: { query?:Partial>>, TError, TData>>, } ): UseQueryResult & { queryKey: QueryKey } => {