From 0ffb84aaa6270b2b28da7d31246014eb2387a603 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sun, 15 May 2022 10:01:08 +0300 Subject: [PATCH] refactor(app): add rq mutation helpers [EE-3176] (#6923) --- app/react-tools/RootProvider.tsx | 50 +++-------------- app/react-tools/react-query.ts | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 43 deletions(-) create mode 100644 app/react-tools/react-query.ts diff --git a/app/react-tools/RootProvider.tsx b/app/react-tools/RootProvider.tsx index 2d936b40d..a7a0ff7b2 100644 --- a/app/react-tools/RootProvider.tsx +++ b/app/react-tools/RootProvider.tsx @@ -1,37 +1,18 @@ import { ReactQueryDevtools } from 'react-query/devtools'; -import { - MutationCache, - QueryCache, - QueryClient, - QueryClientProvider, -} from 'react-query'; +import { QueryClientProvider } from 'react-query'; import { UIRouterContextComponent } from '@uirouter/react-hybrid'; -import { PropsWithChildren, StrictMode, useState, useEffect } from 'react'; +import { PropsWithChildren, StrictMode } from 'react'; import { UserProvider } from '@/portainer/hooks/useUser'; import { UIStateProvider } from '@/portainer/hooks/UIStateProvider'; -import { notifyError } from '@/portainer/services/notifications'; -const queryClient = new QueryClient({ - mutationCache: new MutationCache({ - onError: (error, variable, context, mutation) => { - handleError(error, mutation.meta?.error); - }, - }), - queryCache: new QueryCache({ - onError: (error, mutation) => { - handleError(error, mutation.meta?.error); - }, - }), -}); +import { createQueryClient } from './react-query'; + +const queryClient = createQueryClient(); export function RootProvider({ children }: PropsWithChildren) { - const [showReactQueryDevtools, setShowReactQueryDevtools] = useState(false); - useEffect(() => { - if (process.env.SHOW_REACT_QUERY_DEV_TOOLS === 'true') { - setShowReactQueryDevtools(true); - } - }, []); + const showReactQueryDevtools = + process.env.SHOW_REACT_QUERY_DEV_TOOLS === 'true'; return ( @@ -46,20 +27,3 @@ export function RootProvider({ children }: PropsWithChildren) { ); } - -function handleError(error: unknown, errorMeta?: unknown) { - if (errorMeta && typeof errorMeta === 'object') { - if (!('title' in errorMeta)) { - return; - } - - const { title, message } = errorMeta as { - title: unknown; - message?: unknown; - }; - - if (typeof title === 'string') { - notifyError(title, error as Error, message as string); - } - } -} diff --git a/app/react-tools/react-query.ts b/app/react-tools/react-query.ts new file mode 100644 index 000000000..4d5896c6d --- /dev/null +++ b/app/react-tools/react-query.ts @@ -0,0 +1,95 @@ +import { + MutationCache, + MutationOptions, + QueryCache, + QueryClient, + QueryKey, + QueryOptions, +} from 'react-query'; + +import { notifyError } from '@/portainer/services/notifications'; + +export function withError(fallbackMessage?: string, title = 'Failure') { + return { + onError(error: unknown) { + notifyError(title, error as Error, fallbackMessage); + }, + }; +} + +export function withGlobalError(fallbackMessage?: string, title = 'Failure') { + return { + meta: { + error: { message: fallbackMessage, title }, + }, + }; +} + +type OptionalReadonly = T | Readonly; + +export function withInvalidate( + queryClient: QueryClient, + queryKeysToInvalidate: OptionalReadonly[] +) { + return { + onSuccess() { + return queryKeysToInvalidate.map((keys) => + queryClient.invalidateQueries(keys) + ); + }, + }; +} + +export function mutationOptions< + TData = unknown, + TError = unknown, + TVariables = void, + TContext = unknown +>(...options: MutationOptions[]) { + return mergeOptions(options); +} + +export function queryOptions< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey +>(...options: QueryOptions[]) { + return mergeOptions(options); +} + +function mergeOptions(...options: T[]) { + return options.reduce( + (acc, option) => ({ + ...acc, + ...option, + }), + {} + ); +} + +export function createQueryClient() { + return new QueryClient({ + mutationCache: new MutationCache({ + onError: (error, variable, context, mutation) => { + handleError(error, mutation.meta?.error); + }, + }), + queryCache: new QueryCache({ + onError: (error, mutation) => { + handleError(error, mutation.meta?.error); + }, + }), + }); +} + +function handleError(error: unknown, errorMeta?: unknown) { + if (errorMeta && typeof errorMeta === 'object') { + const { title = 'Failure', message } = errorMeta as { + title?: string; + message?: string; + }; + + notifyError(title, error as Error, message); + } +}