From dc1938768b58addce2b1dc8cce63028981b19537 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 10:52:14 +0100 Subject: [PATCH 001/103] Upgrade react-query to v5 --- examples/crm/package.json | 4 +- examples/demo/package.json | 4 +- examples/no-code/package.json | 2 +- examples/simple/package.json | 4 +- examples/tutorial/package.json | 2 +- package.json | 4 +- packages/create-react-admin/package.json | 4 +- packages/ra-core/package.json | 8 +- packages/ra-core/src/auth/WithPermissions.tsx | 2 +- packages/ra-core/src/auth/useAuthState.ts | 80 ++++--- packages/ra-core/src/auth/useAuthenticated.ts | 14 +- .../src/auth/useGetIdentity.stories.tsx | 2 +- packages/ra-core/src/auth/useGetIdentity.ts | 89 +++---- .../ra-core/src/auth/useHandleAuthCallback.ts | 74 +++--- packages/ra-core/src/auth/useLogout.ts | 2 +- packages/ra-core/src/auth/usePermissions.ts | 65 +++--- .../button/useDeleteWithConfirmController.tsx | 8 +- .../button/useDeleteWithUndoController.tsx | 8 +- .../controller/create/useCreateController.ts | 12 +- .../src/controller/edit/useEditController.ts | 16 +- .../field/useReferenceArrayFieldController.ts | 7 +- .../field/useReferenceManyFieldController.ts | 2 +- .../field/useReferenceOneFieldController.tsx | 13 +- .../input/useReferenceArrayInputController.ts | 23 +- .../input/useReferenceInputController.ts | 23 +- .../list/useInfiniteListController.ts | 33 ++- .../src/controller/list/useListController.ts | 23 +- .../src/controller/saveContext/SaveContext.ts | 12 +- .../src/controller/show/useShowController.ts | 6 +- .../src/controller/usePrevNextController.ts | 39 ++-- .../ra-core/src/controller/useReference.ts | 6 +- .../ra-core/src/core/CoreAdminContext.tsx | 4 +- packages/ra-core/src/core/CoreAdminRoutes.tsx | 3 +- packages/ra-core/src/core/Resource.tsx | 3 +- .../src/core/ResourceDefinitionContext.tsx | 7 +- .../useConfigureAdminRouterFromChildren.tsx | 22 +- .../ra-core/src/dataProvider/useCreate.ts | 75 +++--- .../useDelete.optimistic.stories.tsx | 2 +- .../useDelete.pessimistic.stories.tsx | 2 +- .../ra-core/src/dataProvider/useDelete.ts | 196 ++++++++-------- .../useDelete.undoable.stories.tsx | 2 +- .../ra-core/src/dataProvider/useDeleteMany.ts | 194 ++++++++------- .../ra-core/src/dataProvider/useGetList.ts | 73 +++--- .../ra-core/src/dataProvider/useGetMany.ts | 100 +++++--- .../src/dataProvider/useGetManyAggregate.ts | 77 +++--- .../src/dataProvider/useGetManyReference.ts | 62 +++-- .../ra-core/src/dataProvider/useGetOne.ts | 34 ++- .../src/dataProvider/useInfiniteGetList.ts | 122 ++++++---- .../src/dataProvider/useIsDataLoaded.ts | 2 +- .../ra-core/src/dataProvider/useLoading.ts | 2 +- .../ra-core/src/dataProvider/useRefresh.ts | 2 +- .../useUpdate.optimistic.stories.tsx | 2 +- .../useUpdate.pessimistic.stories.tsx | 2 +- .../ra-core/src/dataProvider/useUpdate.ts | 198 ++++++++-------- .../useUpdate.undoable.stories.tsx | 2 +- .../ra-core/src/dataProvider/useUpdateMany.ts | 210 ++++++++--------- .../ra-core/src/form/useUnique.stories.tsx | 2 +- packages/ra-input-rich-text/package.json | 2 +- packages/ra-no-code/package.json | 6 +- .../src/ui/ImportResourceDialog.tsx | 2 +- packages/ra-ui-materialui/package.json | 4 +- .../button/BulkDeleteWithConfirmButton.tsx | 2 +- .../src/button/BulkDeleteWithUndoButton.tsx | 2 +- .../button/BulkUpdateWithConfirmButton.tsx | 2 +- .../src/button/BulkUpdateWithUndoButton.tsx | 2 +- .../src/button/DeleteButton.tsx | 2 +- .../src/button/DeleteWithConfirmButton.tsx | 2 +- .../src/button/DeleteWithUndoButton.tsx | 2 +- .../src/button/PrevNextButtons.stories.tsx | 2 +- .../src/button/SaveButton.tsx | 2 +- .../src/button/UpdateWithConfirmButton.tsx | 2 +- .../src/button/UpdateWithUndoButton.tsx | 2 +- .../src/field/ReferenceArrayField.tsx | 2 +- .../src/field/ReferenceField.tsx | 2 +- .../src/field/ReferenceManyCount.stories.tsx | 2 +- .../src/field/ReferenceOneField.tsx | 2 +- .../form/SimpleFormConfigurable.stories.tsx | 2 +- .../src/input/ReferenceInput.stories.tsx | 2 +- .../src/layout/AppBar.stories.tsx | 2 +- .../src/layout/Layout.stories.tsx | 2 +- .../src/list/Count.stories.tsx | 2 +- packages/ra-ui-materialui/src/types.ts | 2 +- yarn.lock | 220 +++++------------- 83 files changed, 1185 insertions(+), 1082 deletions(-) diff --git a/examples/crm/package.json b/examples/crm/package.json index 9d42788d91b..fbff460de97 100644 --- a/examples/crm/package.json +++ b/examples/crm/package.json @@ -14,7 +14,7 @@ "lodash": "~4.17.5", "prop-types": "^15.7.2", "ra-data-fakerest": "^4.12.0", - "react": "^17.0.0", + "react": "^18.0.0", "react-admin": "^4.12.0", "react-dom": "^17.0.0", "react-error-boundary": "^3.1.4", @@ -28,7 +28,7 @@ "@types/faker": "^5.1.7", "@types/jest": "^29.5.2", "@types/lodash": "~4.14.168", - "@types/react": "^17.0.20", + "@types/react": "^18.2.37", "@types/react-dom": "^17.0.9", "@vitejs/plugin-react": "^2.2.0", "rollup-plugin-visualizer": "^5.9.2", diff --git a/examples/demo/package.json b/examples/demo/package.json index 31b50cc4f88..a92516060b2 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -29,7 +29,7 @@ "ra-input-rich-text": "^4.12.0", "ra-language-english": "^4.12.0", "ra-language-french": "^4.12.0", - "react": "^17.0.0", + "react": "^18.0.0", "react-admin": "^4.12.0", "react-app-polyfill": "^2.0.0", "react-dom": "^17.0.0", @@ -47,7 +47,7 @@ "@types/jest": "^29.5.2", "@types/node": "^12.12.14", "@types/prop-types": "^15.6.0", - "@types/react": "^17.0.20", + "@types/react": "^18.2.37", "@types/react-dom": "^17.0.9", "@vitejs/plugin-react": "^2.2.0", "rewire": "^5.0.0", diff --git a/examples/no-code/package.json b/examples/no-code/package.json index bee37454663..2811e581a60 100644 --- a/examples/no-code/package.json +++ b/examples/no-code/package.json @@ -11,7 +11,7 @@ "@mui/material": "^5.0.2", "ra-data-local-storage": "^4.12.0", "ra-no-code": "^4.12.0", - "react": "^17.0.0", + "react": "^18.0.0", "react-admin": "^4.12.0", "react-dom": "^17.0.0" }, diff --git a/examples/simple/package.json b/examples/simple/package.json index 2253d438149..c3c1ea09fdf 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.6.0", "@mui/icons-material": "^5.0.1", "@mui/material": "^5.0.2", + "@tanstack/react-query": "^5.8.4", "jsonexport": "^3.2.0", "lodash": "~4.17.5", "prop-types": "^15.7.2", @@ -22,11 +23,10 @@ "ra-input-rich-text": "^4.15.5", "ra-language-english": "^4.15.5", "ra-language-french": "^4.15.5", - "react": "^17.0.0", + "react": "^18.0.0", "react-admin": "^4.15.5", "react-dom": "^17.0.0", "react-hook-form": "^7.43.9", - "react-query": "^3.32.1", "react-router": "^6.1.0", "react-router-dom": "^6.1.0" }, diff --git a/examples/tutorial/package.json b/examples/tutorial/package.json index 3338d3bbaf1..0f112a3b315 100644 --- a/examples/tutorial/package.json +++ b/examples/tutorial/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@types/react": "^18.0.22", + "@types/react": "^18.2.37", "@types/react-dom": "^18.0.7", "@vitejs/plugin-react": "^2.2.0", "typescript": "^5.1.3", diff --git a/package.json b/package.json index 853bba58e10..afdaa92572c 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@storybook/react-webpack5": "^7.5.1", "@storybook/testing-react": "^2.0.0", "@types/jest": "^29.5.2", - "@types/react": "^17.0.20", + "@types/react": "^18.2.37", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", "concurrently": "^5.1.0", @@ -60,7 +60,7 @@ "lolex": "~2.3.2", "prettier": "~2.1.1", "raf": "~3.4.1", - "react": "^17.0.0", + "react": "^18.0.0", "react-dom": "^17.0.0", "storybook": "^7.5.1", "ts-jest": "^29.1.0", diff --git a/packages/create-react-admin/package.json b/packages/create-react-admin/package.json index bfd149190e1..4f1d1929499 100644 --- a/packages/create-react-admin/package.json +++ b/packages/create-react-admin/package.json @@ -25,11 +25,11 @@ "ink-select-input": "^4.2.2", "ink-text-input": "^4.0.3", "meow": "^9.0.0", - "react": "^17.0.0", + "react": "^18.0.0", "yn": "^5.0.0" }, "devDependencies": { - "@types/react": ">=17.0.0", + "@types/react": ">=18.0.0", "typescript": "^5.1.3" }, "gitHead": "e936ff2c3f887d2e98ef136cf3b3f3d254725fc4" diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index f82e03328cc..c2aec9392a9 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -32,12 +32,12 @@ "@types/jest": "^29.5.2", "@types/node": "^17.0.8", "@types/node-polyglot": "^0.4.31", - "@types/react": "^17.0.20", + "@types/react": "^18.2.37", "cross-env": "^5.2.0", "expect": "^27.4.6", "history": "^5.1.0", "ignore-styles": "~5.0.1", - "react": "^17.0.0", + "react": "^18.0.0", "react-dom": "^17.0.0", "react-hook-form": "^7.43.9", "react-router": "^6.1.0", @@ -58,6 +58,7 @@ "react-router-dom": "^6.1.0" }, "dependencies": { + "@tanstack/react-query": "^5.8.4", "clsx": "^1.1.1", "date-fns": "^2.19.0", "eventemitter3": "^4.0.7", @@ -66,8 +67,7 @@ "lodash": "~4.17.5", "prop-types": "^15.6.1", "query-string": "^7.1.1", - "react-is": "^17.0.2", - "react-query": "^3.32.1" + "react-is": "^17.0.2" }, "gitHead": "e936ff2c3f887d2e98ef136cf3b3f3d254725fc4" } diff --git a/packages/ra-core/src/auth/WithPermissions.tsx b/packages/ra-core/src/auth/WithPermissions.tsx index a19cb38f6d6..049e3d29626 100644 --- a/packages/ra-core/src/auth/WithPermissions.tsx +++ b/packages/ra-core/src/auth/WithPermissions.tsx @@ -76,7 +76,7 @@ const WithPermissions = (props: WithPermissionsProps) => { ); useAuthenticated(authParams); - const { permissions } = usePermissions(authParams); + const { data: permissions } = usePermissions(authParams); // render even though the usePermissions() call isn't finished (optimistic rendering) if (component) { return createElement(component, { permissions, ...rest }); diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts index 8f2f4ff758d..fdc7453f33d 100644 --- a/packages/ra-core/src/auth/useAuthState.ts +++ b/packages/ra-core/src/auth/useAuthState.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { useQuery, UseQueryOptions } from 'react-query'; +import { useEffect, useMemo } from 'react'; +import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import useAuthProvider, { defaultAuthParams } from './useAuthProvider'; import useLogout from './useLogout'; import { removeDoubleSlashes, useBasename } from '../routing'; @@ -52,43 +52,61 @@ const emptyParams = {}; const useAuthState = ( params: any = emptyParams, logoutOnFailure: boolean = false, - queryOptions?: UseQueryOptions + queryOptions: UseAuthStateOptions = emptyParams ): State => { const authProvider = useAuthProvider(); const logout = useLogout(); const basename = useBasename(); const notify = useNotify(); + const { onSuccess, onError, ...options } = queryOptions; - const result = useQuery( - ['auth', 'checkAuth', params], - () => { + const result = useQuery({ + queryKey: ['auth', 'checkAuth', params], + queryFn: () => { // The authProvider is optional in react-admin return authProvider?.checkAuth(params).then(() => true); }, - { - onError: error => { - const loginUrl = removeDoubleSlashes( - `${basename}/${defaultAuthParams.loginUrl}` + retry: false, + ...options, + }); + + useEffect(() => { + if (result.data && onSuccess) { + onSuccess(result.data); + } + }, [onSuccess, result.data]); + + useEffect(() => { + if (result.error) { + if (onError) { + return onError(result.error); + } + + const loginUrl = removeDoubleSlashes( + `${basename}/${defaultAuthParams.loginUrl}` + ); + if (logoutOnFailure) { + logout( + {}, + result.error && result.error.redirectTo != null + ? result.error.redirectTo + : loginUrl ); - if (logoutOnFailure) { - logout( - {}, - error && error.redirectTo != null - ? error.redirectTo - : loginUrl + const shouldSkipNotify = + result.error && result.error.message === false; + !shouldSkipNotify && + notify( + getErrorMessage( + result.error, + 'ra.auth.auth_check_error' + ), + { + type: 'error', + } ); - const shouldSkipNotify = error && error.message === false; - !shouldSkipNotify && - notify( - getErrorMessage(error, 'ra.auth.auth_check_error'), - { type: 'error' } - ); - } - }, - retry: false, - ...queryOptions, + } } - ); + }, [basename, logout, logoutOnFailure, notify, onError, result.error]); return useMemo(() => { return { @@ -102,6 +120,14 @@ const useAuthState = ( }, [authProvider, result]); }; +type UseAuthStateOptions = Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (data: boolean) => void; + onError?: (err: Error) => void; +}; + export default useAuthState; const getErrorMessage = (error, defaultMessage) => diff --git a/packages/ra-core/src/auth/useAuthenticated.ts b/packages/ra-core/src/auth/useAuthenticated.ts index b7554ccdbb2..a73de72b2ba 100644 --- a/packages/ra-core/src/auth/useAuthenticated.ts +++ b/packages/ra-core/src/auth/useAuthenticated.ts @@ -1,4 +1,4 @@ -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import useAuthState from './useAuthState'; /** @@ -33,11 +33,11 @@ export const useAuthenticated = ({ useAuthState(params ?? emptyParams, true, options); }; -export type UseAuthenticatedOptions = UseQueryOptions< - boolean, - any -> & { - params?: ParamsType; -}; +export type UseAuthenticatedOptions = Omit< + UseQueryOptions & { + params?: ParamsType; + }, + 'queryKey' +>; const emptyParams = {}; diff --git a/packages/ra-core/src/auth/useGetIdentity.stories.tsx b/packages/ra-core/src/auth/useGetIdentity.stories.tsx index 9e80e008dbc..b62c38278d7 100644 --- a/packages/ra-core/src/auth/useGetIdentity.stories.tsx +++ b/packages/ra-core/src/auth/useGetIdentity.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { useGetIdentity } from './useGetIdentity'; import AuthContext from './AuthContext'; diff --git a/packages/ra-core/src/auth/useGetIdentity.ts b/packages/ra-core/src/auth/useGetIdentity.ts index 667f7727487..8b16d2faf9c 100644 --- a/packages/ra-core/src/auth/useGetIdentity.ts +++ b/packages/ra-core/src/auth/useGetIdentity.ts @@ -1,5 +1,9 @@ -import { useMemo } from 'react'; -import { useQuery, UseQueryOptions, QueryObserverResult } from 'react-query'; +import { useEffect } from 'react'; +import { + useQuery, + UseQueryOptions, + QueryObserverResult, +} from '@tanstack/react-query'; import useAuthProvider from './useAuthProvider'; import { UserIdentity } from '../types'; @@ -42,63 +46,42 @@ const defaultQueryParams = { * } */ export const useGetIdentity = ( - queryParams: UseQueryOptions = defaultQueryParams -): UseGetIdentityResult => { + options: UseGetIdentityOptions = defaultQueryParams +): QueryObserverResult => { const authProvider = useAuthProvider(); + const { onSuccess, onError, ...queryOptions } = options; - const result = useQuery( - ['auth', 'getIdentity'], - authProvider - ? () => authProvider.getIdentity() - : async () => defaultIdentity, - { - enabled: typeof authProvider?.getIdentity === 'function', - ...queryParams, + const result = useQuery({ + queryKey: ['auth', 'getIdentity'], + queryFn: () => { + return authProvider + ? authProvider.getIdentity() + : Promise.resolve(defaultIdentity); + }, + enabled: typeof authProvider?.getIdentity === 'function', + ...queryOptions, + }); + + useEffect(() => { + if (result.data && onSuccess) { + onSuccess(result.data); } - ); + }, [onSuccess, result.data]); - // @FIXME: return useQuery's result directly by removing identity prop (BC break - to be done in v5) - return useMemo( - () => - result.isLoading - ? { isLoading: true } - : result.error - ? { error: result.error, isLoading: false } - : { - data: result.data, - identity: result.data, - refetch: result.refetch, - isLoading: false, - }, + useEffect(() => { + if (result.error && onError) { + onError(result.error); + } + }, [onError, result.error]); - [result] - ); + // @FIXME: return useQuery's result directly by removing identity prop (BC break - to be done in v5) + return result; }; -export type UseGetIdentityResult = - | { - isLoading: true; - data?: undefined; - identity?: undefined; - error?: undefined; - refetch?: undefined; - } - | { - isLoading: false; - data?: undefined; - identity?: undefined; - error: Error; - refetch?: undefined; - } - | { - isLoading: false; - data: UserIdentity; - /** - * @deprecated Use data instead - */ - identity: UserIdentity; - error?: undefined; - refetch: () => Promise>; - }; +export interface UseGetIdentityOptions + extends Omit, 'queryKey' | 'queryFn'> { + onSuccess?: (data: UserIdentity) => void; + onError?: (err: Error) => void; +} export default useGetIdentity; diff --git a/packages/ra-core/src/auth/useHandleAuthCallback.ts b/packages/ra-core/src/auth/useHandleAuthCallback.ts index a4af2bcd3ab..b671eb804cc 100644 --- a/packages/ra-core/src/auth/useHandleAuthCallback.ts +++ b/packages/ra-core/src/auth/useHandleAuthCallback.ts @@ -1,8 +1,9 @@ -import { useQuery, UseQueryOptions } from 'react-query'; +import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import { useLocation } from 'react-router'; import { useRedirect } from '../routing'; import { AuthProvider, AuthRedirectResult } from '../types'; import useAuthProvider from './useAuthProvider'; +import { useEffect } from 'react'; /** * This hook calls the `authProvider.handleCallback()` method on mount. This is meant to be used in a route called @@ -12,7 +13,7 @@ import useAuthProvider from './useAuthProvider'; * @returns An object containing { isLoading, data, error, refetch }. */ export const useHandleAuthCallback = ( - options?: UseQueryOptions> + options?: UseHandleAuthCallbackOptions ) => { const authProvider = useAuthProvider(); const redirect = useRedirect(); @@ -21,32 +22,45 @@ export const useHandleAuthCallback = ( const nextPathName = locationState && locationState.nextPathname; const nextSearch = locationState && locationState.nextSearch; const defaultRedirectUrl = nextPathName ? nextPathName + nextSearch : '/'; + const { onSuccess, onError, ...queryOptions } = options ?? {}; - return useQuery( - ['auth', 'handleCallback'], - () => authProvider.handleCallback(), - { - retry: false, - onSuccess: data => { - // AuthProviders relying on a third party services redirect back to the app can't - // use the location state to store the path on which the user was before the login. - // So we support a fallback on the localStorage. - const previousLocation = localStorage.getItem( - PreviousLocationStorageKey - ); - const redirectTo = - (data as AuthRedirectResult)?.redirectTo ?? - previousLocation; - - if (redirectTo === false) { - return; - } - - redirect(redirectTo ?? defaultRedirectUrl); - }, - ...options, + const queryResult = useQuery({ + queryKey: ['auth', 'handleCallback'], + queryFn: () => authProvider.handleCallback(), + retry: false, + ...queryOptions, + }); + + useEffect(() => { + if (queryResult.error && onError) { + onError(queryResult.error); + } + }, [onError, queryResult.error]); + + useEffect(() => { + if (queryResult.data) { + if (onSuccess) { + onSuccess(queryResult.data); + } + // AuthProviders relying on a third party services redirect back to the app can't + // use the location state to store the path on which the user was before the login. + // So we support a fallback on the localStorage. + const previousLocation = localStorage.getItem( + PreviousLocationStorageKey + ); + const redirectTo = + (queryResult.data as AuthRedirectResult)?.redirectTo ?? + previousLocation; + + if (redirectTo === false) { + return; + } + + redirect(redirectTo ?? defaultRedirectUrl); } - ); + }, [defaultRedirectUrl, onSuccess, queryResult.data, redirect]); + + return queryResult; }; /** @@ -54,3 +68,11 @@ export const useHandleAuthCallback = ( * Used by the useHandleAuthCallback hook to redirect the user to their previous location after a successful login. */ export const PreviousLocationStorageKey = '@react-admin/nextPathname'; + +export type UseHandleAuthCallbackOptions = Omit< + UseQueryOptions>, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (data: ReturnType) => void; + onError?: (err: Error) => void; +}; diff --git a/packages/ra-core/src/auth/useLogout.ts b/packages/ra-core/src/auth/useLogout.ts index 1f3011297a7..2d92e49db73 100644 --- a/packages/ra-core/src/auth/useLogout.ts +++ b/packages/ra-core/src/auth/useLogout.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef } from 'react'; import { useLocation, useNavigate, Path } from 'react-router-dom'; -import { useQueryClient } from 'react-query'; +import { useQueryClient } from '@tanstack/react-query'; import useAuthProvider, { defaultAuthParams } from './useAuthProvider'; import { useResetStore } from '../store'; diff --git a/packages/ra-core/src/auth/usePermissions.ts b/packages/ra-core/src/auth/usePermissions.ts index 3ac451ce7d1..4f674f6761b 100644 --- a/packages/ra-core/src/auth/usePermissions.ts +++ b/packages/ra-core/src/auth/usePermissions.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { useQuery, UseQueryOptions } from 'react-query'; +import { useEffect } from 'react'; +import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import useAuthProvider from './useAuthProvider'; import useLogoutIfAccessDenied from './useLogoutIfAccessDenied'; @@ -35,40 +35,51 @@ const emptyParams = {}; * } * }; */ -const usePermissions = ( +const usePermissions = ( params = emptyParams, - queryParams: UseQueryOptions = { + queryParams: UsePermissionsOptions = { staleTime: 5 * 60 * 1000, } ) => { const authProvider = useAuthProvider(); const logoutIfAccessDenied = useLogoutIfAccessDenied(); + const { onSuccess, onError, ...queryOptions } = queryParams ?? {}; - const result = useQuery( - ['auth', 'getPermissions', params], - authProvider - ? () => authProvider.getPermissions(params) - : async () => [], - { - onError: error => { - if (process.env.NODE_ENV === 'development') { - console.error(error); - } - logoutIfAccessDenied(error); - }, - ...queryParams, + const result = useQuery({ + queryKey: ['auth', 'getPermissions', params], + queryFn: () => { + return authProvider + ? authProvider.getPermissions(params) + : Promise.resolve([]); + }, + ...queryOptions, + }); + + useEffect(() => { + if (result.data && onSuccess) { + onSuccess(result.data); + } + }, [onSuccess, result.data]); + + useEffect(() => { + if (result.error) { + if (onError) { + return onError(result.error); + } + if (process.env.NODE_ENV === 'development') { + console.error(result.error); + } + logoutIfAccessDenied(result.error); } - ); + }, [logoutIfAccessDenied, onError, result.error]); - return useMemo( - () => ({ - permissions: result.data, - isLoading: result.isLoading, - error: result.error, - refetch: result.refetch, - }), - [result] - ); + return result; }; export default usePermissions; + +export interface UsePermissionsOptions + extends Omit, 'queryKey' | 'queryFn'> { + onSuccess?: (data: Permissions) => void; + onError?: (err: Error) => void; +} diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx index ec2d1e69cf1..552a4ea0dfc 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx @@ -4,7 +4,7 @@ import { ReactEventHandler, SyntheticEvent, } from 'react'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { useDelete } from '../../dataProvider'; import { useUnselect } from '../../controller'; @@ -79,7 +79,7 @@ const useDeleteWithConfirmController = ( const notify = useNotify(); const unselect = useUnselect(resource); const redirect = useRedirect(); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleDialogOpen = e => { setOpen(true); @@ -157,7 +157,7 @@ const useDeleteWithConfirmController = ( return { open, - isLoading, + isPending, handleDialogOpen, handleDialogClose, handleDelete, @@ -183,7 +183,7 @@ export interface UseDeleteWithConfirmControllerParams< export interface UseDeleteWithConfirmControllerReturn { open: boolean; - isLoading: boolean; + isPending: boolean; handleDialogOpen: (e: SyntheticEvent) => void; handleDialogClose: (e: SyntheticEvent) => void; handleDelete: ReactEventHandler; diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx index 8b4d706d6b8..824c5c085ee 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx @@ -1,5 +1,5 @@ import { useCallback, ReactEventHandler } from 'react'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { useDelete } from '../../dataProvider'; import { useUnselect } from '../../controller'; @@ -57,7 +57,7 @@ const useDeleteWithUndoController = ( const notify = useNotify(); const unselect = useUnselect(resource); const redirect = useRedirect(); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleDelete = useCallback( event => { @@ -119,7 +119,7 @@ const useDeleteWithUndoController = ( ] ); - return { isLoading, handleDelete }; + return { isPending, handleDelete }; }; export interface UseDeleteWithUndoControllerParams< @@ -139,7 +139,7 @@ export interface UseDeleteWithUndoControllerParams< } export interface UseDeleteWithUndoControllerReturn { - isLoading: boolean; + isPending: boolean; handleDelete: ReactEventHandler; } diff --git a/packages/ra-core/src/controller/create/useCreateController.ts b/packages/ra-core/src/controller/create/useCreateController.ts index fa6cee564d2..f827da8b078 100644 --- a/packages/ra-core/src/controller/create/useCreateController.ts +++ b/packages/ra-core/src/controller/create/useCreateController.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { parse } from 'query-string'; import { useLocation } from 'react-router-dom'; import { Location } from 'history'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { @@ -13,7 +13,11 @@ import { } from '../../dataProvider'; import { useRedirect, RedirectionSideEffect } from '../../routing'; import { useNotify } from '../../notification'; -import { SaveContextValue, useMutationMiddlewares } from '../saveContext'; +import { + SaveContextValue, + SaveHandlerCallbacks, + useMutationMiddlewares, +} from '../saveContext'; import { useTranslate } from '../../i18n'; import { Identifier, RaRecord, TransformData } from '../../types'; import { @@ -80,7 +84,7 @@ export const useCreateController = < unregisterMutationMiddleware, } = useMutationMiddlewares(); - const [create, { isLoading: saving }] = useCreate< + const [create, { isPending: saving }] = useCreate< RecordType, MutationOptionsError, ResultRecordType @@ -94,7 +98,7 @@ export const useCreateController = < onError: onErrorFromSave, transform: transformFromSave, meta: metaFromSave, - } = {} + } = {} as SaveHandlerCallbacks ) => Promise.resolve( transformFromSave diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts index 0242f6ca175..0d324300ea4 100644 --- a/packages/ra-core/src/controller/edit/useEditController.ts +++ b/packages/ra-core/src/controller/edit/useEditController.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useParams } from 'react-router-dom'; -import { UseQueryOptions, UseMutationOptions } from 'react-query'; +import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { RaRecord, MutationMode, TransformData } from '../../types'; @@ -20,7 +20,11 @@ import { useGetResourceLabel, useGetRecordRepresentation, } from '../../core'; -import { SaveContextValue, useMutationMiddlewares } from '../saveContext'; +import { + SaveContextValue, + SaveHandlerCallbacks, + useMutationMiddlewares, +} from '../saveContext'; /** * Prepare data for the Edit view. @@ -121,7 +125,7 @@ export const useEditController = < const recordCached = { id, previousData: record }; - const [update, { isLoading: saving }] = useUpdate< + const [update, { isPending: saving }] = useUpdate< RecordType, MutationOptionsError >(resource, recordCached, { @@ -138,7 +142,7 @@ export const useEditController = < onError: onErrorFromSave, transform: transformFromSave, meta: metaFromSave, - } = {} + } = {} as SaveHandlerCallbacks ) => Promise.resolve( transformFromSave @@ -256,7 +260,9 @@ export interface EditControllerProps< MutationOptionsError, UseUpdateMutateParams > & { meta?: any }; - queryOptions?: UseQueryOptions & { meta?: any }; + queryOptions?: Omit, 'queryFn' | 'queryKey'> & { + meta?: any; + }; redirect?: RedirectionSideEffect; resource?: string; transform?: TransformData; diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts index 45112e54b22..71a4e96d815 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts @@ -4,7 +4,7 @@ import { RaRecord, SortPayload } from '../../types'; import { useGetManyAggregate } from '../../dataProvider'; import { ListControllerResult, useList } from '../list'; import { useNotify } from '../../notification'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; export interface UseReferenceArrayFieldControllerParams< RecordType extends RaRecord = RaRecord, @@ -18,7 +18,10 @@ export interface UseReferenceArrayFieldControllerParams< resource: string; sort?: SortPayload; source: string; - queryOptions?: UseQueryOptions; + queryOptions?: Omit< + UseQueryOptions, + 'queryFn' | 'queryKey' + >; } const emptyArray = []; diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts index 9c16fc56980..4c81402af7e 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts @@ -163,7 +163,7 @@ export const useReferenceManyFieldController = < }, { enabled: get(record, source) != null, - keepPreviousData: true, + placeholderData: previousData => previousData, onError: error => notify( typeof error === 'string' diff --git a/packages/ra-core/src/controller/field/useReferenceOneFieldController.tsx b/packages/ra-core/src/controller/field/useReferenceOneFieldController.tsx index 0ce2a3cc39d..2518964e99d 100644 --- a/packages/ra-core/src/controller/field/useReferenceOneFieldController.tsx +++ b/packages/ra-core/src/controller/field/useReferenceOneFieldController.tsx @@ -1,5 +1,5 @@ import get from 'lodash/get'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import { useGetManyReference } from '../../dataProvider'; import { useNotify } from '../../notification'; @@ -15,10 +15,13 @@ export interface UseReferenceOneFieldControllerParams< target: string; sort?: SortPayload; filter?: any; - queryOptions?: UseQueryOptions<{ - data: RecordType[]; - total: number; - }> & { meta?: any }; + queryOptions?: Omit< + UseQueryOptions<{ + data: RecordType[]; + total: number; + }>, + 'queryFn' | 'queryKey' + > & { meta?: any }; } /** diff --git a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts index c33cfa9ef5c..8807cf72ff5 100644 --- a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts +++ b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts @@ -5,7 +5,7 @@ import { FilterPayload, RaRecord, SortPayload } from '../../types'; import { useGetList, useGetManyAggregate } from '../../dataProvider'; import { useReferenceParams } from './useReferenceParams'; import { ChoicesContextValue } from '../../form'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; /** * Prepare data for the ReferenceArrayInput components @@ -111,7 +111,7 @@ export const useReferenceArrayInputController = < { retry: false, enabled: isGetMatchingEnabled, - keepPreviousData: true, + placeholderData: previousData => previousData, ...otherQueryOptions, } ); @@ -193,14 +193,17 @@ export interface UseReferenceArrayInputParams< > { debounce?: number; filter?: FilterPayload; - queryOptions?: UseQueryOptions<{ - data: RecordType[]; - total?: number; - pageInfo?: { - hasNextPage?: boolean; - hasPreviousPage?: boolean; - }; - }> & { meta?: any }; + queryOptions?: Omit< + UseQueryOptions<{ + data: RecordType[]; + total?: number; + pageInfo?: { + hasNextPage?: boolean; + hasPreviousPage?: boolean; + }; + }>, + 'queryFn' | 'queryKey' + > & { meta?: any }; page?: number; perPage?: number; record?: RecordType; diff --git a/packages/ra-core/src/controller/input/useReferenceInputController.ts b/packages/ra-core/src/controller/input/useReferenceInputController.ts index fd705fe718b..b58b5a6dbc8 100644 --- a/packages/ra-core/src/controller/input/useReferenceInputController.ts +++ b/packages/ra-core/src/controller/input/useReferenceInputController.ts @@ -5,7 +5,7 @@ import { FilterPayload, RaRecord, SortPayload } from '../../types'; import { useReference } from '../useReference'; import { ChoicesContextValue } from '../../form'; import { useReferenceParams } from './useReferenceParams'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; const defaultReferenceSource = (resource: string, source: string) => `${resource}@${source}`; @@ -99,7 +99,7 @@ export const useReferenceInputController = ( }, { enabled: isGetMatchingEnabled, - keepPreviousData: true, + placeholderData: previousData => previousData, ...otherQueryOptions, } ); @@ -193,14 +193,17 @@ export interface UseReferenceInputControllerParams< > { debounce?: number; filter?: FilterPayload; - queryOptions?: UseQueryOptions<{ - data: RecordType[]; - total?: number; - pageInfo?: { - hasNextPage?: boolean; - hasPreviousPage?: boolean; - }; - }> & { meta?: any }; + queryOptions?: Omit< + UseQueryOptions<{ + data: RecordType[]; + total?: number; + pageInfo?: { + hasNextPage?: boolean; + hasPreviousPage?: boolean; + }; + }>, + 'queryFn' | 'queryKey' + > & { meta?: any }; page?: number; perPage?: number; record?: RaRecord; diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.ts b/packages/ra-core/src/controller/list/useInfiniteListController.ts index 15d12542428..ade2de08d57 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.ts +++ b/packages/ra-core/src/controller/list/useInfiniteListController.ts @@ -2,7 +2,9 @@ import { isValidElement, useEffect, useMemo } from 'react'; import { UseInfiniteQueryOptions, InfiniteQueryObserverBaseResult, -} from 'react-query'; + InfiniteData, + QueryKey, +} from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { useTranslate } from '../../i18n'; @@ -50,13 +52,13 @@ export const useInfiniteListController = ( filter, filterDefaultValues, perPage = 10, - queryOptions = {}, + queryOptions, sort, storeKey, } = props; useAuthenticated({ enabled: !disableAuthentication }); const resource = useResourceContext(props); - const { meta, ...otherQueryOptions } = queryOptions; + const { meta, ...otherQueryOptions } = queryOptions ?? {}; if (!resource) { throw new Error( @@ -109,7 +111,7 @@ export const useInfiniteListController = ( meta, }, { - keepPreviousData: true, + placeholderData: previousData => previousData, retry: false, onError: error => notify(error?.message || 'ra.notification.http_error', { @@ -212,10 +214,17 @@ export interface InfiniteListControllerProps< filterDefaultValues?: object; perPage?: number; // FIXME: Make it generic, but Parameters>[2] doesn't work - queryOptions?: UseInfiniteQueryOptions< - GetInfiniteListResult, - Error - >; + queryOptions?: Omit< + UseInfiniteQueryOptions< + GetInfiniteListResult, + Error, + InfiniteData>, + GetInfiniteListResult, + QueryKey, + number + >, + 'queryFn' | 'queryKey' + > & { meta?: any }; resource?: string; sort?: SortPayload; storeKey?: string | false; @@ -224,15 +233,15 @@ export interface InfiniteListControllerProps< export interface InfiniteListControllerResult extends ListControllerResult { fetchNextPage: InfiniteQueryObserverBaseResult< - GetInfiniteListResult + InfiniteData> >['fetchNextPage']; fetchPreviousPage: InfiniteQueryObserverBaseResult< - GetInfiniteListResult + InfiniteData> >['fetchPreviousPage']; isFetchingNextPage: InfiniteQueryObserverBaseResult< - GetInfiniteListResult + InfiniteData> >['isFetchingNextPage']; isFetchingPreviousPage: InfiniteQueryObserverBaseResult< - GetInfiniteListResult + InfiniteData> >['isFetchingPreviousPage']; } diff --git a/packages/ra-core/src/controller/list/useListController.ts b/packages/ra-core/src/controller/list/useListController.ts index 2be63fb0ea1..a6d71822584 100644 --- a/packages/ra-core/src/controller/list/useListController.ts +++ b/packages/ra-core/src/controller/list/useListController.ts @@ -1,5 +1,5 @@ import { isValidElement, useEffect, useMemo } from 'react'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { useTranslate } from '../../i18n'; @@ -94,7 +94,7 @@ export const useListController = ( meta, }, { - keepPreviousData: true, + placeholderData: previousData => previousData, retry: false, onError: error => notify(error?.message || 'ra.notification.http_error', { @@ -336,14 +336,17 @@ export interface ListControllerProps { * ); * } */ - queryOptions?: UseQueryOptions<{ - data: RecordType[]; - total?: number; - pageInfo?: { - hasNextPage?: boolean; - hasPreviousPage?: boolean; - }; - }> & { meta?: any }; + queryOptions?: Omit< + UseQueryOptions<{ + data: RecordType[]; + total?: number; + pageInfo?: { + hasNextPage?: boolean; + hasPreviousPage?: boolean; + }; + }>, + 'queryFn' | 'queryKey' + > & { meta?: any }; /** * The resource name. Defaults to the resource from ResourceContext. diff --git a/packages/ra-core/src/controller/saveContext/SaveContext.ts b/packages/ra-core/src/controller/saveContext/SaveContext.ts index 90fc39b9cbe..8c90e6c0795 100644 --- a/packages/ra-core/src/controller/saveContext/SaveContext.ts +++ b/packages/ra-core/src/controller/saveContext/SaveContext.ts @@ -24,11 +24,13 @@ export interface SaveContextValue< export type SaveHandler = ( record: Partial, - callbacks?: { - onSuccess?: OnSuccess; - onError?: OnError; - transform?: TransformData; - } + callbacks?: SaveHandlerCallbacks ) => Promise | Record; +export type SaveHandlerCallbacks = { + onSuccess?: OnSuccess; + onError?: OnError; + transform?: TransformData; + meta?: any; +}; export const SaveContext = createContext(undefined); diff --git a/packages/ra-core/src/controller/show/useShowController.ts b/packages/ra-core/src/controller/show/useShowController.ts index 15d112b1024..726eccd1ccc 100644 --- a/packages/ra-core/src/controller/show/useShowController.ts +++ b/packages/ra-core/src/controller/show/useShowController.ts @@ -1,5 +1,5 @@ import { useParams } from 'react-router-dom'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { RaRecord } from '../../types'; @@ -111,7 +111,9 @@ export const useShowController = ( export interface ShowControllerProps { disableAuthentication?: boolean; id?: RecordType['id']; - queryOptions?: UseQueryOptions & { meta?: any }; + queryOptions?: Omit, 'queryFn' | 'queryKey'> & { + meta?: any; + }; resource?: string; } diff --git a/packages/ra-core/src/controller/usePrevNextController.ts b/packages/ra-core/src/controller/usePrevNextController.ts index 52387fe0ee1..9937fd9d30f 100644 --- a/packages/ra-core/src/controller/usePrevNextController.ts +++ b/packages/ra-core/src/controller/usePrevNextController.ts @@ -1,4 +1,8 @@ -import { UseQueryOptions, useQuery, useQueryClient } from 'react-query'; +import { + UseQueryOptions, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; import { useResourceContext } from '../core'; import { useDataProvider } from '../dataProvider'; import { useStore } from '../store'; @@ -180,14 +184,12 @@ export const usePrevNextController = ( // If the previous and next ids are not in the cache, fetch the entire list. // This is necessary e.g. when coming directly to a detail page, // without displaying the list first - const { data, error, isLoading } = useQuery( - [resource, 'getList', params], - () => dataProvider.getList(resource, params), - { - enabled: !canUseCacheData, - ...otherQueryOptions, - } - ); + const { data, error, isLoading } = useQuery({ + queryKey: [resource, 'getList', params], + queryFn: () => dataProvider.getList(resource, params), + enabled: !canUseCacheData, + ...otherQueryOptions, + }); const finalData = canUseCacheData ? queryData.data : data?.data || []; @@ -234,14 +236,17 @@ export interface UsePrevNextControllerProps { filterDefaultValues?: FilterPayload; sort?: SortPayload; resource?: string; - queryOptions?: UseQueryOptions<{ - data: RecordType[]; - total?: number; - pageInfo?: { - hasNextPage?: boolean; - hasPreviousPage?: boolean; - }; - }> & { meta?: any }; + queryOptions?: Omit< + UseQueryOptions<{ + data: RecordType[]; + total?: number; + pageInfo?: { + hasNextPage?: boolean; + hasPreviousPage?: boolean; + }; + }>, + 'queryFn' | 'queryKey' + > & { meta?: any }; } export type UsePrevNextControllerResult = diff --git a/packages/ra-core/src/controller/useReference.ts b/packages/ra-core/src/controller/useReference.ts index 4da25d1d039..51401164edd 100644 --- a/packages/ra-core/src/controller/useReference.ts +++ b/packages/ra-core/src/controller/useReference.ts @@ -1,11 +1,13 @@ import { RaRecord, Identifier } from '../types'; import { UseGetManyHookValue, useGetManyAggregate } from '../dataProvider'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; interface UseReferenceProps { id: Identifier; reference: string; - options?: UseQueryOptions & { meta?: any }; + options?: Omit, 'queryFn' | 'queryKey'> & { + meta?: any; + }; } export interface UseReferenceResult { diff --git a/packages/ra-core/src/core/CoreAdminContext.tsx b/packages/ra-core/src/core/CoreAdminContext.tsx index 4b1619e5961..2ea030f6fc0 100644 --- a/packages/ra-core/src/core/CoreAdminContext.tsx +++ b/packages/ra-core/src/core/CoreAdminContext.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useMemo } from 'react'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { History } from 'history'; import { AdminRouter } from '../routing'; @@ -119,7 +119,7 @@ export interface CoreAdminContextProps { * @see https://marmelab.com/react-admin/Admin.html#queryclient * @example * import { Admin } from 'react-admin'; - * import { QueryClient } from 'react-query'; + * import { QueryClient } from '@tanstack/react-query'; * * const queryClient = new QueryClient({ * defaultOptions: { diff --git a/packages/ra-core/src/core/CoreAdminRoutes.tsx b/packages/ra-core/src/core/CoreAdminRoutes.tsx index df775746bca..6c91e4df3b1 100644 --- a/packages/ra-core/src/core/CoreAdminRoutes.tsx +++ b/packages/ra-core/src/core/CoreAdminRoutes.tsx @@ -120,7 +120,8 @@ export const CoreAdminRoutes = (props: CoreAdminRoutesProps) => { ); }; -export interface CoreAdminRoutesProps extends CoreLayoutProps { +export interface CoreAdminRoutesProps + extends Omit { layout: LayoutComponent; catchAll: CatchAllComponent; children?: AdminChildren; diff --git a/packages/ra-core/src/core/Resource.tsx b/packages/ra-core/src/core/Resource.tsx index 68a97ec81ec..60fe8b14e71 100644 --- a/packages/ra-core/src/core/Resource.tsx +++ b/packages/ra-core/src/core/Resource.tsx @@ -30,7 +30,8 @@ const getElement = (ElementOrComponent: ComponentType | ReactElement) => { } if (isValidElementType(ElementOrComponent)) { - return ; + const Element = ElementOrComponent as ComponentType; + return ; } return null; diff --git a/packages/ra-core/src/core/ResourceDefinitionContext.tsx b/packages/ra-core/src/core/ResourceDefinitionContext.tsx index 844400e5260..d939e95abc2 100644 --- a/packages/ra-core/src/core/ResourceDefinitionContext.tsx +++ b/packages/ra-core/src/core/ResourceDefinitionContext.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { createContext, useCallback, useState, useMemo } from 'react'; import isEqual from 'lodash/isEqual'; -import { ResourceDefinition, ResourceOptions } from '../types'; +import { AdminChildren, ResourceDefinition, ResourceOptions } from '../types'; export type ResourceDefinitions = { [name: string]: ResourceDefinition; @@ -47,7 +47,7 @@ export const ResourceDefinitionContextProvider = ({ children, }: { definitions?: ResourceDefinitions; - children: React.ReactNode; + children: AdminChildren; }) => { const [definitions, setState] = useState( defaultDefinitions @@ -78,7 +78,8 @@ export const ResourceDefinitionContextProvider = ({ return ( - {children} + {/* Had to cast here because Provider only accepts ReactNode but we might have a render function */} + {children as React.ReactNode} ); }; diff --git a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx index 7ae64adf838..ca8beb470cd 100644 --- a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx +++ b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx @@ -43,7 +43,7 @@ import { useResourceDefinitionContext } from './useResourceDefinitionContext'; export const useConfigureAdminRouterFromChildren = ( children: AdminChildren ): RoutesAndResources & { status: AdminRouterStatus } => { - const { permissions, isLoading } = usePermissions(); + const { data: permissions, isLoading } = usePermissions(); // Whenever children are updated, update our custom routes and resources const [routesAndResources, status] = useRoutesAndResourcesFromChildren( @@ -70,7 +70,7 @@ export const useConfigureAdminRouterFromChildren = ( * @param permissions The permissions */ const useRoutesAndResourcesFromChildren = ( - children: ReactNode, + children: AdminChildren, permissions: any, isLoading: boolean ): [RoutesAndResources, AdminRouterStatus] => { @@ -109,7 +109,9 @@ const useRoutesAndResourcesFromChildren = ( ); } else { mergeRoutesAndResources( - getRoutesAndResourceFromNodes(childrenFuncResult) + getRoutesAndResourceFromNodes( + childrenFuncResult as ReactNode + ) ); setStatus('ready'); } @@ -243,7 +245,7 @@ const getStatus = ({ customRoutesWithLayout, customRoutesWithoutLayout, }: { - children: ReactNode; + children: AdminChildren; resources: ReactElement[]; customRoutesWithLayout: ReactElement[]; customRoutesWithoutLayout: ReactElement[]; @@ -263,7 +265,7 @@ const getStatus = ({ * Returns the function child if one was provided, or null otherwise. */ const getSingleChildFunction = ( - children: ReactNode + children: AdminChildren ): RenderResourcesFunction | null => { const childrenArray = Array.isArray(children) ? children : [children]; @@ -291,11 +293,19 @@ const getSingleChildFunction = ( * - resources: an array of resources elements */ const getRoutesAndResourceFromNodes = ( - children: ReactNode + children: AdminChildren ): RoutesAndResources => { const customRoutesWithLayout = []; const customRoutesWithoutLayout = []; const resources = []; + + if (typeof children === 'function') { + return { + customRoutesWithLayout: [], + customRoutesWithoutLayout: [], + resources: [], + }; + } Children.forEach(children, element => { if (!React.isValidElement(element)) { // Ignore non-elements. This allows people to more easily inline diff --git a/packages/ra-core/src/dataProvider/useCreate.ts b/packages/ra-core/src/dataProvider/useCreate.ts index 62700331a37..c2b719b0a04 100644 --- a/packages/ra-core/src/dataProvider/useCreate.ts +++ b/packages/ra-core/src/dataProvider/useCreate.ts @@ -5,7 +5,7 @@ import { UseMutationResult, useQueryClient, MutateOptions, -} from 'react-query'; +} from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; import { RaRecord, CreateParams, Identifier } from '../types'; @@ -88,8 +88,8 @@ export const useCreate = < ResultRecordType, MutationError, Partial> - >( - ({ + >({ + mutationFn: ({ resource: callTimeResource = resource, data: callTimeData = paramsRef.current.data, meta: callTimeMeta = paramsRef.current.meta, @@ -100,36 +100,36 @@ export const useCreate = < meta: callTimeMeta, }) .then(({ data }) => data), - { - ...options, - onSuccess: ( - data: ResultRecordType, - variables: Partial> = {}, - context: unknown - ) => { - const { resource: callTimeResource = resource } = variables; - queryClient.setQueryData( - [callTimeResource, 'getOne', { id: String(data.id) }], - data - ); - queryClient.invalidateQueries([callTimeResource, 'getList']); - queryClient.invalidateQueries([ - callTimeResource, - 'getInfiniteList', - ]); - queryClient.invalidateQueries([callTimeResource, 'getMany']); - queryClient.invalidateQueries([ - callTimeResource, - 'getManyReference', - ]); + ...options, + onSuccess: ( + data: ResultRecordType, + variables: Partial> = {}, + context: unknown + ) => { + const { resource: callTimeResource = resource } = variables; + queryClient.setQueryData( + [callTimeResource, 'getOne', { id: String(data.id) }], + data + ); + queryClient.invalidateQueries({ + queryKey: [callTimeResource, 'getList'], + }); + queryClient.invalidateQueries({ + queryKey: [callTimeResource, 'getInfiniteList'], + }); + queryClient.invalidateQueries({ + queryKey: [callTimeResource, 'getMany'], + }); + queryClient.invalidateQueries({ + queryKey: [callTimeResource, 'getManyReference'], + }); - if (options.onSuccess) { - options.onSuccess(data, variables, context); - } - // call-time success callback is executed by react-query - }, - } - ); + if (options.onSuccess) { + options.onSuccess(data, variables, context); + } + // call-time success callback is executed by react-query + }, + }); const create = ( callTimeResource: string = resource, @@ -172,10 +172,13 @@ export type UseCreateOptions< RecordType extends Omit = any, MutationError = unknown, ResultRecordType extends RaRecord = RecordType & { id: Identifier } -> = UseMutationOptions< - ResultRecordType, - MutationError, - Partial> +> = Omit< + UseMutationOptions< + ResultRecordType, + MutationError, + Partial> + >, + 'mutationFn' > & { returnPromise?: boolean }; export type CreateMutationFunction< diff --git a/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx index 493fb6f9d2e..0a58064aea6 100644 --- a/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { QueryClient, useIsMutating } from 'react-query'; +import { QueryClient, useIsMutating } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useDelete } from './useDelete'; diff --git a/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx index 20261ce6655..86aee44452e 100644 --- a/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { QueryClient, useIsMutating } from 'react-query'; +import { QueryClient, useIsMutating } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useDelete } from './useDelete'; diff --git a/packages/ra-core/src/dataProvider/useDelete.ts b/packages/ra-core/src/dataProvider/useDelete.ts index 4d22bfe128e..95cc6074e07 100644 --- a/packages/ra-core/src/dataProvider/useDelete.ts +++ b/packages/ra-core/src/dataProvider/useDelete.ts @@ -7,7 +7,8 @@ import { MutateOptions, QueryKey, UseInfiniteQueryResult, -} from 'react-query'; + InfiniteData, +} from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; import undoableEventEmitter from './undoableEventEmitter'; @@ -117,7 +118,7 @@ export const useDelete = < }; queryClient.setQueriesData( - [resource, 'getList'], + { queryKey: [resource, 'getList'] }, (res: GetListResult) => { if (!res || !res.data) return res; const newCollection = updateColl(res.data); @@ -133,8 +134,14 @@ export const useDelete = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getInfiniteList'], - (res: UseInfiniteQueryResult['data']) => { + { + queryKey: [resource, 'getInfiniteList'], + }, + ( + res: UseInfiniteQueryResult< + InfiniteData + >['data'] + ) => { if (!res || !res.pages) return res; return { ...res, @@ -158,13 +165,13 @@ export const useDelete = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getMany'], + { queryKey: [resource, 'getMany'] }, (coll: RecordType[]) => coll && coll.length > 0 ? updateColl(coll) : coll, { updatedAt } ); queryClient.setQueriesData( - [resource, 'getManyReference'], + { queryKey: [resource, 'getManyReference'] }, (res: GetListResult) => { if (!res || !res.data) return res; const newCollection = updateColl(res.data); @@ -184,8 +191,8 @@ export const useDelete = < RecordType, MutationError, Partial> - >( - ({ + >({ + mutationFn: ({ resource: callTimeResource = resource, id: callTimeId = paramsRef.current.id, previousData: callTimePreviousData = paramsRef.current.previousData, @@ -198,101 +205,85 @@ export const useDelete = < meta: callTimeMeta, }) .then(({ data }) => data), - { - ...reactMutationOptions, - onMutate: async ( - variables: Partial> - ) => { - if (reactMutationOptions.onMutate) { - const userContext = - (await reactMutationOptions.onMutate(variables)) || {}; - return { - snapshot: snapshot.current, - // @ts-ignore - ...userContext, - }; - } else { - // Return a context object with the snapshot value - return { snapshot: snapshot.current }; - } - }, - onError: ( - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // If the mutation fails, use the context returned from onMutate to rollback - context.snapshot.forEach(([key, value]) => { - queryClient.setQueryData(key, value); - }); - } + ...reactMutationOptions, + onMutate: async ( + variables: Partial> + ) => { + if (reactMutationOptions.onMutate) { + const userContext = + (await reactMutationOptions.onMutate(variables)) || {}; + return { + snapshot: snapshot.current, + // @ts-ignore + ...userContext, + }; + } else { + // Return a context object with the snapshot value + return { snapshot: snapshot.current }; + } + }, + onError: ( + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // If the mutation fails, use the context returned from onMutate to rollback + context.snapshot.forEach(([key, value]) => { + queryClient.setQueryData(key, value); + }); + } - if (reactMutationOptions.onError) { - return reactMutationOptions.onError( - error, - variables, - context - ); - } - // call-time error callback is executed by react-query - }, - onSuccess: ( - data: RecordType, - variables: Partial> = {}, - context: unknown - ) => { - if (mode.current === 'pessimistic') { - // update the getOne and getList query cache with the new result - const { - resource: callTimeResource = resource, - id: callTimeId = id, - } = variables; - updateCache({ - resource: callTimeResource, - id: callTimeId, - }); + if (reactMutationOptions.onError) { + return reactMutationOptions.onError(error, variables, context); + } + // call-time error callback is executed by react-query + }, + onSuccess: ( + data: RecordType, + variables: Partial> = {}, + context: unknown + ) => { + if (mode.current === 'pessimistic') { + // update the getOne and getList query cache with the new result + const { + resource: callTimeResource = resource, + id: callTimeId = id, + } = variables; + updateCache({ + resource: callTimeResource, + id: callTimeId, + }); - if (reactMutationOptions.onSuccess) { - reactMutationOptions.onSuccess( - data, - variables, - context - ); - } - // call-time success callback is executed by react-query - } - }, - onSettled: ( - data: RecordType, - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // Always refetch after error or success: - context.snapshot.forEach(([key]) => { - queryClient.invalidateQueries(key); - }); + if (reactMutationOptions.onSuccess) { + reactMutationOptions.onSuccess(data, variables, context); } + // call-time success callback is executed by react-query + } + }, + onSettled: ( + data: RecordType, + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // Always refetch after error or success: + context.snapshot.forEach(([queryKey]) => { + queryClient.invalidateQueries({ queryKey }); + }); + } - if (reactMutationOptions.onSettled) { - return reactMutationOptions.onSettled( - data, - error, - variables, - context - ); - } - }, - } - ); + if (reactMutationOptions.onSettled) { + return reactMutationOptions.onSettled( + data, + error, + variables, + context + ); + } + }, + }); const mutate = async ( callTimeResource: string = resource, @@ -352,13 +343,16 @@ export const useDelete = < * @see https://react-query-v3.tanstack.com/reference/QueryClient#queryclientgetqueriesdata */ snapshot.current = queryKeys.reduce( - (prev, curr) => prev.concat(queryClient.getQueriesData(curr)), + (prev, queryKey) => + prev.concat(queryClient.getQueriesData({ queryKey })), [] as Snapshot ); // Cancel any outgoing re-fetches (so they don't overwrite our optimistic update) await Promise.all( - snapshot.current.map(([key]) => queryClient.cancelQueries(key)) + snapshot.current.map(([queryKey]) => + queryClient.cancelQueries({ queryKey }) + ) ); // Optimistically update to the new value diff --git a/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx index b55a3086a10..bd06243725d 100644 --- a/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { QueryClient, useIsMutating } from 'react-query'; +import { QueryClient, useIsMutating } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import undoableEventEmitter from './undoableEventEmitter'; diff --git a/packages/ra-core/src/dataProvider/useDeleteMany.ts b/packages/ra-core/src/dataProvider/useDeleteMany.ts index 8e49efd724f..f1fc6a83bf9 100644 --- a/packages/ra-core/src/dataProvider/useDeleteMany.ts +++ b/packages/ra-core/src/dataProvider/useDeleteMany.ts @@ -7,7 +7,8 @@ import { MutateOptions, QueryKey, UseInfiniteQueryResult, -} from 'react-query'; + InfiniteData, +} from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; import undoableEventEmitter from './undoableEventEmitter'; @@ -124,7 +125,7 @@ export const useDeleteMany = < }; queryClient.setQueriesData( - [resource, 'getList'], + { queryKey: [resource, 'getList'] }, (res: GetListResult) => { if (!res || !res.data) return res; const newCollection = updateColl(res.data); @@ -143,8 +144,12 @@ export const useDeleteMany = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getInfiniteList'], - (res: UseInfiniteQueryResult['data']) => { + { queryKey: [resource, 'getInfiniteList'] }, + ( + res: UseInfiniteQueryResult< + InfiniteData + >['data'] + ) => { if (!res || !res.pages) return res; return { ...res, @@ -170,13 +175,13 @@ export const useDeleteMany = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getMany'], + { queryKey: [resource, 'getMany'] }, (coll: RecordType[]) => coll && coll.length > 0 ? updateColl(coll) : coll, { updatedAt } ); queryClient.setQueriesData( - [resource, 'getManyReference'], + { queryKey: [resource, 'getManyReference'] }, (res: GetListResult) => { if (!res || !res.data) return res; const newCollection = updateColl(res.data); @@ -198,8 +203,8 @@ export const useDeleteMany = < RecordType['id'][], MutationError, Partial> - >( - ({ + >({ + mutationFn: ({ resource: callTimeResource = resource, ids: callTimeIds = paramsRef.current.ids, meta: callTimeMeta = paramsRef.current.meta, @@ -210,101 +215,85 @@ export const useDeleteMany = < meta: callTimeMeta, }) .then(({ data }) => data), - { - ...reactMutationOptions, - onMutate: async ( - variables: Partial> - ) => { - if (reactMutationOptions.onMutate) { - const userContext = - (await reactMutationOptions.onMutate(variables)) || {}; - return { - snapshot: snapshot.current, - // @ts-ignore - ...userContext, - }; - } else { - // Return a context object with the snapshot value - return { snapshot: snapshot.current }; - } - }, - onError: ( - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // If the mutation fails, use the context returned from onMutate to rollback - context.snapshot.forEach(([key, value]) => { - queryClient.setQueryData(key, value); - }); - } + ...reactMutationOptions, + onMutate: async ( + variables: Partial> + ) => { + if (reactMutationOptions.onMutate) { + const userContext = + (await reactMutationOptions.onMutate(variables)) || {}; + return { + snapshot: snapshot.current, + // @ts-ignore + ...userContext, + }; + } else { + // Return a context object with the snapshot value + return { snapshot: snapshot.current }; + } + }, + onError: ( + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // If the mutation fails, use the context returned from onMutate to rollback + context.snapshot.forEach(([key, value]) => { + queryClient.setQueryData(key, value); + }); + } - if (reactMutationOptions.onError) { - return reactMutationOptions.onError( - error, - variables, - context - ); - } - // call-time error callback is executed by react-query - }, - onSuccess: ( - data: RecordType['id'][], - variables: Partial> = {}, - context: unknown - ) => { - if (mode.current === 'pessimistic') { - // update the getOne and getList query cache with the new result - const { - resource: callTimeResource = resource, - ids: callTimeIds = ids, - } = variables; - updateCache({ - resource: callTimeResource, - ids: callTimeIds, - }); + if (reactMutationOptions.onError) { + return reactMutationOptions.onError(error, variables, context); + } + // call-time error callback is executed by react-query + }, + onSuccess: ( + data: RecordType['id'][], + variables: Partial> = {}, + context: unknown + ) => { + if (mode.current === 'pessimistic') { + // update the getOne and getList query cache with the new result + const { + resource: callTimeResource = resource, + ids: callTimeIds = ids, + } = variables; + updateCache({ + resource: callTimeResource, + ids: callTimeIds, + }); - if (reactMutationOptions.onSuccess) { - reactMutationOptions.onSuccess( - data, - variables, - context - ); - } - // call-time success callback is executed by react-query - } - }, - onSettled: ( - data: RecordType['id'][], - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // Always refetch after error or success: - context.snapshot.forEach(([key]) => { - queryClient.invalidateQueries(key); - }); + if (reactMutationOptions.onSuccess) { + reactMutationOptions.onSuccess(data, variables, context); } + // call-time success callback is executed by react-query + } + }, + onSettled: ( + data: RecordType['id'][], + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // Always refetch after error or success: + context.snapshot.forEach(([queryKey]) => { + queryClient.invalidateQueries({ queryKey }); + }); + } - if (reactMutationOptions.onSettled) { - return reactMutationOptions.onSettled( - data, - error, - variables, - context - ); - } - }, - } - ); + if (reactMutationOptions.onSettled) { + return reactMutationOptions.onSettled( + data, + error, + variables, + context + ); + } + }, + }); const mutate = async ( callTimeResource: string = resource, @@ -361,13 +350,16 @@ export const useDeleteMany = < * @see https://react-query-v3.tanstack.com/reference/QueryClient#queryclientgetqueriesdata */ snapshot.current = queryKeys.reduce( - (prev, curr) => prev.concat(queryClient.getQueriesData(curr)), + (prev, queryKey) => + prev.concat(queryClient.getQueriesData({ queryKey })), [] as Snapshot ); // Cancel any outgoing re-fetches (so they don't overwrite our optimistic update) await Promise.all( - snapshot.current.map(([key]) => queryClient.cancelQueries(key)) + snapshot.current.map(([queryKey]) => + queryClient.cancelQueries({ queryKey }) + ) ); // Optimistically update to the new value diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts index 012d2a27181..81036957214 100644 --- a/packages/ra-core/src/dataProvider/useGetList.ts +++ b/packages/ra-core/src/dataProvider/useGetList.ts @@ -1,10 +1,10 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { useQuery, UseQueryOptions, UseQueryResult, useQueryClient, -} from 'react-query'; +} from '@tanstack/react-query'; import { RaRecord, GetListParams, GetListResult } from '../types'; import { useDataProvider } from './useDataProvider'; @@ -56,7 +56,7 @@ const MAX_DATA_LENGTH_TO_CACHE = 100; export const useGetList = ( resource: string, params: Partial = {}, - options?: UseQueryOptions, Error> + options: UseGetListOptions = {} ): UseGetListHookValue => { const { pagination = { page: 1, perPage: 25 }, @@ -66,13 +66,14 @@ export const useGetList = ( } = params; const dataProvider = useDataProvider(); const queryClient = useQueryClient(); + const { onError, onSuccess, ...queryOptions } = options; const result = useQuery< GetListResult, Error, GetListResult - >( - [resource, 'getList', { pagination, sort, filter, meta }], - () => + >({ + queryKey: [resource, 'getList', { pagination, sort, filter, meta }], + queryFn: () => dataProvider .getList(resource, { pagination, @@ -85,32 +86,34 @@ export const useGetList = ( total, pageInfo, })), - { - ...options, - onSuccess: value => { - // optimistically populate the getOne cache - if ( - value?.data && - value.data.length <= MAX_DATA_LENGTH_TO_CACHE - ) { - value.data.forEach(record => { - queryClient.setQueryData( - [ - resource, - 'getOne', - { id: String(record.id), meta }, - ], - oldRecord => oldRecord ?? record - ); - }); - } - // execute call-time onSuccess if provided - if (options?.onSuccess) { - options.onSuccess(value); - } - }, + ...queryOptions, + }); + + useEffect(() => { + // optimistically populate the getOne cache + if ( + result.data && + result.data?.data && + result.data.data.length <= MAX_DATA_LENGTH_TO_CACHE + ) { + result.data.data.forEach(record => { + queryClient.setQueryData( + [resource, 'getOne', { id: String(record.id), meta }], + oldRecord => oldRecord ?? record + ); + }); } - ); + // execute call-time onSuccess if provided + if (onSuccess) { + onSuccess(result.data); + } + }, [meta, onSuccess, queryClient, resource, result.data]); + + useEffect(() => { + if (result.error && onError) { + onError(result.error); + } + }, [onError, result.error]); return useMemo( () => @@ -132,6 +135,14 @@ export const useGetList = ( }; }; +export type UseGetListOptions = Omit< + UseQueryOptions, Error>, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (value: GetListResult) => void; + onError?: (error: Error) => void; +}; + export type UseGetListHookValue< RecordType extends RaRecord = any > = UseQueryResult & { diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index 402e8dcff76..054e80b6f32 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -3,11 +3,12 @@ import { UseQueryOptions, UseQueryResult, useQueryClient, - hashQueryKey, -} from 'react-query'; + hashKey, +} from '@tanstack/react-query'; import { RaRecord, GetManyParams } from '../types'; import { useDataProvider } from './useDataProvider'; +import { useEffect } from 'react'; /** * Call the dataProvider.getMany() method and return the resolved result @@ -52,15 +53,16 @@ import { useDataProvider } from './useDataProvider'; export const useGetMany = ( resource: string, params: Partial = {}, - options?: UseQueryOptions + options: UseGetManyOptions = {} ): UseGetManyHookValue => { const { ids, meta } = params; const dataProvider = useDataProvider(); const queryClient = useQueryClient(); const queryCache = queryClient.getQueryCache(); + const { onError, onSuccess, ...queryOptions } = options; - return useQuery( - [ + const result = useQuery({ + queryKey: [ resource, 'getMany', { @@ -68,7 +70,7 @@ export const useGetMany = ( meta, }, ], - () => { + queryFn: () => { if (!ids || ids.length === 0) { // no need to call the dataProvider return Promise.resolve([]); @@ -77,39 +79,61 @@ export const useGetMany = ( .getMany(resource, { ids, meta }) .then(({ data }) => data); }, - { - placeholderData: () => { - const records = - !ids || ids.length === 0 - ? [] - : ids.map(id => { - const queryHash = hashQueryKey([ - resource, - 'getOne', - { id: String(id), meta }, - ]); - return queryCache.get(queryHash) - ?.state?.data; - }); - if (records.some(record => record === undefined)) { - return undefined; - } else { - return records as RecordType[]; - } - }, - onSuccess: data => { - // optimistically populate the getOne cache - data.forEach(record => { - queryClient.setQueryData( - [resource, 'getOne', { id: String(record.id), meta }], - oldRecord => oldRecord ?? record - ); - }); - }, - retry: false, - ...options, + placeholderData: () => { + const records = + !ids || ids.length === 0 + ? [] + : ids.map(id => { + const queryHash = hashKey([ + resource, + 'getOne', + { id: String(id), meta }, + ]); + return queryCache.get(queryHash)?.state + ?.data; + }); + if (records.some(record => record === undefined)) { + return undefined; + } else { + return records as RecordType[]; + } + }, + retry: false, + ...queryOptions, + }); + + useEffect(() => { + if (result.data) { + // optimistically populate the getOne cache + result.data.forEach(record => { + queryClient.setQueryData( + [resource, 'getOne', { id: String(record.id), meta }], + oldRecord => oldRecord ?? record + ); + }); + } + + // execute call-time onSuccess if provided + if (onSuccess) { + onSuccess(result.data); + } + }, [queryClient, meta, onSuccess, resource, result.data]); + + useEffect(() => { + if (result.error && onError) { + onError(result.error); } - ); + }, [onError, result.error]); + + return result; +}; + +export type UseGetManyOptions = Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (data: RecordType[]) => void; + onError?: (error: Error) => void; }; export type UseGetManyHookValue< diff --git a/packages/ra-core/src/dataProvider/useGetManyAggregate.ts b/packages/ra-core/src/dataProvider/useGetManyAggregate.ts index c2fe9c93acd..4a7cd658fe9 100644 --- a/packages/ra-core/src/dataProvider/useGetManyAggregate.ts +++ b/packages/ra-core/src/dataProvider/useGetManyAggregate.ts @@ -1,11 +1,11 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { QueryClient, useQueryClient, useQuery, UseQueryOptions, - hashQueryKey, -} from 'react-query'; + hashKey, +} from '@tanstack/react-query'; import union from 'lodash/union'; import { UseGetManyHookValue } from './useGetMany'; @@ -68,15 +68,16 @@ import { useDataProvider } from './useDataProvider'; export const useGetManyAggregate = ( resource: string, params: GetManyParams, - options: UseQueryOptions = {} + options: UseGetManyAggregateOptions = {} ): UseGetManyHookValue => { const dataProvider = useDataProvider(); const queryClient = useQueryClient(); const queryCache = queryClient.getQueryCache(); + const { onError, onSuccess, ...queryOptions } = options; const { ids, meta } = params; const placeholderData = useMemo(() => { const records = (Array.isArray(ids) ? ids : [ids]).map(id => { - const queryHash = hashQueryKey([ + const queryHash = hashKey([ resource, 'getOne', { id: String(id), meta }, @@ -90,8 +91,8 @@ export const useGetManyAggregate = ( } }, [ids, queryCache, resource, meta]); - return useQuery( - [ + const result = useQuery({ + queryKey: [ resource, 'getMany', { @@ -99,7 +100,7 @@ export const useGetManyAggregate = ( meta, }, ], - () => + queryFn: () => new Promise((resolve, reject) => { if (!ids || ids.length === 0) { // no need to call the dataProvider @@ -116,21 +117,35 @@ export const useGetManyAggregate = ( queryClient, }); }), - { - placeholderData, - onSuccess: data => { - // optimistically populate the getOne cache - (data ?? []).forEach(record => { - queryClient.setQueryData( - [resource, 'getOne', { id: String(record.id), meta }], - oldRecord => oldRecord ?? record - ); - }); - }, - retry: false, - ...options, + placeholderData, + retry: false, + ...queryOptions, + }); + + useEffect(() => { + if (result.data) { + // optimistically populate the getOne cache + (result.data ?? []).forEach(record => { + queryClient.setQueryData( + [resource, 'getOne', { id: String(record.id), meta }], + oldRecord => oldRecord ?? record + ); + }); + + // execute call-time onSuccess if provided + if (onSuccess) { + onSuccess(result.data); + } + } + }, [queryClient, meta, onSuccess, resource, result.data]); + + useEffect(() => { + if (result.error && onError) { + onError(result.error); } - ); + }, [onError, result.error]); + + return result; }; /** @@ -269,8 +284,8 @@ const callGetManyQueries = batch((calls: GetManyCallArgs[]) => { * and resolve each of the promises using the results */ queryClient - .fetchQuery( - [ + .fetchQuery({ + queryKey: [ resource, 'getMany', { @@ -278,14 +293,14 @@ const callGetManyQueries = batch((calls: GetManyCallArgs[]) => { meta: uniqueMeta, }, ], - () => + queryFn: () => dataProvider .getMany(resource, { ids: aggregatedIds, meta: uniqueMeta, }) - .then(({ data }) => data) - ) + .then(({ data }) => data), + }) .then(data => { callsForResource.forEach(({ ids, resolve }) => { resolve( @@ -302,3 +317,11 @@ const callGetManyQueries = batch((calls: GetManyCallArgs[]) => { ); }); }); + +export type UseGetManyAggregateOptions = Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (data: RecordType[]) => void; + onError?: (error: Error) => void; +}; diff --git a/packages/ra-core/src/dataProvider/useGetManyReference.ts b/packages/ra-core/src/dataProvider/useGetManyReference.ts index 1bf65eb3bd8..41ea2323932 100644 --- a/packages/ra-core/src/dataProvider/useGetManyReference.ts +++ b/packages/ra-core/src/dataProvider/useGetManyReference.ts @@ -1,10 +1,10 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { useQuery, UseQueryOptions, UseQueryResult, useQueryClient, -} from 'react-query'; +} from '@tanstack/react-query'; import { RaRecord, @@ -62,7 +62,7 @@ import { useDataProvider } from './useDataProvider'; export const useGetManyReference = ( resource: string, params: Partial = {}, - options?: UseQueryOptions<{ data: RecordType[]; total: number }, Error> + options: UseGetManyReferenceHookOptions = {} ): UseGetManyReferenceHookValue => { const { target, @@ -74,17 +74,19 @@ export const useGetManyReference = ( } = params; const dataProvider = useDataProvider(); const queryClient = useQueryClient(); + const { onError, onSuccess, ...queryOptions } = options; + const result = useQuery< GetManyReferenceResult, Error, GetManyReferenceResult - >( - [ + >({ + queryKey: [ resource, 'getManyReference', { target, id, pagination, sort, filter, meta }, ], - () => { + queryFn: () => { if (!target || id == null) { // check at runtime to support partial parameters with the enabled option return Promise.reject(new Error('target and id are required')); @@ -104,19 +106,31 @@ export const useGetManyReference = ( pageInfo, })); }, - { - onSuccess: value => { - // optimistically populate the getOne cache - value?.data?.forEach(record => { - queryClient.setQueryData( - [resource, 'getOne', { id: String(record.id), meta }], - oldRecord => oldRecord ?? record - ); - }); - }, - ...options, + ...queryOptions, + }); + + useEffect(() => { + if (result.data) { + // optimistically populate the getOne cache + result.data?.data?.forEach(record => { + queryClient.setQueryData( + [resource, 'getOne', { id: String(record.id), meta }], + oldRecord => oldRecord ?? record + ); + }); } - ); + + // execute call-time onSuccess if provided + if (onSuccess) { + onSuccess(result.data); + } + }, [queryClient, meta, onSuccess, resource, result.data]); + + useEffect(() => { + if (result.error && onError) { + onError(result.error); + } + }, [onError, result.error]); return useMemo( () => @@ -138,9 +152,19 @@ export const useGetManyReference = ( }; }; +export type UseGetManyReferenceHookOptions< + RecordType extends RaRecord = any +> = Omit< + UseQueryOptions>, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (data: GetManyReferenceResult) => void; + onError?: (error: Error) => void; +}; + export type UseGetManyReferenceHookValue< RecordType extends RaRecord = any -> = UseQueryResult & { +> = Omit, 'queryKey' | 'queryFn'> & { total?: number; pageInfo?: { hasNextPage?: boolean; diff --git a/packages/ra-core/src/dataProvider/useGetOne.ts b/packages/ra-core/src/dataProvider/useGetOne.ts index b0eace49c38..21039fd548f 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.ts +++ b/packages/ra-core/src/dataProvider/useGetOne.ts @@ -1,5 +1,9 @@ -import { RaRecord, GetOneParams } from '../types'; -import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { RaRecord, GetOneParams, GetOneResult } from '../types'; +import { + useQuery, + UseQueryOptions, + UseQueryResult, +} from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; /** @@ -44,22 +48,34 @@ import { useDataProvider } from './useDataProvider'; export const useGetOne = ( resource: string, { id, meta }: GetOneParams, - options?: UseQueryOptions + options: UseGetOneOptions = {} ): UseGetOneHookValue => { const dataProvider = useDataProvider(); - return useQuery( + const { onError, onSuccess, ...queryOptions } = options; + + const result = useQuery({ // Sometimes the id comes as a string (e.g. when read from the URL in a Show view). // Sometimes the id comes as a number (e.g. when read from a Record in useGetList response). // As the react-query cache is type-sensitive, we always stringify the identifier to get a match - [resource, 'getOne', { id: String(id), meta }], - () => + queryKey: [resource, 'getOne', { id: String(id), meta }], + queryFn: () => dataProvider .getOne(resource, { id, meta }) .then(({ data }) => data), - options - ); + ...queryOptions, + }); + + return result; +}; + +export type UseGetOneOptions = Omit< + UseQueryOptions['data']>, + 'queryKey' | 'queryFn' +> & { + onSuccess?: (data: GetOneResult) => void; + onError?: (error: Error) => void; }; export type UseGetOneHookValue< RecordType extends RaRecord = any -> = UseQueryResult; +> = UseQueryResult['data']>; diff --git a/packages/ra-core/src/dataProvider/useInfiniteGetList.ts b/packages/ra-core/src/dataProvider/useInfiniteGetList.ts index ba6dd9160c5..e20063d22b1 100644 --- a/packages/ra-core/src/dataProvider/useInfiniteGetList.ts +++ b/packages/ra-core/src/dataProvider/useInfiniteGetList.ts @@ -1,12 +1,15 @@ import { + InfiniteData, + QueryKey, useInfiniteQuery, UseInfiniteQueryOptions, UseInfiniteQueryResult, useQueryClient, -} from 'react-query'; +} from '@tanstack/react-query'; import { RaRecord, GetListParams, GetInfiniteListResult } from '../types'; import { useDataProvider } from './useDataProvider'; +import { useEffect } from 'react'; /** * Call the dataProvider.getList() method and return the resolved result @@ -63,7 +66,7 @@ import { useDataProvider } from './useDataProvider'; export const useInfiniteGetList = ( resource: string, params: Partial = {}, - options?: UseInfiniteQueryOptions, Error> + options?: UseInfiniteGetListOptions ): UseInfiniteGetListHookValue => { const { pagination = { page: 1, perPage: 25 }, @@ -77,10 +80,16 @@ export const useInfiniteGetList = ( const result = useInfiniteQuery< GetInfiniteListResult, Error, - GetInfiniteListResult - >( - [resource, 'getInfiniteList', { pagination, sort, filter, meta }], - ({ pageParam = pagination.page }) => + InfiniteData>, + QueryKey, + number + >({ + queryKey: [ + resource, + 'getInfiniteList', + { pagination, sort, filter, meta }, + ], + queryFn: ({ pageParam = pagination.page }) => dataProvider .getList(resource, { pagination: { @@ -97,50 +106,48 @@ export const useInfiniteGetList = ( pageParam, pageInfo, })), - { - onSuccess: data => { - // optimistically populate the getOne cache - data.pages.forEach(page => { - page.data.forEach(record => { - queryClient.setQueryData( - [ - resource, - 'getOne', - { id: String(record.id), meta }, - ], - oldRecord => oldRecord ?? record - ); - }); - }); - }, - ...options, - getNextPageParam: lastLoadedPage => { - if (lastLoadedPage.pageInfo) { - return lastLoadedPage.pageInfo.hasNextPage - ? lastLoadedPage.pageParam + 1 - : undefined; - } - const totalPages = Math.ceil( - (lastLoadedPage.total || 0) / pagination.perPage - ); + initialPageParam: pagination.page, + ...options, + getNextPageParam: lastLoadedPage => { + if (lastLoadedPage.pageInfo) { + return lastLoadedPage.pageInfo.hasNextPage + ? lastLoadedPage.pageParam + 1 + : undefined; + } + const totalPages = Math.ceil( + (lastLoadedPage.total || 0) / pagination.perPage + ); - return lastLoadedPage.pageParam < totalPages - ? Number(lastLoadedPage.pageParam) + 1 + return lastLoadedPage.pageParam < totalPages + ? Number(lastLoadedPage.pageParam) + 1 + : undefined; + }, + getPreviousPageParam: lastLoadedPage => { + if (lastLoadedPage.pageInfo) { + return lastLoadedPage.pageInfo.hasPreviousPage + ? lastLoadedPage.pageParam - 1 : undefined; - }, - getPreviousPageParam: lastLoadedPage => { - if (lastLoadedPage.pageInfo) { - return lastLoadedPage.pageInfo.hasPreviousPage - ? lastLoadedPage.pageParam - 1 - : undefined; - } + } + + return lastLoadedPage.pageParam === 1 + ? undefined + : lastLoadedPage.pageParam - 1; + }, + }); - return lastLoadedPage.pageParam === 1 - ? undefined - : lastLoadedPage.pageParam - 1; - }, + useEffect(() => { + if (result.data) { + // optimistically populate the getOne cache + result.data.pages.forEach(page => { + page.data.forEach(record => { + queryClient.setQueryData( + [resource, 'getOne', { id: String(record.id), meta }], + oldRecord => oldRecord ?? record + ); + }); + }); } - ); + }, [meta, queryClient, resource, result.data]); return (result.data ? { @@ -149,16 +156,35 @@ export const useInfiniteGetList = ( total: result.data?.pages[0]?.total ?? undefined, } : result) as UseInfiniteQueryResult< - GetInfiniteListResult, + InfiniteData>, Error > & { total?: number; }; }; +export type UseInfiniteGetListOptions = Omit< + UseInfiniteQueryOptions< + GetInfiniteListResult, + Error, + InfiniteData>, + GetInfiniteListResult, + QueryKey, + number + >, + | 'queryKey' + | 'queryFn' + | 'getNextPageParam' + | 'getPreviousPageParam' + | 'initialPageParam' +> & { + onSuccess?: (data: GetInfiniteListResult) => void; + onError?: (error: Error) => void; +}; + export type UseInfiniteGetListHookValue< RecordType extends RaRecord = any -> = UseInfiniteQueryResult, Error> & { +> = UseInfiniteQueryResult>> & { total?: number; pageParam?: number; }; diff --git a/packages/ra-core/src/dataProvider/useIsDataLoaded.ts b/packages/ra-core/src/dataProvider/useIsDataLoaded.ts index a12ae366de2..e7052ed73a7 100644 --- a/packages/ra-core/src/dataProvider/useIsDataLoaded.ts +++ b/packages/ra-core/src/dataProvider/useIsDataLoaded.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useQueryClient, QueryObserver } from 'react-query'; +import { useQueryClient, QueryObserver } from '@tanstack/react-query'; /** * Check if react-query has already fetched data for a query key. diff --git a/packages/ra-core/src/dataProvider/useLoading.ts b/packages/ra-core/src/dataProvider/useLoading.ts index 9f691595d8b..8a9b3d7e180 100644 --- a/packages/ra-core/src/dataProvider/useLoading.ts +++ b/packages/ra-core/src/dataProvider/useLoading.ts @@ -1,6 +1,6 @@ import React from 'react'; -import { notifyManager, useQueryClient } from 'react-query'; +import { notifyManager, useQueryClient } from '@tanstack/react-query'; /** * Get the state of the dataProvider connection. diff --git a/packages/ra-core/src/dataProvider/useRefresh.ts b/packages/ra-core/src/dataProvider/useRefresh.ts index bf8e9309692..accc83b81a1 100644 --- a/packages/ra-core/src/dataProvider/useRefresh.ts +++ b/packages/ra-core/src/dataProvider/useRefresh.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { useQueryClient } from 'react-query'; +import { useQueryClient } from '@tanstack/react-query'; /** * Hook for triggering a page refresh. Returns a callback function. diff --git a/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx b/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx index 3a0eb22196d..9ad9b919159 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { QueryClient, useIsMutating } from 'react-query'; +import { QueryClient, useIsMutating } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useUpdate } from './useUpdate'; diff --git a/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx b/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx index 1c81f926812..2cd6e6b979d 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { QueryClient, useIsMutating } from 'react-query'; +import { QueryClient, useIsMutating } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useUpdate } from './useUpdate'; diff --git a/packages/ra-core/src/dataProvider/useUpdate.ts b/packages/ra-core/src/dataProvider/useUpdate.ts index e3a68449970..9db23e70715 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.ts +++ b/packages/ra-core/src/dataProvider/useUpdate.ts @@ -7,7 +7,8 @@ import { MutateOptions, QueryKey, UseInfiniteQueryResult, -} from 'react-query'; + InfiniteData, +} from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; import undoableEventEmitter from './undoableEventEmitter'; @@ -130,14 +131,18 @@ export const useUpdate = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getList'], + { queryKey: [resource, 'getList'] }, (res: GetListResult) => res && res.data ? { ...res, data: updateColl(res.data) } : res, { updatedAt } ); queryClient.setQueriesData( - [resource, 'getInfiniteList'], - (res: UseInfiniteQueryResult['data']) => + { queryKey: [resource, 'getInfiniteList'] }, + ( + res: UseInfiniteQueryResult< + InfiniteData + >['data'] + ) => res && res.pages ? { ...res, @@ -150,13 +155,13 @@ export const useUpdate = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getMany'], + { queryKey: [resource, 'getMany'] }, (coll: RecordType[]) => coll && coll.length > 0 ? updateColl(coll) : coll, { updatedAt } ); queryClient.setQueriesData( - [resource, 'getManyReference'], + { queryKey: [resource, 'getManyReference'] }, (res: GetListResult) => res && res.data ? { data: updateColl(res.data), total: res.total } @@ -169,8 +174,8 @@ export const useUpdate = < RecordType, MutationError, Partial> - >( - ({ + >({ + mutationFn: ({ resource: callTimeResource = resource, id: callTimeId = paramsRef.current.id, data: callTimeData = paramsRef.current.data, @@ -185,102 +190,86 @@ export const useUpdate = < meta: callTimeMeta, }) .then(({ data }) => data), - { - ...reactMutationOptions, - onMutate: async ( - variables: Partial> - ) => { - if (reactMutationOptions.onMutate) { - const userContext = - (await reactMutationOptions.onMutate(variables)) || {}; - return { - snapshot: snapshot.current, - // @ts-ignore - ...userContext, - }; - } else { - // Return a context object with the snapshot value - return { snapshot: snapshot.current }; - } - }, - onError: ( - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // If the mutation fails, use the context returned from onMutate to rollback - context.snapshot.forEach(([key, value]) => { - queryClient.setQueryData(key, value); - }); - } + ...reactMutationOptions, + onMutate: async ( + variables: Partial> + ) => { + if (reactMutationOptions.onMutate) { + const userContext = + (await reactMutationOptions.onMutate(variables)) || {}; + return { + snapshot: snapshot.current, + // @ts-ignore + ...userContext, + }; + } else { + // Return a context object with the snapshot value + return { snapshot: snapshot.current }; + } + }, + onError: ( + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // If the mutation fails, use the context returned from onMutate to rollback + context.snapshot.forEach(([key, value]) => { + queryClient.setQueryData(key, value); + }); + } - if (reactMutationOptions.onError) { - return reactMutationOptions.onError( - error, - variables, - context - ); - } - // call-time error callback is executed by react-query - }, - onSuccess: ( - data: RecordType, - variables: Partial> = {}, - context: unknown - ) => { - if (mode.current === 'pessimistic') { - // update the getOne and getList query cache with the new result - const { - resource: callTimeResource = resource, - id: callTimeId = id, - } = variables; - updateCache({ - resource: callTimeResource, - id: callTimeId, - data, - }); + if (reactMutationOptions.onError) { + return reactMutationOptions.onError(error, variables, context); + } + // call-time error callback is executed by react-query + }, + onSuccess: ( + data: RecordType, + variables: Partial> = {}, + context: unknown + ) => { + if (mode.current === 'pessimistic') { + // update the getOne and getList query cache with the new result + const { + resource: callTimeResource = resource, + id: callTimeId = id, + } = variables; + updateCache({ + resource: callTimeResource, + id: callTimeId, + data, + }); - if (reactMutationOptions.onSuccess) { - reactMutationOptions.onSuccess( - data, - variables, - context - ); - } - // call-time success callback is executed by react-query - } - }, - onSettled: ( - data: RecordType, - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // Always refetch after error or success: - context.snapshot.forEach(([key]) => { - queryClient.invalidateQueries(key); - }); + if (reactMutationOptions.onSuccess) { + reactMutationOptions.onSuccess(data, variables, context); } + // call-time success callback is executed by react-query + } + }, + onSettled: ( + data: RecordType, + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // Always refetch after error or success: + context.snapshot.forEach(([queryKey]) => { + queryClient.invalidateQueries({ queryKey }); + }); + } - if (reactMutationOptions.onSettled) { - return reactMutationOptions.onSettled( - data, - error, - variables, - context - ); - } - }, - } - ); + if (reactMutationOptions.onSettled) { + return reactMutationOptions.onSettled( + data, + error, + variables, + context + ); + } + }, + }); const update = async ( callTimeResource: string = resource, @@ -371,13 +360,16 @@ export const useUpdate = < * @see https://react-query-v3.tanstack.com/reference/QueryClient#queryclientgetqueriesdata */ snapshot.current = queryKeys.reduce( - (prev, curr) => prev.concat(queryClient.getQueriesData(curr)), + (prev, queryKey) => + prev.concat(queryClient.getQueriesData({ queryKey })), [] as Snapshot ); // Cancel any outgoing re-fetches (so they don't overwrite our optimistic update) await Promise.all( - snapshot.current.map(([key]) => queryClient.cancelQueries(key)) + snapshot.current.map(([queryKey]) => + queryClient.cancelQueries({ queryKey }) + ) ); // Optimistically update to the new value @@ -455,7 +447,7 @@ export type UseUpdateOptions< > = UseMutationOptions< RecordType, MutationError, - Partial> + Partial, 'mutationFn'>> > & { mutationMode?: MutationMode; returnPromise?: boolean }; export type UpdateMutationFunction< diff --git a/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx b/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx index f7b7af6c430..a207c8d1e89 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { QueryClient, useIsMutating } from 'react-query'; +import { QueryClient, useIsMutating } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import undoableEventEmitter from './undoableEventEmitter'; diff --git a/packages/ra-core/src/dataProvider/useUpdateMany.ts b/packages/ra-core/src/dataProvider/useUpdateMany.ts index ac7762b8b77..b7b09cb0709 100644 --- a/packages/ra-core/src/dataProvider/useUpdateMany.ts +++ b/packages/ra-core/src/dataProvider/useUpdateMany.ts @@ -7,7 +7,8 @@ import { MutateOptions, QueryKey, UseInfiniteQueryResult, -} from 'react-query'; + InfiniteData, +} from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; import undoableEventEmitter from './undoableEventEmitter'; @@ -132,22 +133,26 @@ export const useUpdateMany = < data?: RecordType[]; }; - ids.forEach(id => + ids.forEach(id => { queryClient.setQueryData( [resource, 'getOne', { id: String(id), meta }], (record: RecordType) => ({ ...record, ...data }), { updatedAt } - ) - ); + ); + }); queryClient.setQueriesData( - [resource, 'getList'], + { queryKey: [resource, 'getList'] }, (res: GetListResult) => res && res.data ? { ...res, data: updateColl(res.data) } : res, { updatedAt } ); queryClient.setQueriesData( - [resource, 'getInfiniteList'], - (res: UseInfiniteQueryResult['data']) => + { queryKey: [resource, 'getInfiniteList'] }, + ( + res: UseInfiniteQueryResult< + InfiniteData + >['data'] + ) => res && res.pages ? { ...res, @@ -160,13 +165,13 @@ export const useUpdateMany = < { updatedAt } ); queryClient.setQueriesData( - [resource, 'getMany'], + { queryKey: [resource, 'getMany'] }, (coll: RecordType[]) => coll && coll.length > 0 ? updateColl(coll) : coll, { updatedAt } ); queryClient.setQueriesData( - [resource, 'getManyReference'], + { queryKey: [resource, 'getManyReference'] }, (res: GetListResult) => res && res.data ? { data: updateColl(res.data), total: res.total } @@ -179,8 +184,8 @@ export const useUpdateMany = < Array, MutationError, Partial> - >( - ({ + >({ + mutationFn: ({ resource: callTimeResource = resource, ids: callTimeIds = paramsRef.current.ids, data: callTimeData = paramsRef.current.data, @@ -193,105 +198,89 @@ export const useUpdateMany = < meta: callTimeMeta, }) .then(({ data }) => data), - { - ...reactMutationOptions, - onMutate: async ( - variables: Partial> - ) => { - if (reactMutationOptions.onMutate) { - const userContext = - (await reactMutationOptions.onMutate(variables)) || {}; - return { - snapshot: snapshot.current, - // @ts-ignore - ...userContext, - }; - } else { - // Return a context object with the snapshot value - return { snapshot: snapshot.current }; - } - }, - onError: ( - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // If the mutation fails, use the context returned from onMutate to rollback - context.snapshot.forEach(([key, value]) => { - queryClient.setQueryData(key, value); - }); - } + ...reactMutationOptions, + onMutate: async ( + variables: Partial> + ) => { + if (reactMutationOptions.onMutate) { + const userContext = + (await reactMutationOptions.onMutate(variables)) || {}; + return { + snapshot: snapshot.current, + // @ts-ignore + ...userContext, + }; + } else { + // Return a context object with the snapshot value + return { snapshot: snapshot.current }; + } + }, + onError: ( + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // If the mutation fails, use the context returned from onMutate to rollback + context.snapshot.forEach(([key, value]) => { + queryClient.setQueryData(key, value); + }); + } - if (reactMutationOptions.onError) { - return reactMutationOptions.onError( - error, - variables, - context - ); - } - // call-time error callback is executed by react-query - }, - onSuccess: ( - data: Array, - variables: Partial> = {}, - context: unknown - ) => { - if (mode.current === 'pessimistic') { - // update the getOne and getList query cache with the new result - const { - resource: callTimeResource = resource, - ids: callTimeIds = ids, - data: callTimeData = data, - meta: callTimeMeta = meta, - } = variables; - updateCache({ - resource: callTimeResource, - ids: callTimeIds, - data: callTimeData, - meta: callTimeMeta, - }); + if (reactMutationOptions.onError) { + return reactMutationOptions.onError(error, variables, context); + } + // call-time error callback is executed by react-query + }, + onSuccess: ( + data: Array, + variables: Partial> = {}, + context: unknown + ) => { + if (mode.current === 'pessimistic') { + // update the getOne and getList query cache with the new result + const { + resource: callTimeResource = resource, + ids: callTimeIds = ids, + data: callTimeData = data, + meta: callTimeMeta = meta, + } = variables; + updateCache({ + resource: callTimeResource, + ids: callTimeIds, + data: callTimeData, + meta: callTimeMeta, + }); - if (reactMutationOptions.onSuccess) { - reactMutationOptions.onSuccess( - data, - variables, - context - ); - } - // call-time success callback is executed by react-query - } - }, - onSettled: ( - data: Array, - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { - if ( - mode.current === 'optimistic' || - mode.current === 'undoable' - ) { - // Always refetch after error or success: - context.snapshot.forEach(([key]) => { - queryClient.invalidateQueries(key); - }); + if (reactMutationOptions.onSuccess) { + reactMutationOptions.onSuccess(data, variables, context); } + // call-time success callback is executed by react-query + } + }, + onSettled: ( + data: Array, + error: MutationError, + variables: Partial> = {}, + context: { snapshot: Snapshot } + ) => { + if (mode.current === 'optimistic' || mode.current === 'undoable') { + // Always refetch after error or success: + context.snapshot.forEach(([queryKey]) => { + queryClient.invalidateQueries({ queryKey }); + }); + } - if (reactMutationOptions.onSettled) { - return reactMutationOptions.onSettled( - data, - error, - variables, - context - ); - } - }, - } - ); + if (reactMutationOptions.onSettled) { + return reactMutationOptions.onSettled( + data, + error, + variables, + context + ); + } + }, + }); const updateMany = async ( callTimeResource: string = resource, @@ -372,13 +361,16 @@ export const useUpdateMany = < * @see https://react-query-v3.tanstack.com/reference/QueryClient#queryclientgetqueriesdata */ snapshot.current = queryKeys.reduce( - (prev, curr) => prev.concat(queryClient.getQueriesData(curr)), + (prev, queryKey) => + prev.concat(queryClient.getQueriesData({ queryKey })), [] as Snapshot ); // Cancel any outgoing re-fetches (so they don't overwrite our optimistic update) await Promise.all( - snapshot.current.map(([key]) => queryClient.cancelQueries(key)) + snapshot.current.map(([queryKey]) => + queryClient.cancelQueries({ queryKey }) + ) ); // Optimistically update to the new data @@ -457,7 +449,7 @@ export type UseUpdateManyOptions< > = UseMutationOptions< Array, MutationError, - Partial> + Partial, 'mutationFn'>> > & { mutationMode?: MutationMode }; export type UseUpdateManyResult< diff --git a/packages/ra-core/src/form/useUnique.stories.tsx b/packages/ra-core/src/form/useUnique.stories.tsx index fe65de47d59..b187ea5366b 100644 --- a/packages/ra-core/src/form/useUnique.stories.tsx +++ b/packages/ra-core/src/form/useUnique.stories.tsx @@ -15,7 +15,7 @@ import { useUnique, } from '..'; import { createMemoryHistory } from 'history'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; export default { title: 'ra-core/form/useUnique', diff --git a/packages/ra-input-rich-text/package.json b/packages/ra-input-rich-text/package.json index fc405fbd8af..5ebb9dd3911 100644 --- a/packages/ra-input-rich-text/package.json +++ b/packages/ra-input-rich-text/package.json @@ -55,7 +55,7 @@ "ra-core": "^4.15.5", "ra-data-fakerest": "^4.15.5", "ra-ui-materialui": "^4.15.5", - "react": "^17.0.0", + "react": "^18.0.0", "react-dom": "^17.0.0", "react-hook-form": "^7.43.9", "rimraf": "^3.0.2", diff --git a/packages/ra-no-code/package.json b/packages/ra-no-code/package.json index 3c3b3d4f42d..19eddfdc106 100644 --- a/packages/ra-no-code/package.json +++ b/packages/ra-no-code/package.json @@ -28,7 +28,7 @@ "@testing-library/react": "^11.2.3", "@testing-library/user-event": "^13.1.5", "cross-env": "^5.2.0", - "react": "^17.0.0", + "react": "^18.0.0", "react-dom": "^17.0.0", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", @@ -42,6 +42,7 @@ "dependencies": { "@mui/icons-material": "^5.0.1", "@mui/material": "^5.0.2", + "@tanstack/react-query": "^5.8.4", "clsx": "^1.1.1", "date-fns": "^2.19.0", "inflection": "~1.12.0", @@ -50,8 +51,7 @@ "prop-types": "^15.6.1", "ra-data-local-storage": "^4.15.5", "react-admin": "^4.15.5", - "react-dropzone": "^12.0.4", - "react-query": "^3.32.1" + "react-dropzone": "^12.0.4" }, "gitHead": "b227592132da6ae5f01438fa8269e04596cdfdd8" } diff --git a/packages/ra-no-code/src/ui/ImportResourceDialog.tsx b/packages/ra-no-code/src/ui/ImportResourceDialog.tsx index 2d56e1df2de..89b1985d62a 100644 --- a/packages/ra-no-code/src/ui/ImportResourceDialog.tsx +++ b/packages/ra-no-code/src/ui/ImportResourceDialog.tsx @@ -10,7 +10,7 @@ import { TextField, } from '@mui/material'; import { useDropzone } from 'react-dropzone'; -import { useQueryClient } from 'react-query'; +import { useQueryClient } from '@tanstack/react-query'; import { useNotify } from 'react-admin'; import { useNavigate } from 'react-router-dom'; diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index 286294284f4..d853372b4e5 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -38,7 +38,7 @@ "ra-core": "^4.15.5", "ra-i18n-polyglot": "^4.15.5", "ra-language-english": "^4.15.5", - "react": "^17.0.0", + "react": "^18.0.0", "react-dom": "^17.0.0", "react-hook-form": "^7.43.9", "react-is": "^17.0.0", @@ -60,6 +60,7 @@ "react-router-dom": "^6.1.0" }, "dependencies": { + "@tanstack/react-query": "^5.8.4", "autosuggest-highlight": "^3.1.1", "clsx": "^1.1.1", "css-mediaquery": "^0.1.2", @@ -72,7 +73,6 @@ "query-string": "^7.1.1", "react-dropzone": "^12.0.4", "react-error-boundary": "^3.1.4", - "react-query": "^3.32.1", "react-transition-group": "^4.4.1" }, "gitHead": "b227592132da6ae5f01438fa8269e04596cdfdd8" diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx index 4ae3d63f8d6..f8c00261048 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx @@ -20,7 +20,7 @@ import { import { Confirm } from '../layout'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; export const BulkDeleteWithConfirmButton = ( props: BulkDeleteWithConfirmButtonProps diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx index 1594f9f6eed..58677f5f132 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx @@ -16,7 +16,7 @@ import { import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; export const BulkDeleteWithUndoButton = ( props: BulkDeleteWithUndoButtonProps diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx index 6bef0c1df31..11eb1566ca2 100644 --- a/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx @@ -20,7 +20,7 @@ import { import { Confirm } from '../layout'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; export const BulkUpdateWithConfirmButton = ( props: BulkUpdateWithConfirmButtonProps diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx index a6482eb1885..6b3a5546b03 100644 --- a/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx @@ -14,7 +14,7 @@ import { RaRecord, UpdateManyParams, } from 'ra-core'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; diff --git a/packages/ra-ui-materialui/src/button/DeleteButton.tsx b/packages/ra-ui-materialui/src/button/DeleteButton.tsx index 64802b9b7dd..839c7b5d141 100644 --- a/packages/ra-ui-materialui/src/button/DeleteButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteButton.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ReactElement } from 'react'; import PropTypes from 'prop-types'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { RaRecord, MutationMode, diff --git a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx index f165ae098f8..369d5296061 100644 --- a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ActionDelete from '@mui/icons-material/Delete'; import clsx from 'clsx'; import inflection from 'inflection'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { MutationMode, RaRecord, diff --git a/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx index bfca2128990..48ff17a2647 100644 --- a/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx @@ -3,7 +3,7 @@ import { ReactElement, ReactEventHandler } from 'react'; import PropTypes from 'prop-types'; import ActionDelete from '@mui/icons-material/Delete'; import clsx from 'clsx'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { RaRecord, useDeleteWithUndoController, diff --git a/packages/ra-ui-materialui/src/button/PrevNextButtons.stories.tsx b/packages/ra-ui-materialui/src/button/PrevNextButtons.stories.tsx index 786f735131d..2e5669c7ca9 100644 --- a/packages/ra-ui-materialui/src/button/PrevNextButtons.stories.tsx +++ b/packages/ra-ui-materialui/src/button/PrevNextButtons.stories.tsx @@ -10,7 +10,7 @@ import englishMessages from 'ra-language-english'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import { MemoryRouter } from 'react-router'; import { seed, address, internet, name } from 'faker/locale/en_GB'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import { AdminUI, diff --git a/packages/ra-ui-materialui/src/button/SaveButton.tsx b/packages/ra-ui-materialui/src/button/SaveButton.tsx index 13ccf1ac6a6..5eeaf7e68cc 100644 --- a/packages/ra-ui-materialui/src/button/SaveButton.tsx +++ b/packages/ra-ui-materialui/src/button/SaveButton.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { MouseEventHandler, ReactElement, useCallback } from 'react'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { styled } from '@mui/material/styles'; import PropTypes from 'prop-types'; import { Button, ButtonProps, CircularProgress } from '@mui/material'; diff --git a/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx index 6aab3579232..cde4d71c7e1 100644 --- a/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx @@ -18,7 +18,7 @@ import { import { Confirm } from '../layout'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; export const UpdateWithConfirmButton = ( props: UpdateWithConfirmButtonProps diff --git a/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx index 1a6c554e1a2..9e00fd4ed9e 100644 --- a/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx +++ b/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx @@ -12,7 +12,7 @@ import { useUpdate, UpdateParams, } from 'ra-core'; -import { UseMutationOptions } from 'react-query'; +import { UseMutationOptions } from '@tanstack/react-query'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx index a08786e977e..2c921edb6fb 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx @@ -14,7 +14,7 @@ import { } from 'ra-core'; import { styled } from '@mui/material/styles'; import { SxProps } from '@mui/system'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import { fieldPropTypes, FieldProps } from './types'; import { LinearProgress } from '../layout'; diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.tsx index d6c010b6fc8..198fa2fc379 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceField.tsx @@ -19,7 +19,7 @@ import { useTranslate, RaRecord, } from 'ra-core'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import { LinearProgress } from '../layout'; import { Link } from '../Link'; diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyCount.stories.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyCount.stories.tsx index deab236bccc..762b966ef38 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceManyCount.stories.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceManyCount.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { DataProviderContext, RecordContextProvider, diff --git a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx index b92730ec558..f12e734158f 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; import PropTypes from 'prop-types'; -import { UseQueryOptions } from 'react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import { Typography } from '@mui/material'; import { useReferenceOneFieldController, diff --git a/packages/ra-ui-materialui/src/form/SimpleFormConfigurable.stories.tsx b/packages/ra-ui-materialui/src/form/SimpleFormConfigurable.stories.tsx index e5b5c8760a2..353a65c3fe4 100644 --- a/packages/ra-ui-materialui/src/form/SimpleFormConfigurable.stories.tsx +++ b/packages/ra-ui-materialui/src/form/SimpleFormConfigurable.stories.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { PreferencesEditorContextProvider, I18nContextProvider } from 'ra-core'; import { ThemeProvider, createTheme, Box, Paper } from '@mui/material'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import en from 'ra-language-english'; diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx index c4146731d15..1d8d169e5de 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { createMemoryHistory } from 'history'; import { Admin, AdminContext, Datagrid, List, TextField } from 'react-admin'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import { Resource, Form, testDataProvider, useRedirect } from 'ra-core'; import polyglotI18nProvider from 'ra-i18n-polyglot'; import englishMessages from 'ra-language-english'; diff --git a/packages/ra-ui-materialui/src/layout/AppBar.stories.tsx b/packages/ra-ui-materialui/src/layout/AppBar.stories.tsx index 840493971f7..01858183797 100644 --- a/packages/ra-ui-materialui/src/layout/AppBar.stories.tsx +++ b/packages/ra-ui-materialui/src/layout/AppBar.stories.tsx @@ -12,7 +12,7 @@ import { IconButton, } from '@mui/material'; import SettingsIcon from '@mui/icons-material/Settings'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router'; import { I18nContextProvider, AuthContext } from 'ra-core'; diff --git a/packages/ra-ui-materialui/src/layout/Layout.stories.tsx b/packages/ra-ui-materialui/src/layout/Layout.stories.tsx index 78432491e92..530349be3ce 100644 --- a/packages/ra-ui-materialui/src/layout/Layout.stories.tsx +++ b/packages/ra-ui-materialui/src/layout/Layout.stories.tsx @@ -18,7 +18,7 @@ import { memoryStore, } from 'ra-core'; import * as React from 'react'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router'; import { defaultTheme } from '../theme/defaultTheme'; diff --git a/packages/ra-ui-materialui/src/list/Count.stories.tsx b/packages/ra-ui-materialui/src/list/Count.stories.tsx index 5b348ee8e0d..157d5a8422d 100644 --- a/packages/ra-ui-materialui/src/list/Count.stories.tsx +++ b/packages/ra-ui-materialui/src/list/Count.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { DataProviderContext } from 'ra-core'; import { MemoryRouter } from 'react-router-dom'; diff --git a/packages/ra-ui-materialui/src/types.ts b/packages/ra-ui-materialui/src/types.ts index 8745c05719e..de21c0252cd 100644 --- a/packages/ra-ui-materialui/src/types.ts +++ b/packages/ra-ui-materialui/src/types.ts @@ -9,7 +9,7 @@ import { UseCreateMutateParams, UseUpdateMutateParams, } from 'ra-core'; -import { UseQueryOptions, UseMutationOptions } from 'react-query'; +import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'; export interface EditProps< RecordType extends RaRecord = RaRecord, diff --git a/yarn.lock b/yarn.lock index 8486abaaec0..55dcd4497e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1651,7 +1651,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.21.5, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.21.5, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.23.1 resolution: "@babel/runtime@npm:7.23.1" dependencies: @@ -5320,6 +5320,31 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-core@npm:5.8.3": + version: 5.8.3 + resolution: "@tanstack/query-core@npm:5.8.3" + checksum: cc2cc74f63b73dfda241de0c468e81be1db32e601783dafe599ce678b5e2697b6cedac42118d0e4b5aa7abfca2a6534065494157905dffd46446290b24587827 + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^5.8.4": + version: 5.8.4 + resolution: "@tanstack/react-query@npm:5.8.4" + dependencies: + "@tanstack/query-core": 5.8.3 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + react-native: "*" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 495a36b774b063cabe44e2b61c62185fe25c4b6cd4b1d86a1c6b494560981a25534beb8341557559b785a186dcefe99fb6478091a96a51f7337e419d8c42b045 + languageName: node + linkType: hard + "@testing-library/dom@npm:^7.28.1": version: 7.31.2 resolution: "@testing-library/dom@npm:7.31.2" @@ -6362,7 +6387,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:>=16.9.0, @types/react@npm:^17.0.20": +"@types/react@npm:*, @types/react@npm:>=16.9.0": version: 17.0.38 resolution: "@types/react@npm:17.0.38" dependencies: @@ -6384,25 +6409,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:>=17.0.0": - version: 18.0.35 - resolution: "@types/react@npm:18.0.35" +"@types/react@npm:>=18.0.0, @types/react@npm:^18.2.37": + version: 18.2.37 + resolution: "@types/react@npm:18.2.37" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 0a7aff2a500bf3d9f7b467f1125d8e83922f39635442b8c0f227fc4b08f2960c4d95457ac248d3b98f64632b0b38a845dcd0ec364300decd70b7239222a96ac7 - languageName: node - linkType: hard - -"@types/react@npm:^18.0.22": - version: 18.0.24 - resolution: "@types/react@npm:18.0.24" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: 8ea907a8f944421a120f4db9fe52fe3752837e11031ff16b866e835adebb09b2b2ca3ac5f8aa622683778f6d40f791a369c9008c45537c1a7eb64347648fae3c + checksum: 79dd5d23da05bec54e7423ca17096e345eb8fd80a3bf8dd916bb5cdd60677d27c298523aa5b245d090fcc4ec100cfd58c1af4631fbac709d0a9d8be75f9d78a9 languageName: node linkType: hard @@ -8000,7 +8014,7 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:^1.6.16, big-integer@npm:^1.6.44": +"big-integer@npm:^1.6.44": version: 1.6.51 resolution: "big-integer@npm:1.6.51" checksum: c8139662d57f8833a44802f4b65be911679c569535ea73c5cfd3c1c8994eaead1b84b6f63e1db63833e4d4cacb6b6a9e5522178113dfdc8e4c81ed8436f1e8cc @@ -8128,22 +8142,6 @@ __metadata: languageName: node linkType: hard -"broadcast-channel@npm:^3.4.1": - version: 3.7.0 - resolution: "broadcast-channel@npm:3.7.0" - dependencies: - "@babel/runtime": ^7.7.2 - detect-node: ^2.1.0 - js-sha3: 0.8.0 - microseconds: 0.2.0 - nano-time: 1.0.0 - oblivious-set: 1.0.0 - rimraf: 3.0.2 - unload: 2.2.0 - checksum: 95978446f24c685be666f5508a91350bcd4075c08feda929d26c0c678fb24bd421901f19fa8d36cb6f5ed480a334072f3bdce48fa177a8cb29793d88693911cc - languageName: node - linkType: hard - "browser-assert@npm:^1.2.1": version: 1.2.1 resolution: "browser-assert@npm:1.2.1" @@ -9315,14 +9313,14 @@ __metadata: version: 0.0.0-use.local resolution: "create-react-admin@workspace:packages/create-react-admin" dependencies: - "@types/react": ">=17.0.0" + "@types/react": ">=18.0.0" execa: ^5.1.1 fs-extra: ^11.1.1 ink: ^3.2.0 ink-select-input: ^4.2.2 ink-text-input: ^4.0.3 meow: ^9.0.0 - react: ^17.0.0 + react: ^18.0.0 typescript: ^5.1.3 yn: ^5.0.0 bin: @@ -9940,7 +9938,7 @@ __metadata: "@types/jest": ^29.5.2 "@types/node": ^12.12.14 "@types/prop-types": ^15.6.0 - "@types/react": ^17.0.20 + "@types/react": ^18.2.37 "@types/react-dom": ^17.0.9 "@types/recharts": ^1.8.10 "@vitejs/plugin-react": ^2.2.0 @@ -9964,7 +9962,7 @@ __metadata: ra-input-rich-text: ^4.12.0 ra-language-english: ^4.12.0 ra-language-french: ^4.12.0 - react: ^17.0.0 + react: ^18.0.0 react-admin: ^4.12.0 react-app-polyfill: ^2.0.0 react-dom: ^17.0.0 @@ -10048,13 +10046,6 @@ __metadata: languageName: node linkType: hard -"detect-node@npm:^2.0.4, detect-node@npm:^2.1.0": - version: 2.1.0 - resolution: "detect-node@npm:2.1.0" - checksum: f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 - languageName: node - linkType: hard - "detect-package-manager@npm:^2.0.1": version: 2.0.1 resolution: "detect-package-manager@npm:2.0.1" @@ -14556,13 +14547,6 @@ __metadata: languageName: node linkType: hard -"js-sha3@npm:0.8.0": - version: 0.8.0 - resolution: "js-sha3@npm:0.8.0" - checksum: 43a21dc7967c871bd2c46cb1c2ae97441a97169f324e509f382d43330d8f75cf2c96dba7c806ab08a425765a9c847efdd4bffbac2d99c3a4f3de6c0218f40533 - languageName: node - linkType: hard - "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -15581,16 +15565,6 @@ __metadata: languageName: node linkType: hard -"match-sorter@npm:^6.0.2": - version: 6.3.1 - resolution: "match-sorter@npm:6.3.1" - dependencies: - "@babel/runtime": ^7.12.5 - remove-accents: 0.4.2 - checksum: fb805e1f8cd1a41846dd5dcbba810a3bff3e1436a34a8226201d3f7518970171a7dbedb0d99677a6dce2a2925e4fc3cf1d0d82a1203ac9ef65d13d5d290b1dad - languageName: node - linkType: hard - "mdast-util-definitions@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-definitions@npm:4.0.0" @@ -15723,13 +15697,6 @@ __metadata: languageName: node linkType: hard -"microseconds@npm:0.2.0": - version: 0.2.0 - resolution: "microseconds@npm:0.2.0" - checksum: 59dfae1c696c0bacd79603c4df7cd0dcc9e091b7c5556aaca9b0832017d3c0b40ad8f57ca25e0ee5709ef1973404448c4a2fea6c9c1fad7d9e197ff5c1c9c2d5 - languageName: node - linkType: hard - "mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -16086,15 +16053,6 @@ __metadata: languageName: node linkType: hard -"nano-time@npm:1.0.0": - version: 1.0.0 - resolution: "nano-time@npm:1.0.0" - dependencies: - big-integer: ^1.6.16 - checksum: 3bd12e0bcd30867178afdbe8053b3dde5fdd1c665ecd348bf879863049344fbaf05cbb1d7806a825b91efbca011ee115eee52e76fb38b7da9c97931cd9e61f15 - languageName: node - linkType: hard - "nanoclone@npm:^0.2.1": version: 0.2.1 resolution: "nanoclone@npm:0.2.1" @@ -16164,7 +16122,7 @@ __metadata: "@vitejs/plugin-react": ^2.2.0 ra-data-local-storage: ^4.12.0 ra-no-code: ^4.12.0 - react: ^17.0.0 + react: ^18.0.0 react-admin: ^4.12.0 react-dom: ^17.0.0 typescript: ^5.1.3 @@ -16734,13 +16692,6 @@ __metadata: languageName: node linkType: hard -"oblivious-set@npm:1.0.0": - version: 1.0.0 - resolution: "oblivious-set@npm:1.0.0" - checksum: ca8640474ea1e1feb3b5c98d42f5649f114ac4513ef84774e724f22fc7e529f1de3e7f26a0d9593097ab8942ca0bb8c241f7c1bd63c3e33047dd49de3aca9805 - languageName: node - linkType: hard - "on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -18091,12 +18042,13 @@ __metadata: resolution: "ra-core@workspace:packages/ra-core" dependencies: "@hookform/resolvers": ^3.2.0 + "@tanstack/react-query": ^5.8.4 "@testing-library/react": ^11.2.3 "@testing-library/react-hooks": ^7.0.2 "@types/jest": ^29.5.2 "@types/node": ^17.0.8 "@types/node-polyglot": ^0.4.31 - "@types/react": ^17.0.20 + "@types/react": ^18.2.37 clsx: ^1.1.1 cross-env: ^5.2.0 date-fns: ^2.19.0 @@ -18109,11 +18061,10 @@ __metadata: lodash: ~4.17.5 prop-types: ^15.6.1 query-string: ^7.1.1 - react: ^17.0.0 + react: ^18.0.0 react-dom: ^17.0.0 react-hook-form: ^7.43.9 react-is: ^17.0.2 - react-query: ^3.32.1 react-router: ^6.1.0 react-router-dom: ^6.1.0 react-test-renderer: ^16.9.0 || ^17.0.0 @@ -18291,7 +18242,7 @@ __metadata: ra-core: ^4.15.5 ra-data-fakerest: ^4.15.5 ra-ui-materialui: ^4.15.5 - react: ^17.0.0 + react: ^18.0.0 react-dom: ^17.0.0 react-hook-form: ^7.43.9 rimraf: ^3.0.2 @@ -18333,6 +18284,7 @@ __metadata: dependencies: "@mui/icons-material": ^5.0.1 "@mui/material": ^5.0.2 + "@tanstack/react-query": ^5.8.4 "@testing-library/react": ^11.2.3 "@testing-library/user-event": ^13.1.5 clsx: ^1.1.1 @@ -18343,11 +18295,10 @@ __metadata: papaparse: ^5.3.0 prop-types: ^15.6.1 ra-data-local-storage: ^4.15.5 - react: ^17.0.0 + react: ^18.0.0 react-admin: ^4.15.5 react-dom: ^17.0.0 react-dropzone: ^12.0.4 - react-query: ^3.32.1 react-router: ^6.1.0 react-router-dom: ^6.1.0 rimraf: ^3.0.2 @@ -18364,6 +18315,7 @@ __metadata: dependencies: "@mui/icons-material": ^5.0.1 "@mui/material": ^5.0.2 + "@tanstack/react-query": ^5.8.4 "@testing-library/react": ^11.2.3 "@types/dompurify": ^3.0.2 autosuggest-highlight: ^3.1.1 @@ -18384,13 +18336,12 @@ __metadata: ra-core: ^4.15.5 ra-i18n-polyglot: ^4.15.5 ra-language-english: ^4.15.5 - react: ^17.0.0 + react: ^18.0.0 react-dom: ^17.0.0 react-dropzone: ^12.0.4 react-error-boundary: ^3.1.4 react-hook-form: ^7.43.9 react-is: ^17.0.0 - react-query: ^3.32.1 react-router: ^6.1.0 react-router-dom: ^6.1.0 react-test-renderer: ~16.8.6 @@ -18488,7 +18439,7 @@ __metadata: "@types/faker": ^5.1.7 "@types/jest": ^29.5.2 "@types/lodash": ~4.14.168 - "@types/react": ^17.0.20 + "@types/react": ^18.2.37 "@types/react-dom": ^17.0.9 "@vitejs/plugin-react": ^2.2.0 clsx: ^1.1.1 @@ -18497,7 +18448,7 @@ __metadata: lodash: ~4.17.5 prop-types: ^15.7.2 ra-data-fakerest: ^4.12.0 - react: ^17.0.0 + react: ^18.0.0 react-admin: ^4.12.0 react-dom: ^17.0.0 react-error-boundary: ^3.1.4 @@ -18524,7 +18475,7 @@ __metadata: "@storybook/react-webpack5": ^7.5.1 "@storybook/testing-react": ^2.0.0 "@types/jest": ^29.5.2 - "@types/react": ^17.0.20 + "@types/react": ^18.2.37 "@typescript-eslint/eslint-plugin": ^5.60.0 "@typescript-eslint/parser": ^5.60.0 concurrently: ^5.1.0 @@ -18548,7 +18499,7 @@ __metadata: lolex: ~2.3.2 prettier: ~2.1.1 raf: ~3.4.1 - react: ^17.0.0 + react: ^18.0.0 react-dom: ^17.0.0 storybook: ^7.5.1 ts-jest: ^29.1.0 @@ -18798,24 +18749,6 @@ __metadata: languageName: node linkType: hard -"react-query@npm:^3.32.1": - version: 3.34.8 - resolution: "react-query@npm:3.34.8" - dependencies: - "@babel/runtime": ^7.5.5 - broadcast-channel: ^3.4.1 - match-sorter: ^6.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - checksum: df65bbdeb0cce7bbd550cfb77b76d1461236c4b3a7953451020555df574b2508cdd472d68421b07fc10d6bc9f11a051ba2965b832edad5147644ea5cc0b73d33 - languageName: node - linkType: hard - "react-reconciler@npm:^0.26.2": version: 0.26.2 resolution: "react-reconciler@npm:0.26.2" @@ -19072,17 +19005,7 @@ __metadata: languageName: node linkType: hard -"react@npm:^17.0.0": - version: 17.0.2 - resolution: "react@npm:17.0.2" - dependencies: - loose-envify: ^1.1.0 - object-assign: ^4.1.1 - checksum: 07ae8959acf1596f0550685102fd6097d461a54a4fd46a50f88a0cd7daaa97fdd6415de1dcb4bfe0da6aa43221a6746ce380410fa848acc60f8ac41f6649c148 - languageName: node - linkType: hard - -"react@npm:^18.2.0": +"react@npm:^18.0.0, react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" dependencies: @@ -19464,13 +19387,6 @@ __metadata: languageName: node linkType: hard -"remove-accents@npm:0.4.2": - version: 0.4.2 - resolution: "remove-accents@npm:0.4.2" - checksum: 5cbc00efa52df29ce947a0c572ff975b011f5f197ebe7b4f6e527de26aba534cba12d502e3040b72e46ad01de3d4f2d5ef57a6593c964965e43ddb60438da0f8 - languageName: node - linkType: hard - "renderkid@npm:^3.0.0": version: 3.0.0 resolution: "renderkid@npm:3.0.0" @@ -19683,25 +19599,25 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:3.0.2, rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" +"rimraf@npm:^2.6.1": + version: 2.7.1 + resolution: "rimraf@npm:2.7.1" dependencies: glob: ^7.1.3 bin: - rimraf: bin.js - checksum: 9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + rimraf: ./bin.js + checksum: 4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40 languageName: node linkType: hard -"rimraf@npm:^2.6.1": - version: 2.7.1 - resolution: "rimraf@npm:2.7.1" +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" dependencies: glob: ^7.1.3 bin: - rimraf: ./bin.js - checksum: 4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40 + rimraf: bin.js + checksum: 9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 languageName: node linkType: hard @@ -20130,6 +20046,7 @@ __metadata: "@hookform/devtools": ^4.0.2 "@mui/icons-material": ^5.0.1 "@mui/material": ^5.0.2 + "@tanstack/react-query": ^5.8.4 "@vitejs/plugin-react": ^2.2.0 jsonexport: ^3.2.0 lodash: ~4.17.5 @@ -20140,12 +20057,11 @@ __metadata: ra-input-rich-text: ^4.15.5 ra-language-english: ^4.15.5 ra-language-french: ^4.15.5 - react: ^17.0.0 + react: ^18.0.0 react-admin: ^4.15.5 react-app-polyfill: ^1.0.4 react-dom: ^17.0.0 react-hook-form: ^7.43.9 - react-query: ^3.32.1 react-router: ^6.1.0 react-router-dom: ^6.1.0 typescript: ^5.1.3 @@ -21319,7 +21235,7 @@ __metadata: version: 0.0.0-use.local resolution: "tutorial@workspace:examples/tutorial" dependencies: - "@types/react": ^18.0.22 + "@types/react": ^18.2.37 "@types/react-dom": ^18.0.7 "@vitejs/plugin-react": ^2.2.0 ra-data-json-server: ^4.12.0 @@ -21630,16 +21546,6 @@ __metadata: languageName: node linkType: hard -"unload@npm:2.2.0": - version: 2.2.0 - resolution: "unload@npm:2.2.0" - dependencies: - "@babel/runtime": ^7.6.2 - detect-node: ^2.0.4 - checksum: 0a4f86b502e7aa35d39c27373ebeaad4f2b7da793fb3d6308e5337aab541885cfe7b339ea4a1963477bf73fddabd5d69f4f47023dad71224b4b4a25611ef7dd8 - languageName: node - linkType: hard - "unpipe@npm:1.0.0, unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" From 825af95ea92626ba6d5f3aa6eafe191f2792dbe4 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 14:25:48 +0100 Subject: [PATCH 002/103] Upgrade dependencies --- examples/crm/package.json | 6 +- examples/demo/package.json | 8 +- examples/no-code/package.json | 2 +- examples/simple/package.json | 4 +- examples/tutorial/package.json | 2 +- package.json | 2 +- packages/ra-core/package.json | 6 +- packages/ra-input-rich-text/package.json | 2 +- packages/ra-no-code/package.json | 4 +- packages/ra-ui-materialui/package.json | 11 +- yarn.lock | 127 +++++++++++------------ 11 files changed, 88 insertions(+), 86 deletions(-) diff --git a/examples/crm/package.json b/examples/crm/package.json index fbff460de97..8bc6e2bd10e 100644 --- a/examples/crm/package.json +++ b/examples/crm/package.json @@ -12,11 +12,11 @@ "date-fns": "^2.19.0", "faker": "~5.4.0", "lodash": "~4.17.5", - "prop-types": "^15.7.2", + "prop-types": "^15.8.1", "ra-data-fakerest": "^4.12.0", "react": "^18.0.0", "react-admin": "^4.12.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", "react-router": "^6.1.0", "react-router-dom": "^6.1.0" @@ -29,7 +29,7 @@ "@types/jest": "^29.5.2", "@types/lodash": "~4.14.168", "@types/react": "^18.2.37", - "@types/react-dom": "^17.0.9", + "@types/react-dom": "^18.2.16", "@vitejs/plugin-react": "^2.2.0", "rollup-plugin-visualizer": "^5.9.2", "typescript": "^5.1.3", diff --git a/examples/demo/package.json b/examples/demo/package.json index a92516060b2..9061bfa0cd8 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -18,7 +18,7 @@ "graphql-tag": "^2.12.6", "inflection": "~1.12.0", "json-graphql-server": "~2.3.0", - "prop-types": "^15.6.1", + "prop-types": "^15.8.1", "proxy-polyfill": "^0.3.0", "query-string": "^7.1.1", "ra-data-fakerest": "^4.12.0", @@ -32,7 +32,7 @@ "react": "^18.0.0", "react-admin": "^4.12.0", "react-app-polyfill": "^2.0.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", "recharts": "^2.1.15" @@ -46,9 +46,9 @@ "@types/fetch-mock": "^7.3.2", "@types/jest": "^29.5.2", "@types/node": "^12.12.14", - "@types/prop-types": "^15.6.0", + "@types/prop-types": "^15.7.11", "@types/react": "^18.2.37", - "@types/react-dom": "^17.0.9", + "@types/react-dom": "^18.2.16", "@vitejs/plugin-react": "^2.2.0", "rewire": "^5.0.0", "rollup-plugin-visualizer": "^5.9.2", diff --git a/examples/no-code/package.json b/examples/no-code/package.json index 2811e581a60..a611032a72b 100644 --- a/examples/no-code/package.json +++ b/examples/no-code/package.json @@ -13,7 +13,7 @@ "ra-no-code": "^4.12.0", "react": "^18.0.0", "react-admin": "^4.12.0", - "react-dom": "^17.0.0" + "react-dom": "^18.2.0" }, "devDependencies": { "@vitejs/plugin-react": "^2.2.0", diff --git a/examples/simple/package.json b/examples/simple/package.json index c3c1ea09fdf..bb1937ffd1c 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -16,7 +16,7 @@ "@tanstack/react-query": "^5.8.4", "jsonexport": "^3.2.0", "lodash": "~4.17.5", - "prop-types": "^15.7.2", + "prop-types": "^15.8.1", "proxy-polyfill": "^0.3.0", "ra-data-fakerest": "^4.15.5", "ra-i18n-polyglot": "^4.15.5", @@ -25,7 +25,7 @@ "ra-language-french": "^4.15.5", "react": "^18.0.0", "react-admin": "^4.15.5", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", "react-router": "^6.1.0", "react-router-dom": "^6.1.0" diff --git a/examples/tutorial/package.json b/examples/tutorial/package.json index 0f112a3b315..66d852d74a4 100644 --- a/examples/tutorial/package.json +++ b/examples/tutorial/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/react": "^18.2.37", - "@types/react-dom": "^18.0.7", + "@types/react-dom": "^18.2.16", "@vitejs/plugin-react": "^2.2.0", "typescript": "^5.1.3", "vite": "^3.2.0" diff --git a/package.json b/package.json index afdaa92572c..d0d806594c7 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "prettier": "~2.1.1", "raf": "~3.4.1", "react": "^18.0.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "storybook": "^7.5.1", "ts-jest": "^29.1.0", "typescript": "^5.1.3", diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index c2aec9392a9..eb4e2b0c4f5 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -38,7 +38,7 @@ "history": "^5.1.0", "ignore-styles": "~5.0.1", "react": "^18.0.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", @@ -65,9 +65,9 @@ "inflection": "~1.12.0", "jsonexport": "^3.2.0", "lodash": "~4.17.5", - "prop-types": "^15.6.1", + "prop-types": "^15.8.1", "query-string": "^7.1.1", - "react-is": "^17.0.2" + "react-is": "^18.2.0" }, "gitHead": "e936ff2c3f887d2e98ef136cf3b3f3d254725fc4" } diff --git a/packages/ra-input-rich-text/package.json b/packages/ra-input-rich-text/package.json index 5ebb9dd3911..e0bcab3bc5f 100644 --- a/packages/ra-input-rich-text/package.json +++ b/packages/ra-input-rich-text/package.json @@ -56,7 +56,7 @@ "ra-data-fakerest": "^4.15.5", "ra-ui-materialui": "^4.15.5", "react": "^18.0.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", "rimraf": "^3.0.2", "tippy.js": "^6.3.7", diff --git a/packages/ra-no-code/package.json b/packages/ra-no-code/package.json index 19eddfdc106..d6d9082df36 100644 --- a/packages/ra-no-code/package.json +++ b/packages/ra-no-code/package.json @@ -29,7 +29,7 @@ "@testing-library/user-event": "^13.1.5", "cross-env": "^5.2.0", "react": "^18.0.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", "rimraf": "^3.0.2", @@ -48,7 +48,7 @@ "inflection": "~1.12.0", "lodash": "~4.17.5", "papaparse": "^5.3.0", - "prop-types": "^15.6.1", + "prop-types": "^15.8.1", "ra-data-local-storage": "^4.15.5", "react-admin": "^4.15.5", "react-dropzone": "^12.0.4" diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index d853372b4e5..ef42ef56d81 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -30,6 +30,9 @@ "@mui/material": "^5.0.2", "@testing-library/react": "^11.2.3", "@types/dompurify": "^3.0.2", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.16", + "@types/react-transition-group": "^4.4.9", "cross-env": "^5.2.0", "expect": "^27.4.6", "file-api": "~0.10.4", @@ -39,9 +42,9 @@ "ra-i18n-polyglot": "^4.15.5", "ra-language-english": "^4.15.5", "react": "^18.0.0", - "react-dom": "^17.0.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", - "react-is": "^17.0.0", + "react-is": "^18.2.0", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", "react-test-renderer": "~16.8.6", @@ -69,11 +72,11 @@ "inflection": "~1.12.0", "jsonexport": "^3.2.0", "lodash": "~4.17.5", - "prop-types": "^15.7.0", + "prop-types": "^15.8.1", "query-string": "^7.1.1", "react-dropzone": "^12.0.4", "react-error-boundary": "^3.1.4", - "react-transition-group": "^4.4.1" + "react-transition-group": "^4.4.5" }, "gitHead": "b227592132da6ae5f01438fa8269e04596cdfdd8" } diff --git a/yarn.lock b/yarn.lock index 55dcd4497e4..b4799337375 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6321,13 +6321,20 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.6.0, @types/prop-types@npm:^15.7.4": +"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.4": version: 15.7.4 resolution: "@types/prop-types@npm:15.7.4" checksum: 014bb826592fab01499931259969aafc21d5a8ff4ece3e3fb8e2b5186bed17656f7dcdccf9a98c27fee74d7d0697aa3f53ea971a72679597f0ca0c3d5ca585d3 languageName: node linkType: hard +"@types/prop-types@npm:^15.7.11": + version: 15.7.11 + resolution: "@types/prop-types@npm:15.7.11" + checksum: e53423cf9d510515ef8b47ff42f4f1b65a7b7b37c8704e2dbfcb9a60defe0c0e1f3cb1acfdeb466bad44ca938d7c79bffdd51b48ffb659df2432169d0b27a132 + languageName: node + linkType: hard + "@types/qs@npm:*, @types/qs@npm:^6.9.5": version: 6.9.7 resolution: "@types/qs@npm:6.9.7" @@ -6342,7 +6349,7 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:>=16.9.0, @types/react-dom@npm:^17.0.9": +"@types/react-dom@npm:>=16.9.0": version: 17.0.11 resolution: "@types/react-dom@npm:17.0.11" dependencies: @@ -6351,12 +6358,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.7": - version: 18.0.8 - resolution: "@types/react-dom@npm:18.0.8" +"@types/react-dom@npm:^18.2.16": + version: 18.2.16 + resolution: "@types/react-dom@npm:18.2.16" dependencies: "@types/react": "*" - checksum: e5e18d30a272b799e7579929b27f8cd078c5ee92cc11a4be80b63d9f4330d22c97cc2aeb89945fc8e88d59a92438fa89f37ac92021665021988ed56cb864b424 + checksum: 6efd8936c103b17d24c33fbba9f90aa390559b9027308a20bc08a4af84c2b915a5f615149d106768b14c3b04796cc70e5759ae01fb2d20b307b75697e38473cd languageName: node linkType: hard @@ -6387,29 +6394,16 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:>=16.9.0": - version: 17.0.38 - resolution: "@types/react@npm:17.0.38" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: 828cd27ac220be8232535c4451b0020f6f05535d62407e5fe2547544a7d3123777ed31ab547d7c7f3d8b59d37268ee4b9209f1c110809f084cbbf0a7e27cd7ef - languageName: node - linkType: hard - -"@types/react@npm:>=16": - version: 18.2.6 - resolution: "@types/react@npm:18.2.6" +"@types/react-transition-group@npm:^4.4.9": + version: 4.4.9 + resolution: "@types/react-transition-group@npm:4.4.9" dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: 7740541afde84a50c557e576dd564a861b45ad436d1c3ed33496d70046a6eb734bfe7f7f18995f88bdc50be0d295c2b878f705a6b9573cc0781e11ea58470139 + "@types/react": "*" + checksum: 279cd319202f7ec24ecad174dffd19fd161250bc438bf3c62951f467093e5664a0c895b185976bf45f55b68ec901e520924216eb8abafe934b66f55337319ff5 languageName: node linkType: hard -"@types/react@npm:>=18.0.0, @types/react@npm:^18.2.37": +"@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:>=16.9.0, @types/react@npm:>=18.0.0, @types/react@npm:^18.2.37": version: 18.2.37 resolution: "@types/react@npm:18.2.37" dependencies: @@ -9937,9 +9931,9 @@ __metadata: "@types/inflection": ^1.5.28 "@types/jest": ^29.5.2 "@types/node": ^12.12.14 - "@types/prop-types": ^15.6.0 + "@types/prop-types": ^15.7.11 "@types/react": ^18.2.37 - "@types/react-dom": ^17.0.9 + "@types/react-dom": ^18.2.16 "@types/recharts": ^1.8.10 "@vitejs/plugin-react": ^2.2.0 clsx: ^1.1.1 @@ -9951,7 +9945,7 @@ __metadata: graphql-tag: ^2.12.6 inflection: ~1.12.0 json-graphql-server: ~2.3.0 - prop-types: ^15.6.1 + prop-types: ^15.8.1 proxy-polyfill: ^0.3.0 query-string: ^7.1.1 ra-data-fakerest: ^4.12.0 @@ -9965,7 +9959,7 @@ __metadata: react: ^18.0.0 react-admin: ^4.12.0 react-app-polyfill: ^2.0.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-router: ^6.1.0 react-router-dom: ^6.1.0 recharts: ^2.1.15 @@ -16124,7 +16118,7 @@ __metadata: ra-no-code: ^4.12.0 react: ^18.0.0 react-admin: ^4.12.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 typescript: ^5.1.3 vite: ^3.2.0 languageName: unknown @@ -17631,7 +17625,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.1, prop-types@npm:^15.6.2, prop-types@npm:^15.7.0, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -18059,12 +18053,12 @@ __metadata: inflection: ~1.12.0 jsonexport: ^3.2.0 lodash: ~4.17.5 - prop-types: ^15.6.1 + prop-types: ^15.8.1 query-string: ^7.1.1 react: ^18.0.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-hook-form: ^7.43.9 - react-is: ^17.0.2 + react-is: ^18.2.0 react-router: ^6.1.0 react-router-dom: ^6.1.0 react-test-renderer: ^16.9.0 || ^17.0.0 @@ -18243,7 +18237,7 @@ __metadata: ra-data-fakerest: ^4.15.5 ra-ui-materialui: ^4.15.5 react: ^18.0.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-hook-form: ^7.43.9 rimraf: ^3.0.2 tippy.js: ^6.3.7 @@ -18293,11 +18287,11 @@ __metadata: inflection: ~1.12.0 lodash: ~4.17.5 papaparse: ^5.3.0 - prop-types: ^15.6.1 + prop-types: ^15.8.1 ra-data-local-storage: ^4.15.5 react: ^18.0.0 react-admin: ^4.15.5 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-dropzone: ^12.0.4 react-router: ^6.1.0 react-router-dom: ^6.1.0 @@ -18318,6 +18312,9 @@ __metadata: "@tanstack/react-query": ^5.8.4 "@testing-library/react": ^11.2.3 "@types/dompurify": ^3.0.2 + "@types/react": ^18.2.37 + "@types/react-dom": ^18.2.16 + "@types/react-transition-group": ^4.4.9 autosuggest-highlight: ^3.1.1 clsx: ^1.1.1 cross-env: ^5.2.0 @@ -18331,21 +18328,21 @@ __metadata: inflection: ~1.12.0 jsonexport: ^3.2.0 lodash: ~4.17.5 - prop-types: ^15.7.0 + prop-types: ^15.8.1 query-string: ^7.1.1 ra-core: ^4.15.5 ra-i18n-polyglot: ^4.15.5 ra-language-english: ^4.15.5 react: ^18.0.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-dropzone: ^12.0.4 react-error-boundary: ^3.1.4 react-hook-form: ^7.43.9 - react-is: ^17.0.0 + react-is: ^18.2.0 react-router: ^6.1.0 react-router-dom: ^6.1.0 react-test-renderer: ~16.8.6 - react-transition-group: ^4.4.1 + react-transition-group: ^4.4.5 rimraf: ^3.0.2 typescript: ^5.1.3 peerDependencies: @@ -18440,17 +18437,17 @@ __metadata: "@types/jest": ^29.5.2 "@types/lodash": ~4.14.168 "@types/react": ^18.2.37 - "@types/react-dom": ^17.0.9 + "@types/react-dom": ^18.2.16 "@vitejs/plugin-react": ^2.2.0 clsx: ^1.1.1 date-fns: ^2.19.0 faker: ~5.4.0 lodash: ~4.17.5 - prop-types: ^15.7.2 + prop-types: ^15.8.1 ra-data-fakerest: ^4.12.0 react: ^18.0.0 react-admin: ^4.12.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-error-boundary: ^3.1.4 react-router: ^6.1.0 react-router-dom: ^6.1.0 @@ -18500,7 +18497,7 @@ __metadata: prettier: ~2.1.1 raf: ~3.4.1 react: ^18.0.0 - react-dom: ^17.0.0 + react-dom: ^18.2.0 storybook: ^7.5.1 ts-jest: ^29.1.0 typescript: ^5.1.3 @@ -18615,19 +18612,6 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^17.0.0": - version: 17.0.2 - resolution: "react-dom@npm:17.0.2" - dependencies: - loose-envify: ^1.1.0 - object-assign: ^4.1.1 - scheduler: ^0.20.2 - peerDependencies: - react: 17.0.2 - checksum: 51abbcb72450fe527ebf978c3bc989ba266630faaa53f47a2fae5392369729e8de62b2e4683598cbe651ea7873cd34ec7d5127e2f50bf4bfe6bd0c3ad9bddcb0 - languageName: node - linkType: hard - "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -18728,14 +18712,14 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.12.0 || ^17.0.0, react-is@npm:^17.0.0, react-is@npm:^17.0.1, react-is@npm:^17.0.2": +"react-is@npm:^16.12.0 || ^17.0.0, react-is@npm:^17.0.1, react-is@npm:^17.0.2": version: 17.0.2 resolution: "react-is@npm:17.0.2" checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 languageName: node linkType: hard -"react-is@npm:^18.0.0": +"react-is@npm:^18.0.0, react-is@npm:^18.2.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" checksum: 6eb5e4b28028c23e2bfcf73371e72cd4162e4ac7ab445ddae2afe24e347a37d6dc22fae6e1748632cd43c6d4f9b8f86dcf26bf9275e1874f436d129952528ae0 @@ -18990,7 +18974,7 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:^4.4.1, react-transition-group@npm:^4.4.2": +"react-transition-group@npm:^4.4.2": version: 4.4.2 resolution: "react-transition-group@npm:4.4.2" dependencies: @@ -19005,6 +18989,21 @@ __metadata: languageName: node linkType: hard +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": ^7.5.5 + dom-helpers: ^5.0.1 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + "react@npm:^18.0.0, react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" @@ -20050,7 +20049,7 @@ __metadata: "@vitejs/plugin-react": ^2.2.0 jsonexport: ^3.2.0 lodash: ~4.17.5 - prop-types: ^15.7.2 + prop-types: ^15.8.1 proxy-polyfill: ^0.3.0 ra-data-fakerest: ^4.15.5 ra-i18n-polyglot: ^4.15.5 @@ -20060,7 +20059,7 @@ __metadata: react: ^18.0.0 react-admin: ^4.15.5 react-app-polyfill: ^1.0.4 - react-dom: ^17.0.0 + react-dom: ^18.2.0 react-hook-form: ^7.43.9 react-router: ^6.1.0 react-router-dom: ^6.1.0 @@ -21236,7 +21235,7 @@ __metadata: resolution: "tutorial@workspace:examples/tutorial" dependencies: "@types/react": ^18.2.37 - "@types/react-dom": ^18.0.7 + "@types/react-dom": ^18.2.16 "@vitejs/plugin-react": ^2.2.0 ra-data-json-server: ^4.12.0 react: ^18.2.0 From fa12ff83286b1c0c8a2b9c13e7d854d00fdfa37b Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 14:26:10 +0100 Subject: [PATCH 003/103] Update ra-ui-materialui --- packages/ra-ui-materialui/src/auth/Logout.tsx | 6 +++--- .../src/button/BulkDeleteWithConfirmButton.tsx | 4 ++-- .../src/button/BulkDeleteWithUndoButton.tsx | 4 ++-- .../src/button/BulkUpdateWithConfirmButton.tsx | 4 ++-- .../src/button/BulkUpdateWithUndoButton.tsx | 4 ++-- .../src/button/DeleteWithConfirmButton.tsx | 4 ++-- .../src/button/DeleteWithUndoButton.tsx | 4 ++-- .../src/button/UpdateWithConfirmButton.tsx | 4 ++-- .../src/button/UpdateWithUndoButton.tsx | 4 ++-- packages/ra-ui-materialui/src/form/FormTabHeader.tsx | 4 ++-- packages/ra-ui-materialui/src/input/SelectInput.tsx | 11 +++++------ packages/ra-ui-materialui/src/layout/Confirm.tsx | 2 +- packages/ra-ui-materialui/src/layout/UserMenu.tsx | 2 +- packages/ra-ui-materialui/src/list/ListGuesser.tsx | 2 +- .../src/list/datagrid/DatagridBody.tsx | 1 - packages/ra-ui-materialui/src/theme/ThemeProvider.tsx | 10 ++++++++-- 16 files changed, 37 insertions(+), 33 deletions(-) diff --git a/packages/ra-ui-materialui/src/auth/Logout.tsx b/packages/ra-ui-materialui/src/auth/Logout.tsx index 96213389424..9a61172a3ce 100644 --- a/packages/ra-ui-materialui/src/auth/Logout.tsx +++ b/packages/ra-ui-materialui/src/auth/Logout.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { styled, Theme } from '@mui/material/styles'; -import { useCallback, FunctionComponent, ReactElement } from 'react'; +import { useCallback, FunctionComponent, ReactNode } from 'react'; import PropTypes from 'prop-types'; import { ListItemIcon, @@ -60,7 +60,7 @@ export const Logout: FunctionComponent< Logout.propTypes = { className: PropTypes.string, redirectTo: PropTypes.string, - icon: PropTypes.element, + icon: PropTypes.node, }; const PREFIX = 'RaLogout'; @@ -79,5 +79,5 @@ const StyledMenuItem = styled(MenuItem, { export interface LogoutProps { className?: string; redirectTo?: string; - icon?: ReactElement; + icon?: ReactNode; } diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx index f8c00261048..53a82ed3e99 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx @@ -43,7 +43,7 @@ export const BulkDeleteWithConfirmButton = ( const resource = useResourceContext(props); const refresh = useRefresh(); const translate = useTranslate(); - const [deleteMany, { isLoading }] = useDeleteMany( + const [deleteMany, { isPending }] = useDeleteMany( resource, { ids: selectedIds, meta: mutationMeta }, { @@ -109,7 +109,7 @@ export const BulkDeleteWithConfirmButton = ( { deleteMany( @@ -81,7 +81,7 @@ export const BulkDeleteWithUndoButton = ( {icon} diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx index 11eb1566ca2..8a35d237302 100644 --- a/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx @@ -75,7 +75,7 @@ export const BulkUpdateWithConfirmButton = ( } = props; const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; - const [updateMany, { isLoading }] = useUpdateMany( + const [updateMany, { isPending }] = useUpdateMany( resource, { ids: selectedIds, data, meta: mutationMeta }, { @@ -114,7 +114,7 @@ export const BulkUpdateWithConfirmButton = ( {icon} diff --git a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx index 369d5296061..f26cba9cb8e 100644 --- a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx @@ -42,7 +42,7 @@ export const DeleteWithConfirmButton = ( const { open, - isLoading, + isPending, handleDialogOpen, handleDialogClose, handleDelete, @@ -69,7 +69,7 @@ export const DeleteWithConfirmButton = ( ( const record = useRecordContext(props); const resource = useResourceContext(props); - const { isLoading, handleDelete } = useDeleteWithUndoController({ + const { isPending, handleDelete } = useDeleteWithUndoController({ record, resource, redirect, @@ -42,7 +42,7 @@ export const DeleteWithUndoButton = ( return ( diff --git a/examples/crm/src/notes/Note.tsx b/examples/crm/src/notes/Note.tsx index 305be0c7ca6..b561262af48 100644 --- a/examples/crm/src/notes/Note.tsx +++ b/examples/crm/src/notes/Note.tsx @@ -40,7 +40,7 @@ export const Note = ({ const record = useRecordContext(); const notify = useNotify(); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const [deleteNote] = useDelete( resource, @@ -150,7 +150,7 @@ export const Note = ({ type="submit" color="primary" variant="contained" - disabled={isLoading} + disabled={isPending} > Update Note diff --git a/examples/crm/vite.config.ts b/examples/crm/vite.config.ts index d7b514c1eea..51ce3224b41 100644 --- a/examples/crm/vite.config.ts +++ b/examples/crm/vite.config.ts @@ -46,7 +46,7 @@ export default defineConfig({ preserveSymlinks: true, alias: [ // allow profiling in production - { find: 'react-dom', replacement: 'react-dom/profiling' }, + // { find: 'react-dom', replacement: 'react-dom/profiling' }, { find: 'scheduler/tracing', replacement: 'scheduler/tracing-profiling', diff --git a/examples/demo/src/authProvider.ts b/examples/demo/src/authProvider.ts index 4ee6f7d9bb5..f3af79c2db6 100644 --- a/examples/demo/src/authProvider.ts +++ b/examples/demo/src/authProvider.ts @@ -13,7 +13,7 @@ const authProvider: AuthProvider = { checkError: () => Promise.resolve(), checkAuth: () => localStorage.getItem('username') ? Promise.resolve() : Promise.reject(), - getPermissions: () => Promise.resolve(), + getPermissions: () => Promise.resolve([]), getIdentity: () => Promise.resolve({ id: 'user', diff --git a/examples/demo/src/index.tsx b/examples/demo/src/index.tsx index c5787a505cb..6743a50f12e 100644 --- a/examples/demo/src/index.tsx +++ b/examples/demo/src/index.tsx @@ -1,8 +1,11 @@ -import 'proxy-polyfill'; - -import * as React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import App from './App'; -ReactDOM.render(, document.getElementById('root')); +const container = document.getElementById('root'); +if (!container) { + throw new Error('No container found'); +} +const root = createRoot(container); + +root.render(); diff --git a/examples/demo/src/reviews/AcceptButton.tsx b/examples/demo/src/reviews/AcceptButton.tsx index f7acbcf3b23..25e45f2bf52 100644 --- a/examples/demo/src/reviews/AcceptButton.tsx +++ b/examples/demo/src/reviews/AcceptButton.tsx @@ -20,7 +20,7 @@ const AcceptButton = () => { const redirectTo = useRedirect(); const record = useRecordContext(); - const [approve, { isLoading }] = useUpdate( + const [approve, { isPending }] = useUpdate( 'reviews', { id: record.id, data: { status: 'accepted' }, previousData: record }, { @@ -49,7 +49,7 @@ const AcceptButton = () => { startIcon={ theme.palette.success.main }} /> } - disabled={isLoading} + disabled={isPending} > {translate('resources.reviews.action.accept')} diff --git a/examples/demo/src/reviews/BulkAcceptButton.tsx b/examples/demo/src/reviews/BulkAcceptButton.tsx index b38d4ca86a0..e8ac612bb02 100644 --- a/examples/demo/src/reviews/BulkAcceptButton.tsx +++ b/examples/demo/src/reviews/BulkAcceptButton.tsx @@ -17,7 +17,7 @@ const BulkAcceptButton = () => { const notify = useNotify(); const unselectAll = useUnselectAll('reviews'); - const [updateMany, { isLoading }] = useUpdateMany( + const [updateMany, { isPending }] = useUpdateMany( 'reviews', { ids: selectedIds, data: { status: 'accepted' } }, { @@ -41,7 +41,7 @@ const BulkAcceptButton = () => { diff --git a/examples/demo/src/reviews/BulkRejectButton.tsx b/examples/demo/src/reviews/BulkRejectButton.tsx index 2cd51cf094c..395d8fd8eb8 100644 --- a/examples/demo/src/reviews/BulkRejectButton.tsx +++ b/examples/demo/src/reviews/BulkRejectButton.tsx @@ -17,7 +17,7 @@ const BulkRejectButton = () => { const notify = useNotify(); const unselectAll = useUnselectAll('reviews'); - const [updateMany, { isLoading }] = useUpdateMany( + const [updateMany, { isPending }] = useUpdateMany( 'reviews', { ids: selectedIds, data: { status: 'rejected' } }, { @@ -41,7 +41,7 @@ const BulkRejectButton = () => { diff --git a/examples/demo/src/reviews/RejectButton.tsx b/examples/demo/src/reviews/RejectButton.tsx index 6a70bfb7df4..f001d0f8ce1 100644 --- a/examples/demo/src/reviews/RejectButton.tsx +++ b/examples/demo/src/reviews/RejectButton.tsx @@ -20,7 +20,7 @@ const RejectButton = () => { const redirectTo = useRedirect(); const record = useRecordContext(); - const [reject, { isLoading }] = useUpdate( + const [reject, { isPending }] = useUpdate( 'reviews', { id: record.id, data: { status: 'rejected' }, previousData: record }, { @@ -50,7 +50,7 @@ const RejectButton = () => { startIcon={ theme.palette.error.main }} /> } - disabled={isLoading} + disabled={isPending} > {translate('resources.reviews.action.reject')} diff --git a/examples/demo/vite.config.ts b/examples/demo/vite.config.ts index d7b514c1eea..51ce3224b41 100644 --- a/examples/demo/vite.config.ts +++ b/examples/demo/vite.config.ts @@ -46,7 +46,7 @@ export default defineConfig({ preserveSymlinks: true, alias: [ // allow profiling in production - { find: 'react-dom', replacement: 'react-dom/profiling' }, + // { find: 'react-dom', replacement: 'react-dom/profiling' }, { find: 'scheduler/tracing', replacement: 'scheduler/tracing-profiling', diff --git a/examples/no-code/src/main.tsx b/examples/no-code/src/main.tsx index 35477277ac2..34fbaecead9 100644 --- a/examples/no-code/src/main.tsx +++ b/examples/no-code/src/main.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { Root } from 'ra-no-code'; import { defaultTheme } from 'react-admin'; import { createTheme } from '@mui/material/styles'; @@ -7,9 +7,11 @@ import { createTheme } from '@mui/material/styles'; // FIXME Material UI bug https://github.com/mui/material-ui/issues/13394 const theme = createTheme(defaultTheme); -ReactDOM.render( +const container = document.createElement('root'); +const root = createRoot(container); + +root.render( - , - document.getElementById('root') + ); diff --git a/examples/simple/src/Layout.tsx b/examples/simple/src/Layout.tsx index dc0c902ee43..e65ed0a1dfa 100644 --- a/examples/simple/src/Layout.tsx +++ b/examples/simple/src/Layout.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ReactQueryDevtools } from 'react-query/devtools'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { AppBar, Layout, InspectorButton, TitlePortal } from 'react-admin'; const MyAppBar = () => ( @@ -12,9 +12,6 @@ const MyAppBar = () => ( export default props => ( <> - + ); diff --git a/examples/simple/src/comments/PostPreview.tsx b/examples/simple/src/comments/PostPreview.tsx index a3b5428d3f0..a9766ea2c4e 100644 --- a/examples/simple/src/comments/PostPreview.tsx +++ b/examples/simple/src/comments/PostPreview.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useQueryClient } from 'react-query'; +import { useQueryClient } from '@tanstack/react-query'; import { SimpleShowLayout, TextField, diff --git a/examples/simple/src/index.tsx b/examples/simple/src/index.tsx index 800767145e2..7d6afa78fbc 100644 --- a/examples/simple/src/index.tsx +++ b/examples/simple/src/index.tsx @@ -1,7 +1,7 @@ /* eslint react/jsx-key: off */ import * as React from 'react'; import { Admin, Resource, CustomRoutes } from 'react-admin'; // eslint-disable-line import/no-unresolved -import { render } from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { Route } from 'react-router-dom'; import authProvider from './authProvider'; @@ -15,7 +15,10 @@ import posts from './posts'; import users from './users'; import tags from './tags'; -render( +const container = document.getElementById('root'); +const root = createRoot(container); + +root.render( - , - document.getElementById('root') + ); diff --git a/packages/ra-core/src/auth/useCheckAuth.spec.tsx b/packages/ra-core/src/auth/useCheckAuth.spec.tsx index a57e971e8e3..fee39c5aa24 100644 --- a/packages/ra-core/src/auth/useCheckAuth.spec.tsx +++ b/packages/ra-core/src/auth/useCheckAuth.spec.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import expect from 'expect'; import { screen, render, waitFor } from '@testing-library/react'; import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { createMemoryHistory } from 'history'; import { useCheckAuth } from './useCheckAuth'; diff --git a/packages/ra-core/src/auth/useGetIdentity.spec.tsx b/packages/ra-core/src/auth/useGetIdentity.spec.tsx index fb48c23f868..bbd84ec9141 100644 --- a/packages/ra-core/src/auth/useGetIdentity.spec.tsx +++ b/packages/ra-core/src/auth/useGetIdentity.spec.tsx @@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { Basic, ErrorCase, ResetIdentity } from './useGetIdentity.stories'; import useGetIdentity from './useGetIdentity'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import AuthContext from './AuthContext'; describe('useGetIdentity', () => { diff --git a/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx b/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx index ddcf7282ede..0c042b00270 100644 --- a/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx +++ b/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import expect from 'expect'; import { render, screen, waitFor } from '@testing-library/react'; import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom'; -import { QueryClientProvider, QueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { createMemoryHistory } from 'history'; import { useHandleAuthCallback } from './useHandleAuthCallback'; diff --git a/packages/ra-core/src/auth/useLogout.spec.tsx b/packages/ra-core/src/auth/useLogout.spec.tsx index 898da047a02..15c57fa7a4f 100644 --- a/packages/ra-core/src/auth/useLogout.spec.tsx +++ b/packages/ra-core/src/auth/useLogout.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import expect from 'expect'; import { useGetOne } from '../dataProvider'; diff --git a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx index ca8beb470cd..c6fad1412ae 100644 --- a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx +++ b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.tsx @@ -306,6 +306,7 @@ const getRoutesAndResourceFromNodes = ( resources: [], }; } + // @ts-ignore Children.forEach(children, element => { if (!React.isValidElement(element)) { // Ignore non-elements. This allows people to more easily inline diff --git a/packages/ra-core/src/dataProvider/useDelete.spec.tsx b/packages/ra-core/src/dataProvider/useDelete.spec.tsx index d214a0a35f1..617ab93be1b 100644 --- a/packages/ra-core/src/dataProvider/useDelete.spec.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.spec.tsx @@ -18,7 +18,7 @@ import { ErrorCase as ErrorCaseUndoable, SuccessCase as SuccessCaseUndoable, } from './useDelete.undoable.stories'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; describe('useDelete', () => { it('returns a callback that can be used with deleteOne arguments', async () => { diff --git a/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx b/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx index 863d5135946..92117121330 100644 --- a/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx +++ b/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx @@ -5,7 +5,7 @@ import expect from 'expect'; import { CoreAdminContext } from '../core'; import { testDataProvider } from './testDataProvider'; import { useDeleteMany } from './useDeleteMany'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; describe('useDeleteMany', () => { it('returns a callback that can be used with update arguments', async () => { diff --git a/packages/ra-core/src/dataProvider/useGetList.spec.tsx b/packages/ra-core/src/dataProvider/useGetList.spec.tsx index 18f868b0b5c..46d21d70643 100644 --- a/packages/ra-core/src/dataProvider/useGetList.spec.tsx +++ b/packages/ra-core/src/dataProvider/useGetList.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import expect from 'expect'; import { render, waitFor } from '@testing-library/react'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useGetList } from './useGetList'; diff --git a/packages/ra-core/src/dataProvider/useUpdate.spec.tsx b/packages/ra-core/src/dataProvider/useUpdate.spec.tsx index aeb85e09dbb..2d10c1c0bb2 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.spec.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.spec.tsx @@ -17,7 +17,7 @@ import { ErrorCase as ErrorCaseUndoable, SuccessCase as SuccessCaseUndoable, } from './useUpdate.undoable.stories'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; describe('useUpdate', () => { describe('mutate', () => { diff --git a/packages/ra-core/src/dataProvider/useUpdateMany.spec.tsx b/packages/ra-core/src/dataProvider/useUpdateMany.spec.tsx index b2478c2862f..c04d20d1bac 100644 --- a/packages/ra-core/src/dataProvider/useUpdateMany.spec.tsx +++ b/packages/ra-core/src/dataProvider/useUpdateMany.spec.tsx @@ -5,7 +5,7 @@ import expect from 'expect'; import { testDataProvider } from './testDataProvider'; import { CoreAdminContext } from '../core'; import { useUpdateMany } from './useUpdateMany'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; describe('useUpdateMany', () => { it('returns a callback that can be used with update arguments', async () => { diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index 3b85ba897a5..d06e31f996f 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -307,7 +307,10 @@ export type RenderResourcesFunction = ( | Promise // (permissions) => fetch().then(() => <>) | ResourceElement[] // // (permissions) => [, , ] | Promise; // (permissions) => fetch().then(() => [, , ]) -export type AdminChildren = RenderResourcesFunction | ReactNode; +export type AdminChildren = + | RenderResourcesFunction + | Iterable + | ReactNode; export type TitleComponent = string | ReactElement; export type CatchAllComponent = ComponentType<{ title?: TitleComponent }>; From 6059cd87818248b4873e5a53da5eb8a131338968 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:25:18 +0100 Subject: [PATCH 007/103] Update react-test-renderer --- packages/ra-core/package.json | 2 +- packages/ra-ui-materialui/package.json | 2 +- yarn.lock | 75 +++++++++----------------- 3 files changed, 27 insertions(+), 52 deletions(-) diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index b7c0155d92a..10651b383fe 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -42,7 +42,7 @@ "react-hook-form": "^7.43.9", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", - "react-test-renderer": "^16.9.0 || ^17.0.0", + "react-test-renderer": "^18.2.0", "recharts": "^2.1.15", "rimraf": "^3.0.2", "typescript": "^5.1.3", diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index 0658302894e..d40809989de 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -47,7 +47,7 @@ "react-is": "^18.2.0", "react-router": "^6.1.0", "react-router-dom": "^6.1.0", - "react-test-renderer": "~16.8.6", + "react-test-renderer": "^18.2.0", "rimraf": "^3.0.2", "typescript": "^5.1.3" }, diff --git a/yarn.lock b/yarn.lock index 3b33d70e0a7..20aaf24cf7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18246,7 +18246,7 @@ __metadata: react-is: ^18.2.0 react-router: ^6.1.0 react-router-dom: ^6.1.0 - react-test-renderer: ^16.9.0 || ^17.0.0 + react-test-renderer: ^18.2.0 recharts: ^2.1.15 rimraf: ^3.0.2 typescript: ^5.1.3 @@ -18526,7 +18526,7 @@ __metadata: react-is: ^18.2.0 react-router: ^6.1.0 react-router-dom: ^6.1.0 - react-test-renderer: ~16.8.6 + react-test-renderer: ^18.2.0 react-transition-group: ^4.4.5 rimraf: ^3.0.2 typescript: ^5.1.3 @@ -18890,27 +18890,27 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.6": +"react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: 33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 languageName: node linkType: hard -"react-is@npm:^16.12.0 || ^17.0.0, react-is@npm:^17.0.1, react-is@npm:^17.0.2": - version: 17.0.2 - resolution: "react-is@npm:17.0.2" - checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 - languageName: node - linkType: hard - -"react-is@npm:^18.0.0, react-is@npm:^18.2.0": +"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" checksum: 6eb5e4b28028c23e2bfcf73371e72cd4162e4ac7ab445ddae2afe24e347a37d6dc22fae6e1748632cd43c6d4f9b8f86dcf26bf9275e1874f436d129952528ae0 languageName: node linkType: hard +"react-is@npm:^17.0.1, react-is@npm:^17.0.2": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 + languageName: node + linkType: hard + "react-lifecycles-compat@npm:^3.0.4": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" @@ -19048,15 +19048,15 @@ __metadata: languageName: node linkType: hard -"react-shallow-renderer@npm:^16.13.1": - version: 16.14.1 - resolution: "react-shallow-renderer@npm:16.14.1" +"react-shallow-renderer@npm:^16.15.0": + version: 16.15.0 + resolution: "react-shallow-renderer@npm:16.15.0" dependencies: object-assign: ^4.1.1 - react-is: ^16.12.0 || ^17.0.0 + react-is: ^16.12.0 || ^17.0.0 || ^18.0.0 peerDependencies: - react: ^16.0.0 || ^17.0.0 - checksum: b95f7ee2d88cf093e5b370663943ceb9199b8dff4217cb8cbffdf5e60966286f898a3ce7257b3c31bab54d698a3c80e70e92b0a8320bc69bdcf4d565678261c9 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: c194d741792e86043a4ae272f7353c1cb9412bc649945c4220c6a101a6ea5410cceb3d65d5a4d750f11a24f7426e8eec7977e8a4e3ad5d3ee235ca2b18166fa8 languageName: node linkType: hard @@ -19116,31 +19116,16 @@ __metadata: languageName: node linkType: hard -"react-test-renderer@npm:^16.9.0 || ^17.0.0": - version: 17.0.2 - resolution: "react-test-renderer@npm:17.0.2" - dependencies: - object-assign: ^4.1.1 - react-is: ^17.0.2 - react-shallow-renderer: ^16.13.1 - scheduler: ^0.20.2 - peerDependencies: - react: 17.0.2 - checksum: a4ea1e745a87bb9015540d96a3077b614bf88e306a0edd639f8fb849a393fa5104e84eca4349bc4b026f2f0b115a4172d58950d7076316115795266557659276 - languageName: node - linkType: hard - -"react-test-renderer@npm:~16.8.6": - version: 16.8.6 - resolution: "react-test-renderer@npm:16.8.6" +"react-test-renderer@npm:^18.2.0": + version: 18.2.0 + resolution: "react-test-renderer@npm:18.2.0" dependencies: - object-assign: ^4.1.1 - prop-types: ^15.6.2 - react-is: ^16.8.6 - scheduler: ^0.13.6 + react-is: ^18.2.0 + react-shallow-renderer: ^16.15.0 + scheduler: ^0.23.0 peerDependencies: - react: ^16.0.0 - checksum: 51eea5ce0b3d070ff5e61e6e957de1b0101094297a8b3079f4a69226d7f606ef1c8211daed3c9b726aab1a87eddbce0ff205af8971dbb913ed114c09b772d23e + react: ^18.2.0 + checksum: 53dfada1da1e8dd0498a5601e9eea3dc6ca23c6c2694d1cab9712faea869c11e4ce1c9a618d674cb668a668b41fb6bcf9a7b0a078cd853b1922f002fa22f42c8 languageName: node linkType: hard @@ -19947,16 +19932,6 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.13.6": - version: 0.13.6 - resolution: "scheduler@npm:0.13.6" - dependencies: - loose-envify: ^1.1.0 - object-assign: ^4.1.1 - checksum: 6dee75ece8e2a36fe66a9bfabe465026a057ed5f4d941111d94e03c748359812a6f7c5fa12a1cdf2d2dc60e92e0c29e19eb72b1e068f2265c862c0e112ae35e1 - languageName: node - linkType: hard - "scheduler@npm:^0.20.2": version: 0.20.2 resolution: "scheduler@npm:0.20.2" From 5c49394ac3589cd549a822014abd497f52931d23 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:25:41 +0100 Subject: [PATCH 008/103] Fix useGetOne and useShowController --- .../src/controller/show/useShowController.ts | 12 +++++++----- packages/ra-core/src/dataProvider/useGetOne.ts | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/ra-core/src/controller/show/useShowController.ts b/packages/ra-core/src/controller/show/useShowController.ts index 726eccd1ccc..42a5964072c 100644 --- a/packages/ra-core/src/controller/show/useShowController.ts +++ b/packages/ra-core/src/controller/show/useShowController.ts @@ -1,9 +1,13 @@ import { useParams } from 'react-router-dom'; -import { UseQueryOptions } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { RaRecord } from '../../types'; -import { useGetOne, useRefresh, UseGetOneHookValue } from '../../dataProvider'; +import { + useGetOne, + useRefresh, + UseGetOneHookValue, + UseGetOneOptions, +} from '../../dataProvider'; import { useTranslate } from '../../i18n'; import { useRedirect } from '../../routing'; import { useNotify } from '../../notification'; @@ -111,9 +115,7 @@ export const useShowController = ( export interface ShowControllerProps { disableAuthentication?: boolean; id?: RecordType['id']; - queryOptions?: Omit, 'queryFn' | 'queryKey'> & { - meta?: any; - }; + queryOptions?: UseGetOneOptions; resource?: string; } diff --git a/packages/ra-core/src/dataProvider/useGetOne.ts b/packages/ra-core/src/dataProvider/useGetOne.ts index 21039fd548f..3f0fc592547 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.ts +++ b/packages/ra-core/src/dataProvider/useGetOne.ts @@ -5,6 +5,7 @@ import { UseQueryResult, } from '@tanstack/react-query'; import { useDataProvider } from './useDataProvider'; +import { useEffect } from 'react'; /** * Call the dataProvider.getOne() method and return the resolved value @@ -65,6 +66,18 @@ export const useGetOne = ( ...queryOptions, }); + useEffect(() => { + if (result.data && onSuccess) { + onSuccess(result.data); + } + }, [onSuccess, result.data]); + + useEffect(() => { + if (result.error && onError) { + onError(result.error); + } + }, [onError, result.error]); + return result; }; @@ -72,7 +85,7 @@ export type UseGetOneOptions = Omit< UseQueryOptions['data']>, 'queryKey' | 'queryFn' > & { - onSuccess?: (data: GetOneResult) => void; + onSuccess?: (data: GetOneResult['data']) => void; onError?: (error: Error) => void; }; From 929620da7f0a41e9ca06f0ece1ece2cce43e1f76 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:25:57 +0100 Subject: [PATCH 009/103] Fix some auth tests --- .../ra-core/src/auth/addRefreshAuthToAuthProvider.spec.ts | 4 ++-- packages/ra-core/src/auth/useAuthenticated.ts | 2 +- packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx | 4 ++-- packages/ra-core/src/auth/useHandleAuthCallback.ts | 2 +- packages/ra-core/src/auth/usePermissions.spec.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/ra-core/src/auth/addRefreshAuthToAuthProvider.spec.ts b/packages/ra-core/src/auth/addRefreshAuthToAuthProvider.spec.ts index c9084cb197b..14d2e292cea 100644 --- a/packages/ra-core/src/auth/addRefreshAuthToAuthProvider.spec.ts +++ b/packages/ra-core/src/auth/addRefreshAuthToAuthProvider.spec.ts @@ -8,8 +8,8 @@ describe('addRefreshAuthToAuthProvider', () => { logout: jest.fn(), checkAuth: jest.fn(), checkError: jest.fn(), - getIdentity: jest.fn(), - getPermissions: jest.fn(), + getIdentity: jest.fn().mockResolvedValue({}), + getPermissions: jest.fn().mockResolvedValue({}), }; it('should call refreshAuth before calling checkAuth', async () => { diff --git a/packages/ra-core/src/auth/useAuthenticated.ts b/packages/ra-core/src/auth/useAuthenticated.ts index a73de72b2ba..4c8c1627e8b 100644 --- a/packages/ra-core/src/auth/useAuthenticated.ts +++ b/packages/ra-core/src/auth/useAuthenticated.ts @@ -37,7 +37,7 @@ export type UseAuthenticatedOptions = Omit< UseQueryOptions & { params?: ParamsType; }, - 'queryKey' + 'queryKey' | 'queryFn' >; const emptyParams = {}; diff --git a/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx b/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx index 0c042b00270..92d9351b457 100644 --- a/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx +++ b/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx @@ -49,14 +49,14 @@ const authProvider: AuthProvider = { return Promise.resolve(); }, getPermissions: () => Promise.reject('not authenticated'), - handleCallback: () => Promise.resolve(), + handleCallback: () => Promise.resolve({}), }; const queryClient = new QueryClient(); describe('useHandleAuthCallback', () => { afterEach(() => { - redirect.mockClear(); + jest.clearAllMocks(); }); it('should redirect to the home route by default when the callback was successfully handled', async () => { diff --git a/packages/ra-core/src/auth/useHandleAuthCallback.ts b/packages/ra-core/src/auth/useHandleAuthCallback.ts index b671eb804cc..ce2c18164e9 100644 --- a/packages/ra-core/src/auth/useHandleAuthCallback.ts +++ b/packages/ra-core/src/auth/useHandleAuthCallback.ts @@ -40,7 +40,7 @@ export const useHandleAuthCallback = ( useEffect(() => { if (queryResult.data) { if (onSuccess) { - onSuccess(queryResult.data); + return onSuccess(queryResult.data); } // AuthProviders relying on a third party services redirect back to the app can't // use the location state to store the path on which the user was before the login. diff --git a/packages/ra-core/src/auth/usePermissions.spec.tsx b/packages/ra-core/src/auth/usePermissions.spec.tsx index a3df64a2fee..e56be55c235 100644 --- a/packages/ra-core/src/auth/usePermissions.spec.tsx +++ b/packages/ra-core/src/auth/usePermissions.spec.tsx @@ -16,7 +16,7 @@ const UsePermissions = ({ children }: any) => { const stateInpector = state => (
{state.isLoading && 'LOADING'} - {state.permissions && PERMISSIONS: {state.permissions}} + {state.data && PERMISSIONS: {state.data}} {state.error && 'ERROR'}
); From 14e3c5007c06dc8147b98e2346aafd742eb2c715 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:44:42 +0100 Subject: [PATCH 010/103] Fix useAuthState --- packages/ra-core/src/auth/useAuthState.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts index fdc7453f33d..14bb0bb3774 100644 --- a/packages/ra-core/src/auth/useAuthState.ts +++ b/packages/ra-core/src/auth/useAuthState.ts @@ -64,7 +64,10 @@ const useAuthState = ( queryKey: ['auth', 'checkAuth', params], queryFn: () => { // The authProvider is optional in react-admin - return authProvider?.checkAuth(params).then(() => true); + if (!authProvider) { + return true; + } + return authProvider.checkAuth(params).then(() => true); }, retry: false, ...options, From c093b909ef38b4c7096da5df452af9e94d726aec Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:44:54 +0100 Subject: [PATCH 011/103] Fix useListController --- .../src/controller/list/useListController.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/ra-core/src/controller/list/useListController.ts b/packages/ra-core/src/controller/list/useListController.ts index a6d71822584..6c5339c4186 100644 --- a/packages/ra-core/src/controller/list/useListController.ts +++ b/packages/ra-core/src/controller/list/useListController.ts @@ -1,10 +1,13 @@ import { isValidElement, useEffect, useMemo } from 'react'; -import { UseQueryOptions } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { useTranslate } from '../../i18n'; import { useNotify } from '../../notification'; -import { useGetList, UseGetListHookValue } from '../../dataProvider'; +import { + useGetList, + UseGetListHookValue, + UseGetListOptions, +} from '../../dataProvider'; import { SORT_ASC } from './queryReducer'; import { defaultExporter } from '../../export'; import { FilterPayload, SortPayload, RaRecord, Exporter } from '../../types'; @@ -336,17 +339,7 @@ export interface ListControllerProps { * ); * } */ - queryOptions?: Omit< - UseQueryOptions<{ - data: RecordType[]; - total?: number; - pageInfo?: { - hasNextPage?: boolean; - hasPreviousPage?: boolean; - }; - }>, - 'queryFn' | 'queryKey' - > & { meta?: any }; + queryOptions?: UseGetListOptions; /** * The resource name. Defaults to the resource from ResourceContext. From b06d9eaa61c4fdbabcded4350de51cfa125817ab Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:45:20 +0100 Subject: [PATCH 012/103] Fix react-query imports --- packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx | 2 +- .../ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx | 2 +- packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx index d8673c609cb..5f0fa9d2bdf 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx @@ -8,7 +8,7 @@ import { useGetMany, ResourceDefinitionContextProvider, } from 'ra-core'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import { ReferenceField } from './ReferenceField'; diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx index 6ea2d3db8e3..a0c748e6824 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx @@ -13,7 +13,7 @@ import { Form, useInput, } from 'ra-core'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import { AdminContext } from '../AdminContext'; import { SimpleForm } from '../form'; diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx index 5b9b8ca0129..c2faeacd097 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import expect from 'expect'; import { render, screen, waitFor, fireEvent } from '@testing-library/react'; -import { QueryClient } from 'react-query'; +import { QueryClient } from '@tanstack/react-query'; import { testDataProvider, useChoicesContext, From ae03d12a1988ca557d7279dd2e69528eeeafa156 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:20:52 +0100 Subject: [PATCH 013/103] Fix useUnique tests --- packages/ra-core/src/form/useUnique.spec.tsx | 105 ++++++++++--------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/packages/ra-core/src/form/useUnique.spec.tsx b/packages/ra-core/src/form/useUnique.spec.tsx index 82400fa887a..d18d534d04c 100644 --- a/packages/ra-core/src/form/useUnique.spec.tsx +++ b/packages/ra-core/src/form/useUnique.spec.tsx @@ -33,20 +33,25 @@ describe('useUnique', () => { await screen.findByDisplayValue('John Doe'); fireEvent.click(screen.getByText('Submit')); - await screen.findByText('Must be unique'); - expect(dataProvider.getList).toHaveBeenCalledWith('users', { - filter: { - name: 'John Doe', - }, - pagination: { - page: 1, - perPage: 1, - }, - sort: { - field: 'id', - order: 'ASC', + await waitFor( + () => { + expect(dataProvider.getList).toHaveBeenCalledWith('users', { + filter: { + name: 'John Doe', + }, + pagination: { + page: 1, + perPage: 1, + }, + sort: { + field: 'id', + order: 'ASC', + }, + }); }, - }); + { timeout: 5000 } + ); + await screen.findByText('Must be unique'); expect(dataProvider.create).not.toHaveBeenCalled(); }); @@ -80,33 +85,36 @@ describe('useUnique', () => { id: 1, }) ); - fireEvent.change(screen.getByDisplayValue('John Doe'), { + fireEvent.change(await screen.findByDisplayValue('John Doe'), { target: { value: 'Jane Doe' }, }); - fireEvent.blur(screen.getByDisplayValue('Jane Doe')); + fireEvent.blur(await screen.findByDisplayValue('Jane Doe')); fireEvent.click(screen.getByText('Submit')); - await waitFor(() => - expect(dataProvider.getList).toHaveBeenCalledWith('users', { - filter: { - name: 'Jane Doe', - }, - pagination: { - page: 1, - perPage: 1, - }, - sort: { - field: 'id', - order: 'ASC', - }, - }) + await waitFor( + () => + expect(dataProvider.getList).toHaveBeenCalledWith('users', { + filter: { + name: 'Jane Doe', + }, + pagination: { + page: 1, + perPage: 1, + }, + sort: { + field: 'id', + order: 'ASC', + }, + }), + { timeout: 5000 } ); await screen.findByText('Must be unique'); fireEvent.change(screen.getByDisplayValue('Jane Doe'), { target: { value: 'John Doe' }, }); - await waitFor(() => - expect(screen.queryByText('Must be unique')).toBeNull() + await waitFor( + () => expect(screen.queryByText('Must be unique')).toBeNull(), + { timeout: 5000 } ); }); @@ -224,23 +232,26 @@ describe('useUnique', () => { fireEvent.click(screen.getByText('Submit')); - await waitFor(() => { - expect(dataProvider.getList).toHaveBeenCalledWith('users', { - filter: { - identity: { - name: 'John Doe', + await waitFor( + () => { + expect(dataProvider.getList).toHaveBeenCalledWith('users', { + filter: { + identity: { + name: 'John Doe', + }, }, - }, - pagination: { - page: 1, - perPage: 1, - }, - sort: { - field: 'id', - order: 'ASC', - }, - }); - }); + pagination: { + page: 1, + perPage: 1, + }, + sort: { + field: 'id', + order: 'ASC', + }, + }); + }, + { timeout: 5000 } + ); await screen.findByText('Must be unique'); expect(dataProvider.create).not.toHaveBeenCalled(); }); From 228c4242d135145a6ee12a8798c6df8ffa1ee242 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:14:42 +0100 Subject: [PATCH 014/103] Fix useConfigureAdminRouterFromChildren tests --- ...eConfigureAdminRouterFromChildren.spec.tsx | 200 ++++++++++++------ 1 file changed, 132 insertions(+), 68 deletions(-) diff --git a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx index c044598d58f..2e164ca3e69 100644 --- a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx +++ b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx @@ -190,7 +190,10 @@ const TestedComponentWithPermissions = () => { }; const TestedComponentWithOnlyLazyCustomRoutes = ({ history }) => { - const [lazyRoutes, setLazyRoutes] = React.useState(null); + const [ + lazyRoutes, + setLazyRoutes, + ] = React.useState(null); React.useEffect(() => { const timer = setTimeout( @@ -243,31 +246,18 @@ const TestedComponentWithForcedRoutes = () => { ); }; -const expectResource = (resource: string) => - expect(screen.queryByText(`"name":"${resource}"`, { exact: false })); - -const expectResourceView = ( - resource: string, - view: 'list' | 'create' | 'edit' | 'show' -) => - expect( - screen.queryByText( - `"has${view.at(0).toUpperCase()}${view.substring(1)}":true`, - { - selector: `[data-resource=${resource}]`, - exact: false, - } - ) - ); - describe('useConfigureAdminRouterFromChildren', () => { it('should always load static resources', async () => { render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - await waitFor(() => expectResource('posts').not.toBeNull()); - expectResource('comments').not.toBeNull(); - expectResource('user').toBeNull(); - expectResource('admin').toBeNull(); + await screen.findByText(`"name":"posts"`, { exact: false }); + await screen.findByText(`"name":"comments"`, { exact: false }); + expect( + screen.queryByText(`"name":"user"`, { exact: false }) + ).toBeNull(); + expect( + screen.queryByText(`"name":"admin"`, { exact: false }) + ).toBeNull(); }); it('should not call the children function until the permissions have been retrieved', async () => { const callback = jest.fn(() => @@ -282,61 +272,99 @@ describe('useConfigureAdminRouterFromChildren', () => { it('should load dynamic resource definitions', async () => { render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResource('user').not.toBeNull(); - expectResource('admin').not.toBeNull(); + await screen.findByText(`"name":"user"`, { exact: false }); + await screen.findByText(`"name":"admin"`, { exact: false }); }); it('should accept function returning null', async () => { render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResource('user').not.toBeNull(); - expectResource('admin').not.toBeNull(); + await screen.findByText(`"name":"user"`, { exact: false }); + await screen.findByText(`"name":"admin"`, { exact: false }); }); it('should call registerResource with the permissions', async () => { render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - - expectResourceView('posts', 'list').not.toBeNull(); - expectResourceView('posts', 'create').not.toBeNull(); - expectResourceView('posts', 'edit').not.toBeNull(); - expectResourceView('posts', 'show').not.toBeNull(); - expectResourceView('comments', 'list').not.toBeNull(); - expectResourceView('comments', 'create').toBeNull(); - expectResourceView('comments', 'edit').toBeNull(); - expectResourceView('comments', 'show').not.toBeNull(); - expectResourceView('users', 'list').not.toBeNull(); - expectResourceView('users', 'create').toBeNull(); - expectResourceView('users', 'edit').toBeNull(); - expectResourceView('users', 'show').toBeNull(); + await screen.findByText(`"name":"posts"`, { + exact: false, + }); + await screen.findByText(`"hasList":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasCreate":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasShow":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasList":true`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasShow":true`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasCreate":false`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasEdit":false`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasList":true`, { + selector: `[data-resource=users]`, + exact: false, + }); + await screen.findByText(`"hasShow":false`, { + selector: `[data-resource=users]`, + exact: false, + }); + await screen.findByText(`"hasCreate":false`, { + selector: `[data-resource=users]`, + exact: false, + }); + await screen.findByText(`"hasEdit":false`, { + selector: `[data-resource=users]`, + exact: false, + }); }); it('should allow adding new resource after the first render', async () => { const { rerender } = render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResource('posts').not.toBeNull(); - expectResource('comments').not.toBeNull(); - expectResource('user').not.toBeNull(); - expectResource('admin').toBeNull(); + await screen.findByText(`"name":"posts"`, { exact: false }); + await screen.findByText(`"name":"comments"`, { exact: false }); + await screen.findByText(`"name":"user"`, { exact: false }); + expect( + screen.queryByText(`"name":"admin"`, { exact: false }) + ).toBeNull(); rerender(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResource('posts').not.toBeNull(); - expectResource('comments').not.toBeNull(); - expectResource('user').not.toBeNull(); - expectResource('admin').not.toBeNull(); + await screen.findByText(`"name":"posts"`, { exact: false }); + await screen.findByText(`"name":"comments"`, { exact: false }); + await screen.findByText(`"name":"user"`, { exact: false }); + await screen.findByText(`"name":"admin"`, { exact: false }); }); it('should allow removing a resource after the first render', async () => { const { rerender } = render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResource('posts').not.toBeNull(); - expectResource('comments').not.toBeNull(); - expectResource('user').not.toBeNull(); - expectResource('admin').not.toBeNull(); + await screen.findByText(`"name":"posts"`, { exact: false }); + await screen.findByText(`"name":"comments"`, { exact: false }); + await screen.findByText(`"name":"user"`, { exact: false }); + await screen.findByText(`"name":"admin"`, { exact: false }); rerender(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResource('posts').not.toBeNull(); - expectResource('comments').not.toBeNull(); - expectResource('user').not.toBeNull(); - expectResource('admin').toBeNull(); + await screen.findByText(`"name":"posts"`, { exact: false }); + await screen.findByText(`"name":"comments"`, { exact: false }); + await screen.findByText(`"name":"user"`, { exact: false }); + expect( + screen.queryByText(`"name":"admin"`, { exact: false }) + ).toBeNull(); }); it('should allow dynamically loaded custom routes without any resources', async () => { const history = createMemoryHistory(); @@ -346,23 +374,59 @@ describe('useConfigureAdminRouterFromChildren', () => { await new Promise(resolve => setTimeout(resolve, 1010)); expect(screen.queryByText('Ready')).toBeNull(); history.push('/foo'); - expect(screen.queryByText('Foo')).not.toBeNull(); + await screen.findByText('Foo'); }); it('should support forcing hasEdit hasCreate or hasShow', async () => { render(); await waitFor(() => expect(screen.queryByText('Loading')).toBeNull()); - expectResourceView('posts', 'list').not.toBeNull(); - expectResourceView('posts', 'create').not.toBeNull(); - expectResourceView('posts', 'edit').not.toBeNull(); - expectResourceView('posts', 'show').not.toBeNull(); - expectResourceView('comments', 'list').not.toBeNull(); - expectResourceView('comments', 'create').toBeNull(); - expectResourceView('comments', 'edit').toBeNull(); - expectResourceView('comments', 'show').toBeNull(); - expectResourceView('user', 'list').not.toBeNull(); - expectResourceView('user', 'create').toBeNull(); - expectResourceView('user', 'edit').not.toBeNull(); - expectResourceView('user', 'show').toBeNull(); + await screen.findByText(`"hasList":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasShow":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasEdit":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasCreate":true`, { + selector: `[data-resource=posts]`, + exact: false, + }); + await screen.findByText(`"hasList":true`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasShow":false`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasEdit":false`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasCreate":false`, { + selector: `[data-resource=comments]`, + exact: false, + }); + await screen.findByText(`"hasList":true`, { + selector: `[data-resource=user]`, + exact: false, + }); + await screen.findByText(`"hasShow":false`, { + selector: `[data-resource=user]`, + exact: false, + }); + await screen.findByText(`"hasEdit":true`, { + selector: `[data-resource=user]`, + exact: false, + }); + await screen.findByText(`"hasCreate":false`, { + selector: `[data-resource=user]`, + exact: false, + }); }); }); From 12b4fb17d68acfa145e6be7a704f79f79934930b Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:16:38 +0100 Subject: [PATCH 015/103] Fix useGetList --- .../src/dataProvider/useGetList.spec.tsx | 91 ++++++++++++------- .../ra-core/src/dataProvider/useGetList.ts | 2 +- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useGetList.spec.tsx b/packages/ra-core/src/dataProvider/useGetList.spec.tsx index 46d21d70643..c5fc7e5399d 100644 --- a/packages/ra-core/src/dataProvider/useGetList.spec.tsx +++ b/packages/ra-core/src/dataProvider/useGetList.spec.tsx @@ -5,16 +5,25 @@ import { QueryClient } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useGetList } from './useGetList'; -import { DataProvider } from '../types'; +import { PaginationPayload, SortPayload } from '../types'; +import { testDataProvider } from './testDataProvider'; const UseGetList = ({ resource = 'posts', pagination = { page: 1, perPage: 10 }, - sort = { field: 'id', order: 'DESC' }, + sort = { field: 'id', order: 'DESC' } as const, filter = {}, options = {}, meta = undefined, callback = null, +}: { + resource?: string; + pagination?: PaginationPayload; + sort?: SortPayload; + filter?: any; + options?: any; + meta?: any; + callback?: any; }) => { const hookValue = useGetList( resource, @@ -27,11 +36,15 @@ const UseGetList = ({ describe('useGetList', () => { it('should call dataProvider.getList() on mount', async () => { - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => - Promise.resolve({ data: [{ id: 1, title: 'foo' }], total: 1 }) + Promise.resolve({ + data: [{ id: 1, title: 'foo' }], + total: 1, + }) ), - }; + }); render( @@ -48,11 +61,12 @@ describe('useGetList', () => { }); it('should not call the dataProvider on update', async () => { - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'foo' }], total: 1 }) ), - }; + }); const { rerender } = render( @@ -72,11 +86,12 @@ describe('useGetList', () => { }); it('should call the dataProvider on update when the resource changes', async () => { - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'foo' }], total: 1 }) ), - }; + }); const { rerender } = render( @@ -96,11 +111,12 @@ describe('useGetList', () => { }); it('should accept a meta parameter', async () => { - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'foo' }], total: 1 }) ), - }; + }); render( { total: 1, } ); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'live' }], total: 1 }) ), - }; + }); render( { it('should return isFetching false once the dataProvider returns', async () => { const callback = jest.fn(); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [ @@ -174,7 +192,7 @@ describe('useGetList', () => { total: 2, }) ), - }; + }); render( @@ -195,9 +213,10 @@ describe('useGetList', () => { it('should set the error state when the dataProvider fails', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); const callback = jest.fn(); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.reject(new Error('failed'))), - }; + }); render( @@ -218,7 +237,8 @@ describe('useGetList', () => { it('should execute success side effects on success', async () => { const onSuccess1 = jest.fn(); const onSuccess2 = jest.fn(); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest .fn() .mockReturnValueOnce( @@ -239,7 +259,7 @@ describe('useGetList', () => { total: 2, }) ), - }; + }); render( @@ -272,9 +292,10 @@ describe('useGetList', () => { it('should execute error side effects on failure', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); const onError = jest.fn(); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.reject(new Error('failed'))), - }; + }); render( @@ -289,11 +310,12 @@ describe('useGetList', () => { it('should pre-populate getOne Query Cache', async () => { const callback = jest.fn(); const queryClient = new QueryClient(); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'live' }], total: 1 }) ), - }; + }); render( { const callback = jest.fn(); const onSuccess = jest.fn(); const queryClient = new QueryClient(); - const dataProvider = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'live' }], total: 1 }) ), - }; + }); render( { it('should not pre-populate getOne Query Cache if more than 100 results', async () => { const callback: any = jest.fn(); const queryClient = new QueryClient(); - const dataProvider: any = { + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: Array.from(Array(101).keys()).map(index => ({ @@ -357,7 +381,7 @@ describe('useGetList', () => { total: 101, }) ), - }; + }); render( { ).toBeUndefined(); }); - it('should not fail when the query is disabled and the cache gets updated by another query', async () => { + // NOTE: it seems that disabled queries are not updated anymore when the cache is updated + it.skip('should not fail when the query is disabled and the cache gets updated by another query', async () => { const callback: any = jest.fn(); const onSuccess = jest.fn(); const queryClient = new QueryClient(); - const dataProvider = ({ + const dataProvider = testDataProvider({ + // @ts-ignore getList: jest.fn(() => Promise.resolve({ data: [{ id: 1, title: 'live' }], total: 1 }) ), - } as unknown) as DataProvider; + }); render( { expect(callback).toHaveBeenCalled(); }); // Simulate the side-effect of e.g. a call to delete - queryClient.setQueriesData(['posts', 'getList'], res => res); + queryClient.setQueriesData( + { queryKey: ['posts', 'getList'] }, + res => res + ); // If we get this far without an error being thrown, the test passes await waitFor(() => { expect(onSuccess).toHaveBeenCalled(); diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts index 81036957214..03167328ee2 100644 --- a/packages/ra-core/src/dataProvider/useGetList.ts +++ b/packages/ra-core/src/dataProvider/useGetList.ts @@ -104,7 +104,7 @@ export const useGetList = ( }); } // execute call-time onSuccess if provided - if (onSuccess) { + if (result.data && onSuccess) { onSuccess(result.data); } }, [meta, onSuccess, queryClient, resource, result.data]); From 399b8515b9876353ddaec75e863ef153f4493eb2 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:16:47 +0100 Subject: [PATCH 016/103] Fix useGetOne --- .../src/dataProvider/useGetOne.spec.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useGetOne.spec.tsx b/packages/ra-core/src/dataProvider/useGetOne.spec.tsx index 91e70ad9310..0f614556bcd 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.spec.tsx +++ b/packages/ra-core/src/dataProvider/useGetOne.spec.tsx @@ -11,7 +11,13 @@ const UseGetOne = ({ id, meta = undefined, options = {}, - callback = null, + callback = undefined, +}: { + resource: string; + id: string | number; + meta?: object; + options?: object; + callback?: Function; }) => { const hookValue = useGetOne(resource, { id, meta }, options); if (callback) callback(hookValue); @@ -162,11 +168,13 @@ describe('useGetOne', () => { await waitFor(() => { expect(dataProvider.getOne).toHaveBeenCalledTimes(1); }); - expect(hookValue).toHaveBeenCalledWith( - expect.objectContaining({ - error: new Error('failed'), - }) - ); + await waitFor(() => { + expect(hookValue).toHaveBeenCalledWith( + expect.objectContaining({ + error: new Error('failed'), + }) + ); + }); }); it('should execute success side effects on success', async () => { From af0d03811faea8d980252e58ebd40e7c7b1d7f67 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:19:46 +0100 Subject: [PATCH 017/103] Fix useInfiniteGetList --- .../src/dataProvider/useInfiniteGetList.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useInfiniteGetList.ts b/packages/ra-core/src/dataProvider/useInfiniteGetList.ts index e20063d22b1..0575e7969d7 100644 --- a/packages/ra-core/src/dataProvider/useInfiniteGetList.ts +++ b/packages/ra-core/src/dataProvider/useInfiniteGetList.ts @@ -66,7 +66,7 @@ import { useEffect } from 'react'; export const useInfiniteGetList = ( resource: string, params: Partial = {}, - options?: UseInfiniteGetListOptions + options: UseInfiniteGetListOptions = {} ): UseInfiniteGetListHookValue => { const { pagination = { page: 1, perPage: 25 }, @@ -76,6 +76,7 @@ export const useInfiniteGetList = ( } = params; const dataProvider = useDataProvider(); const queryClient = useQueryClient(); + const { onSuccess, onError, ...queryOptions } = options; const result = useInfiniteQuery< GetInfiniteListResult, @@ -107,7 +108,7 @@ export const useInfiniteGetList = ( pageInfo, })), initialPageParam: pagination.page, - ...options, + ...queryOptions, getNextPageParam: lastLoadedPage => { if (lastLoadedPage.pageInfo) { return lastLoadedPage.pageInfo.hasNextPage @@ -146,8 +147,18 @@ export const useInfiniteGetList = ( ); }); }); + + if (onSuccess) { + onSuccess(result.data); + } + } + }, [meta, onSuccess, queryClient, resource, result.data]); + + useEffect(() => { + if (result.error && onError) { + onError(result.error); } - }, [meta, queryClient, resource, result.data]); + }, [onError, result.error]); return (result.data ? { @@ -178,7 +189,7 @@ export type UseInfiniteGetListOptions = Omit< | 'getPreviousPageParam' | 'initialPageParam' > & { - onSuccess?: (data: GetInfiniteListResult) => void; + onSuccess?: (data: InfiniteData>) => void; onError?: (error: Error) => void; }; From 984ed318902c1b0a7c7b22ca826449f52dc40b6a Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 10:18:57 +0100 Subject: [PATCH 018/103] Fix useGetMany --- .../src/dataProvider/useGetMany.spec.tsx | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useGetMany.spec.tsx b/packages/ra-core/src/dataProvider/useGetMany.spec.tsx index 56eb7f08acf..9002b291620 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.spec.tsx +++ b/packages/ra-core/src/dataProvider/useGetMany.spec.tsx @@ -12,7 +12,13 @@ const UseGetMany = ({ ids, meta = undefined, options = {}, - callback = null, + callback = undefined, +}: { + resource: string; + ids: any[]; + meta?: any; + options?: any; + callback?: Function; }) => { const hookValue = useGetMany(resource, { ids, meta }, options); if (callback) callback(hookValue); @@ -21,7 +27,17 @@ const UseGetMany = ({ let updateState; -const UseCustomGetMany = ({ resource, ids, options = {}, callback = null }) => { +const UseCustomGetMany = ({ + resource, + ids, + options = {}, + callback = undefined, +}: { + resource: string; + ids: any[]; + options?: any; + callback?: Function; +}) => { const [stateIds, setStateIds] = useState(ids); const hookValue = useGetMany(resource, { ids: stateIds }, options); if (callback) callback(hookValue); @@ -190,7 +206,7 @@ describe('useGetMany', () => { expect(hookValue).toHaveBeenCalledWith( expect.objectContaining({ data: [{ id: 1, title: 'foo' }], - isFetching: false, + isFetching: true, isLoading: false, error: null, }) @@ -216,11 +232,13 @@ describe('useGetMany', () => { await waitFor(() => { expect(dataProvider.getMany).toHaveBeenCalledTimes(1); }); - expect(hookValue).toHaveBeenCalledWith( - expect.objectContaining({ - error: new Error('failed'), - }) - ); + await waitFor(() => { + expect(hookValue).toHaveBeenCalledWith( + expect.objectContaining({ + error: new Error('failed'), + }) + ); + }); }); it('should execute success side effects on success', async () => { @@ -300,14 +318,16 @@ describe('useGetMany', () => { }) ); - expect(hookValue.mock.calls[1][0]).toStrictEqual( - expect.objectContaining({ - data: [{ id: 1, title: 'foo' }], - isError: false, - isFetching: false, - isLoading: false, - }) - ); + await waitFor(() => { + expect(hookValue.mock.calls[1][0]).toStrictEqual( + expect.objectContaining({ + data: [{ id: 1, title: 'foo' }], + isError: false, + isFetching: false, + isLoading: false, + }) + ); + }); // Updating ids... updateState([1, 2]); @@ -316,6 +336,10 @@ describe('useGetMany', () => { expect(dataProvider.getMany).toBeCalledTimes(2); }); + await waitFor(() => { + expect(hookValue).toBeCalledTimes(4); + }); + expect(hookValue.mock.calls[2][0]).toStrictEqual( expect.objectContaining({ data: undefined, @@ -325,14 +349,6 @@ describe('useGetMany', () => { }) ); expect(hookValue.mock.calls[3][0]).toStrictEqual( - expect.objectContaining({ - data: undefined, - isError: false, - isFetching: true, - isLoading: true, - }) - ); - expect(hookValue.mock.calls[4][0]).toStrictEqual( expect.objectContaining({ data: [ { id: 1, title: 'foo' }, From a2207c47324c4d1ba5833622d0cb7d11424225d6 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:14:06 +0100 Subject: [PATCH 019/103] Fix CoreAdminRoutes --- .../ra-core/src/core/CoreAdminRoutes.spec.tsx | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/ra-core/src/core/CoreAdminRoutes.spec.tsx b/packages/ra-core/src/core/CoreAdminRoutes.spec.tsx index ca2737d6f18..bcbd63f4007 100644 --- a/packages/ra-core/src/core/CoreAdminRoutes.spec.tsx +++ b/packages/ra-core/src/core/CoreAdminRoutes.spec.tsx @@ -21,7 +21,7 @@ describe('', () => { }; describe('With resources as regular children', () => { - it('should render resources and custom routes with and without layout', () => { + it('should render resources and custom routes with and without layout', async () => { const history = createMemoryHistory(); render( ', () => { ); - expect(screen.getByText('Layout')).not.toBeNull(); + await screen.findByText('Layout'); history.push('/posts'); - expect(screen.getByText('PostList')).not.toBeNull(); + await screen.findByText('PostList'); history.push('/comments'); - expect(screen.getByText('CommentList')).not.toBeNull(); + await screen.findByText('CommentList'); history.push('/foo'); + await screen.findByText('Foo'); expect(screen.queryByText('Layout')).toBeNull(); - expect(screen.getByText('Foo')).not.toBeNull(); history.push('/bar'); - expect(screen.getByText('Layout')).not.toBeNull(); - expect(screen.getByText('Bar')).not.toBeNull(); + await screen.findByText('Layout'); + await screen.findByText('Bar'); }); }); @@ -102,17 +102,16 @@ describe('', () => { ); history.push('/foo'); + await screen.findByText('Foo'); expect(screen.queryByText('Layout')).toBeNull(); - expect(screen.getByText('Foo')).not.toBeNull(); history.push('/bar'); - await waitFor(() => { - expect(screen.queryByText('Layout')).not.toBeNull(); - }); - expect(screen.getByText('Bar')).not.toBeNull(); + await screen.findByText('Bar'); + await screen.findByText('Layout'); + await screen.findByText('Bar'); history.push('/posts'); - expect(screen.queryByText('PostList')).not.toBeNull(); + await screen.findByText('PostList'); history.push('/comments'); - expect(screen.queryByText('CommentList')).not.toBeNull(); + await screen.findByText('CommentList'); }); it('should render resources and custom routes with and without layout even without an authProvider', async () => { @@ -152,17 +151,15 @@ describe('', () => { ); history.push('/foo'); + await screen.findByText('Foo'); expect(screen.queryByText('Layout')).toBeNull(); - expect(screen.getByText('Foo')).not.toBeNull(); history.push('/bar'); - await waitFor(() => { - expect(screen.queryByText('Layout')).not.toBeNull(); - }); - expect(screen.getByText('Bar')).not.toBeNull(); + await screen.findByText('Bar'); + expect(screen.queryByText('Layout')).not.toBeNull(); history.push('/posts'); - expect(screen.queryByText('PostList')).not.toBeNull(); + await screen.findByText('PostList'); history.push('/comments'); - expect(screen.queryByText('CommentList')).not.toBeNull(); + await screen.findByText('CommentList'); }); it('should return loading while the function child is not resolved', async () => { @@ -198,10 +195,10 @@ describe('', () => { // Timeout needed because we wait for a second before displaying the loading screen jest.advanceTimersByTime(1010); history.push('/posts'); - expect(screen.queryByText('Loading')).not.toBeNull(); + await screen.findByText('Loading'); history.push('/foo'); + await screen.findByText('Custom'); expect(screen.queryByText('Loading')).toBeNull(); - expect(screen.queryByText('Custom')).not.toBeNull(); jest.useRealTimers(); }); }); From ab9bbe0c1a55cd5d9bb628848b822d7eb694bab1 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:30:26 +0100 Subject: [PATCH 020/103] Fix useCreatePath --- packages/ra-core/src/routing/useCreatePath.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ra-core/src/routing/useCreatePath.spec.tsx b/packages/ra-core/src/routing/useCreatePath.spec.tsx index dafd834a0d1..5059f5b0e19 100644 --- a/packages/ra-core/src/routing/useCreatePath.spec.tsx +++ b/packages/ra-core/src/routing/useCreatePath.spec.tsx @@ -55,7 +55,7 @@ describe('useCreatePath', () => { screen.getByText('Post list').click(); await screen.findByText('Posts'); screen.getByText('Home').click(); - screen.getByText('Post detail').click(); + (await screen.findByText('Post detail')).click(); await screen.findByText('Post 123'); }); @@ -67,7 +67,7 @@ describe('useCreatePath', () => { screen.getByText('Post list').click(); await screen.findByText('Posts'); screen.getByText('Home').click(); - screen.getByText('Post detail').click(); + (await screen.findByText('Post detail')).click(); await screen.findByText('Post 123'); }); }); From ae4388f0d4cf140bbd798ddb5d10bece9b6df20e Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:39:20 +0100 Subject: [PATCH 021/103] Fix Resource --- packages/ra-core/src/core/Resource.spec.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/ra-core/src/core/Resource.spec.tsx b/packages/ra-core/src/core/Resource.spec.tsx index 7b632509bf9..a913208c0a0 100644 --- a/packages/ra-core/src/core/Resource.spec.tsx +++ b/packages/ra-core/src/core/Resource.spec.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import expect from 'expect'; import { render, screen } from '@testing-library/react'; import { createMemoryHistory } from 'history'; @@ -27,7 +26,7 @@ const resource = { }; describe('', () => { - it('renders resource routes by default', () => { + it('renders resource routes by default', async () => { const history = createMemoryHistory(); render( @@ -37,14 +36,14 @@ describe('', () => { // Resource does not declare a route matching its name, it only renders its child routes // so we don't need to navigate to a path matching its name history.push('/'); - expect(screen.getByText('PostList')).not.toBeNull(); + await screen.findByText('PostList'); history.push('/123'); - expect(screen.getByText('PostEdit')).not.toBeNull(); + await screen.findByText('PostEdit'); history.push('/123/show'); - expect(screen.getByText('PostShow')).not.toBeNull(); + await screen.findByText('PostShow'); history.push('/create'); - expect(screen.getByText('PostCreate')).not.toBeNull(); + await screen.findByText('PostCreate'); history.push('/customroute'); - expect(screen.getByText('PostCustomRoute')).not.toBeNull(); + await screen.findByText('PostCustomRoute'); }); }); From 2bb0ad9113951ca435c5103fdbf167f8b5b988d2 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:39:35 +0100 Subject: [PATCH 022/103] Fix useHandleAuthCallback --- .../src/auth/useHandleAuthCallback.spec.tsx | 75 +++++++++++-------- .../ra-core/src/auth/useHandleAuthCallback.ts | 3 +- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx b/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx index 92d9351b457..f73e1e1b03e 100644 --- a/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx +++ b/packages/ra-core/src/auth/useHandleAuthCallback.spec.tsx @@ -1,27 +1,18 @@ import * as React from 'react'; import expect from 'expect'; import { render, screen, waitFor } from '@testing-library/react'; -import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom'; +import { + unstable_HistoryRouter as HistoryRouter, + Route, + Routes, +} from 'react-router-dom'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { createMemoryHistory } from 'history'; import { useHandleAuthCallback } from './useHandleAuthCallback'; import AuthContext from './AuthContext'; -import { useRedirect } from '../routing/useRedirect'; -import useLogout from './useLogout'; import { AuthProvider } from '../types'; -jest.mock('../routing/useRedirect'); -jest.mock('./useLogout'); - -const redirect = jest.fn(); -// @ts-ignore -useRedirect.mockImplementation(() => redirect); - -const logout = jest.fn(); -// @ts-ignore -useLogout.mockImplementation(() => logout); - const TestComponent = ({ customError }: { customError?: boolean }) => { const [error, setError] = React.useState(); useHandleAuthCallback( @@ -52,31 +43,38 @@ const authProvider: AuthProvider = { handleCallback: () => Promise.resolve({}), }; -const queryClient = new QueryClient(); - describe('useHandleAuthCallback', () => { afterEach(() => { jest.clearAllMocks(); }); it('should redirect to the home route by default when the callback was successfully handled', async () => { - const history = createMemoryHistory({ initialEntries: ['/'] }); + const history = createMemoryHistory({ + initialEntries: ['/auth-callback'], + }); render( - - + + + Home} /> + Test} /> + } + /> + ); - await waitFor(() => { - expect(redirect).toHaveBeenCalledWith('/'); - }); + await screen.findByText('Home'); }); it('should redirect to the provided route when the callback was successfully handled', async () => { - const history = createMemoryHistory({ initialEntries: ['/'] }); + const history = createMemoryHistory({ + initialEntries: ['/auth-callback'], + }); render( { }, }} > - - + + + Home} /> + Test} /> + } + /> + ); - await waitFor(() => { - expect(redirect).toHaveBeenCalledWith('/test'); - }); + await screen.findByText('Test'); }); it('should use custom useQuery options such as onError', async () => { - const history = createMemoryHistory({ initialEntries: ['/'] }); + const history = createMemoryHistory({ + initialEntries: ['/auth-callback'], + }); render( { Promise.reject(new Error('Custom Error')), }} > - - + + + Home} /> + } + /> + @@ -118,6 +129,6 @@ describe('useHandleAuthCallback', () => { await waitFor(() => { screen.getByText('Custom Error'); }); - expect(redirect).not.toHaveBeenCalledWith('/test'); + expect(screen.queryByText('Home')).toBeNull(); }); }); diff --git a/packages/ra-core/src/auth/useHandleAuthCallback.ts b/packages/ra-core/src/auth/useHandleAuthCallback.ts index ce2c18164e9..01bcd7877a7 100644 --- a/packages/ra-core/src/auth/useHandleAuthCallback.ts +++ b/packages/ra-core/src/auth/useHandleAuthCallback.ts @@ -1,9 +1,9 @@ +import { useEffect } from 'react'; import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import { useLocation } from 'react-router'; import { useRedirect } from '../routing'; import { AuthProvider, AuthRedirectResult } from '../types'; import useAuthProvider from './useAuthProvider'; -import { useEffect } from 'react'; /** * This hook calls the `authProvider.handleCallback()` method on mount. This is meant to be used in a route called @@ -51,7 +51,6 @@ export const useHandleAuthCallback = ( const redirectTo = (queryResult.data as AuthRedirectResult)?.redirectTo ?? previousLocation; - if (redirectTo === false) { return; } From feeea9c0e0154f96959ec85305eb3bbcd5c4908b Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:39:39 +0100 Subject: [PATCH 023/103] Fix useAuthenticated --- packages/ra-core/src/auth/useAuthenticated.spec.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ra-core/src/auth/useAuthenticated.spec.tsx b/packages/ra-core/src/auth/useAuthenticated.spec.tsx index 837154444e5..d077ea74a1e 100644 --- a/packages/ra-core/src/auth/useAuthenticated.spec.tsx +++ b/packages/ra-core/src/auth/useAuthenticated.spec.tsx @@ -96,7 +96,7 @@ describe('useAuthenticated', () => { const authProvider = { login: jest.fn().mockResolvedValue(''), logout: jest.fn().mockResolvedValue(''), - checkAuth: jest.fn().mockRejectedValue(undefined), + checkAuth: jest.fn().mockRejectedValue({}), checkError: jest.fn().mockResolvedValue(''), getPermissions: jest.fn().mockResolvedValue(''), }; @@ -146,9 +146,14 @@ describe('useAuthenticated', () => { expect(authProvider.checkAuth).toHaveBeenCalledTimes(1); }); expect(authProvider.checkAuth.mock.calls[0][0]).toEqual({}); - await waitFor(() => { - expect(authProvider.logout).toHaveBeenCalledTimes(1); - }); + await waitFor( + () => { + expect(authProvider.logout).toHaveBeenCalledTimes(1); + }, + { + timeout: 4000, + } + ); expect(authProvider.logout.mock.calls[0][0]).toEqual({}); expect(reset).toHaveBeenCalledTimes(1); expect(notificationsSpy).toEqual([ From 2e890cde942996f497d251e7053f059c77d6821e Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:41:24 +0100 Subject: [PATCH 024/103] Fix Authenticated --- packages/ra-core/src/auth/Authenticated.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ra-core/src/auth/Authenticated.spec.tsx b/packages/ra-core/src/auth/Authenticated.spec.tsx index bdf6535d1ba..77412cbdde0 100644 --- a/packages/ra-core/src/auth/Authenticated.spec.tsx +++ b/packages/ra-core/src/auth/Authenticated.spec.tsx @@ -38,7 +38,7 @@ describe('', () => { const authProvider = { login: jest.fn().mockResolvedValue(''), logout: jest.fn().mockResolvedValue(''), - checkAuth: jest.fn().mockRejectedValue(undefined), + checkAuth: jest.fn().mockRejectedValue({}), checkError: jest.fn().mockResolvedValue(''), getPermissions: jest.fn().mockResolvedValue(''), }; From f65459e786a0f4d7ba10d5cb4a3d8620c2ef6750 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:39:37 +0100 Subject: [PATCH 025/103] Fix useUpdate --- .../useUpdate.optimistic.stories.tsx | 12 +++++----- .../useUpdate.pessimistic.stories.tsx | 12 +++++----- .../src/dataProvider/useUpdate.spec.tsx | 22 +++++++++++++------ .../useUpdate.undoable.stories.tsx | 12 +++++----- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx b/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx index 9ad9b919159..ccc4f646a74 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.optimistic.stories.tsx @@ -22,7 +22,9 @@ export const SuccessCase = () => { return new Promise(resolve => { setTimeout(() => { const post = posts.find(p => p.id === params.id); - post.title = params.data.title; + if (post) { + post.title = params.data.title; + } resolve({ data: post }); }, 1000); }); @@ -42,7 +44,7 @@ const SuccessCore = () => { const isMutating = useIsMutating(); const [success, setSuccess] = useState(); const { data, refetch } = useGetOne('posts', { id: 1 }); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const handleClick = () => { update( 'posts', @@ -65,7 +67,7 @@ const SuccessCore = () => {
{data?.author}
-   @@ -110,7 +112,7 @@ const ErrorCore = () => { const [success, setSuccess] = useState(); const [error, setError] = useState(); const { data, refetch } = useGetOne('posts', { id: 1 }); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const handleClick = () => { update( 'posts', @@ -137,7 +139,7 @@ const ErrorCore = () => {
{data?.author}
-   diff --git a/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx b/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx index 2cd6e6b979d..0b08f6cd27f 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.pessimistic.stories.tsx @@ -22,7 +22,9 @@ export const SuccessCase = () => { return new Promise(resolve => { setTimeout(() => { const post = posts.find(p => p.id === params.id); - post.title = params.data.title; + if (post) { + post.title = params.data.title; + } resolve({ data: post }); }, 1000); }); @@ -42,7 +44,7 @@ const SuccessCore = () => { const isMutating = useIsMutating(); const [success, setSuccess] = useState(); const { data, refetch } = useGetOne('posts', { id: 1 }); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const handleClick = () => { update( 'posts', @@ -65,7 +67,7 @@ const SuccessCore = () => {
{data?.author}
-   @@ -110,7 +112,7 @@ const ErrorCore = () => { const [success, setSuccess] = useState(); const [error, setError] = useState(); const { data, refetch } = useGetOne('posts', { id: 1 }); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const handleClick = () => { setError(undefined); update( @@ -135,7 +137,7 @@ const ErrorCore = () => {
{data?.author}
-   diff --git a/packages/ra-core/src/dataProvider/useUpdate.spec.tsx b/packages/ra-core/src/dataProvider/useUpdate.spec.tsx index 2d10c1c0bb2..029b818c900 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.spec.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.spec.tsx @@ -255,6 +255,7 @@ describe('useUpdate', () => { expect(screen.queryByText('Hello World')).toBeNull(); expect(screen.queryByText('mutating')).toBeNull(); }); + await screen.findByText('Hello'); }); it('when undoable, displays result and success side effects right away and fetched on confirm', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); @@ -271,15 +272,19 @@ describe('useUpdate', () => { expect(screen.queryByText('Hello World')).not.toBeNull(); expect(screen.queryByText('mutating')).not.toBeNull(); }); - await waitFor(() => { - expect(screen.queryByText('success')).not.toBeNull(); - expect(screen.queryByText('Hello World')).not.toBeNull(); - expect(screen.queryByText('mutating')).toBeNull(); - }); + await waitFor( + () => { + expect(screen.queryByText('mutating')).toBeNull(); + }, + { timeout: 4000 } + ); + expect(screen.queryByText('success')).not.toBeNull(); + expect(screen.queryByText('Hello World')).not.toBeNull(); }); it('when undoable, displays result and success side effects right away and reverts on cancel', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); render(); + await screen.findByText('Hello'); screen.getByText('Update title').click(); await waitFor(() => { expect(screen.queryByText('success')).not.toBeNull(); @@ -289,13 +294,15 @@ describe('useUpdate', () => { screen.getByText('Cancel').click(); await waitFor(() => { expect(screen.queryByText('Hello World')).toBeNull(); - expect(screen.queryByText('mutating')).toBeNull(); }); + expect(screen.queryByText('mutating')).toBeNull(); + await screen.findByText('Hello'); }); - it('when undoable, displays result and success side effects right away and reverts on error', async () => { + it('when undoable, displays result and success side effects right away and reverts on error', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); jest.spyOn(console, 'error').mockImplementation(() => {}); render(); + await screen.findByText('Hello'); screen.getByText('Update title').click(); await waitFor(() => { expect(screen.queryByText('success')).not.toBeNull(); @@ -308,6 +315,7 @@ describe('useUpdate', () => { expect(screen.queryByText('Hello World')).not.toBeNull(); expect(screen.queryByText('mutating')).not.toBeNull(); }); + await screen.findByText('Hello', undefined, { timeout: 4000 }); await waitFor(() => { expect(screen.queryByText('success')).toBeNull(); expect(screen.queryByText('Hello World')).toBeNull(); diff --git a/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx b/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx index a207c8d1e89..d225ef22cb6 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.undoable.stories.tsx @@ -23,7 +23,9 @@ export const SuccessCase = () => { return new Promise(resolve => { setTimeout(() => { const post = posts.find(p => p.id === params.id); - post.title = params.data.title; + if (post) { + post.title = params.data.title; + } resolve({ data: post }); }, 1000); }); @@ -44,7 +46,7 @@ const SuccessCore = () => { const [notification, setNotification] = useState(false); const [success, setSuccess] = useState(); const { data, refetch } = useGetOne('posts', { id: 1 }); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const handleClick = () => { update( 'posts', @@ -93,7 +95,7 @@ const SuccessCore = () => { ) : ( - )} @@ -140,7 +142,7 @@ const ErrorCore = () => { const [success, setSuccess] = useState(); const [error, setError] = useState(); const { data, refetch } = useGetOne('posts', { id: 1 }); - const [update, { isLoading }] = useUpdate(); + const [update, { isPending }] = useUpdate(); const handleClick = () => { update( 'posts', @@ -193,7 +195,7 @@ const ErrorCore = () => { ) : ( - )} From 958811fce64b5ee516edff80f6636a52a6bf7152 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:41:14 +0100 Subject: [PATCH 026/103] Fix useDelete --- .../useDelete.optimistic.stories.tsx | 8 ++--- .../useDelete.pessimistic.stories.tsx | 8 ++--- .../src/dataProvider/useDelete.spec.tsx | 30 +++++++++++-------- .../useDelete.undoable.stories.tsx | 8 ++--- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx index 0a58064aea6..d64564224b8 100644 --- a/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx @@ -46,7 +46,7 @@ const SuccessCore = () => { const isMutating = useIsMutating(); const [success, setSuccess] = useState(); const { data, refetch } = useGetList('posts'); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleClick = () => { deleteOne( 'posts', @@ -68,7 +68,7 @@ const SuccessCore = () => { ))}
-   @@ -117,7 +117,7 @@ const ErrorCore = () => { const [success, setSuccess] = useState(); const [error, setError] = useState(); const { data, refetch } = useGetList('posts'); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleClick = () => { setError(undefined); deleteOne( @@ -144,7 +144,7 @@ const ErrorCore = () => { ))}
-   diff --git a/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx index 86aee44452e..9dad1c1e306 100644 --- a/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx @@ -46,7 +46,7 @@ const SuccessCore = () => { const isMutating = useIsMutating(); const [success, setSuccess] = useState(); const { data, refetch } = useGetList('posts'); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleClick = () => { deleteOne( 'posts', @@ -68,7 +68,7 @@ const SuccessCore = () => { ))}
-   @@ -117,7 +117,7 @@ const ErrorCore = () => { const [success, setSuccess] = useState(); const [error, setError] = useState(); const { data, refetch } = useGetList('posts'); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleClick = () => { setError(undefined); deleteOne( @@ -141,7 +141,7 @@ const ErrorCore = () => { ))}
-   diff --git a/packages/ra-core/src/dataProvider/useDelete.spec.tsx b/packages/ra-core/src/dataProvider/useDelete.spec.tsx index 617ab93be1b..7f16dd16630 100644 --- a/packages/ra-core/src/dataProvider/useDelete.spec.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.spec.tsx @@ -339,12 +339,15 @@ describe('useDelete', () => { expect(screen.queryByText('World')).not.toBeNull(); expect(screen.queryByText('mutating')).not.toBeNull(); }); - await waitFor(() => { - expect(screen.queryByText('success')).not.toBeNull(); - expect(screen.queryByText('Hello')).toBeNull(); - expect(screen.queryByText('World')).not.toBeNull(); - expect(screen.queryByText('mutating')).toBeNull(); - }); + await waitFor( + () => { + expect(screen.queryByText('success')).not.toBeNull(); + expect(screen.queryByText('Hello')).toBeNull(); + expect(screen.queryByText('World')).not.toBeNull(); + expect(screen.queryByText('mutating')).toBeNull(); + }, + { timeout: 4000 } + ); }); it('when undoable, displays result and success side effects right away and reverts on cancel', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); @@ -383,12 +386,15 @@ describe('useDelete', () => { expect(screen.queryByText('World')).not.toBeNull(); expect(screen.queryByText('mutating')).not.toBeNull(); }); - await waitFor(() => { - expect(screen.queryByText('success')).toBeNull(); - expect(screen.queryByText('Hello')).not.toBeNull(); - expect(screen.queryByText('World')).not.toBeNull(); - expect(screen.queryByText('mutating')).toBeNull(); - }); + await waitFor( + () => { + expect(screen.queryByText('success')).toBeNull(); + expect(screen.queryByText('Hello')).not.toBeNull(); + expect(screen.queryByText('World')).not.toBeNull(); + expect(screen.queryByText('mutating')).toBeNull(); + }, + { timeout: 4000 } + ); }); }); diff --git a/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx index bd06243725d..56672e21f8a 100644 --- a/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx +++ b/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx @@ -48,7 +48,7 @@ const SuccessCore = () => { const [notification, setNotification] = useState(false); const [success, setSuccess] = useState(); const { data, refetch } = useGetList('posts'); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleClick = () => { deleteOne( 'posts', @@ -96,7 +96,7 @@ const SuccessCore = () => { ) : ( - )} @@ -147,7 +147,7 @@ const ErrorCore = () => { const [success, setSuccess] = useState(); const [error, setError] = useState(); const { data, refetch } = useGetList('posts'); - const [deleteOne, { isLoading }] = useDelete(); + const [deleteOne, { isPending }] = useDelete(); const handleClick = () => { setError(undefined); deleteOne( @@ -200,7 +200,7 @@ const ErrorCore = () => { ) : ( - )} From 5966771b73c568b0658c6c1ab7343aaa56cc0626 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:53:14 +0100 Subject: [PATCH 027/103] Fix useListController --- .../list/useListController.spec.tsx | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/ra-core/src/controller/list/useListController.spec.tsx b/packages/ra-core/src/controller/list/useListController.spec.tsx index 5e1cdee0e91..1e768aefece 100644 --- a/packages/ra-core/src/controller/list/useListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useListController.spec.tsx @@ -7,7 +7,6 @@ import { screen, act, } from '@testing-library/react'; -import lolex from 'lolex'; // TODO: we shouldn't import mui components in ra-core import { TextField } from '@mui/material'; import { createMemoryHistory } from 'history'; @@ -87,9 +86,9 @@ describe('useListController', () => { ); @@ -110,7 +109,6 @@ describe('useListController', () => { }); describe('setFilters', () => { - let clock; let childFunction = ({ setFilters, filterValues }) => ( // TODO: we shouldn't import mui components in ra-core { /> ); - beforeEach(() => { - clock = lolex.install(); - }); - - it('should take only last change in case of a burst of changes (case of inputs being currently edited)', () => { + it('should take only last change in case of a burst of changes (case of inputs being currently edited)', async () => { const props = { ...defaultProps, children: childFunction, @@ -150,7 +144,7 @@ describe('useListController', () => { fireEvent.change(searchInput, { target: { value: 'hel' } }); fireEvent.change(searchInput, { target: { value: 'hell' } }); fireEvent.change(searchInput, { target: { value: 'hello' } }); - clock.tick(210); + await new Promise(resolve => setTimeout(resolve, 210)); expect(storeSpy).toHaveBeenCalledTimes(1); expect(storeSpy).toHaveBeenCalledWith('posts.listParams', { @@ -162,7 +156,7 @@ describe('useListController', () => { }); }); - it('should remove empty filters', () => { + it('should remove empty filters', async () => { const props = { ...defaultProps, children: childFunction, @@ -192,7 +186,7 @@ describe('useListController', () => { // FIXME: For some reason, triggering the change event with an empty string // does not call the event handler on childFunction fireEvent.change(searchInput, { target: { value: '' } }); - clock.tick(210); + await new Promise(resolve => setTimeout(resolve, 210)); expect(storeSpy).toHaveBeenCalledTimes(2); @@ -259,10 +253,6 @@ describe('useListController', () => { ); expect(children).toHaveBeenCalledTimes(2); }); - - afterEach(() => { - clock.uninstall(); - }); }); describe('showFilter', () => { From 74551685d3fe2097db242c180320bde0760fa854 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:53:26 +0100 Subject: [PATCH 028/103] Fix useInfiniteListController --- .../list/useInfiniteListController.spec.tsx | 19 ++++--------------- .../list/useInfiniteListController.ts | 19 +++++-------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx index d07fdb49406..a3680dac4f5 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx @@ -7,7 +7,6 @@ import { screen, act, } from '@testing-library/react'; -import lolex from 'lolex'; // TODO: we shouldn't import mui components in ra-core import { TextField } from '@mui/material'; import { createMemoryHistory } from 'history'; @@ -126,7 +125,6 @@ describe('useInfiniteListController', () => { }); describe('setFilters', () => { - let clock; let childFunction = ({ setFilters, filterValues }) => ( // TODO: we shouldn't import mui components in ra-core { /> ); - beforeEach(() => { - // @ts-ignore - clock = lolex.install(); - }); - - it('should take only last change in case of a burst of changes (case of inputs being currently edited)', () => { + it('should take only last change in case of a burst of changes (case of inputs being currently edited)', async () => { const props = { ...defaultProps, children: childFunction, @@ -167,7 +160,7 @@ describe('useInfiniteListController', () => { fireEvent.change(searchInput, { target: { value: 'hel' } }); fireEvent.change(searchInput, { target: { value: 'hell' } }); fireEvent.change(searchInput, { target: { value: 'hello' } }); - clock.tick(210); + await new Promise(resolve => setTimeout(resolve, 210)); expect(storeSpy).toHaveBeenCalledTimes(1); expect(storeSpy).toHaveBeenCalledWith('posts.listParams', { @@ -179,7 +172,7 @@ describe('useInfiniteListController', () => { }); }); - it('should remove empty filters', () => { + it('should remove empty filters', async () => { const props = { ...defaultProps, children: childFunction, @@ -209,7 +202,7 @@ describe('useInfiniteListController', () => { // FIXME: For some reason, triggering the change event with an empty string // does not call the event handler on childFunction fireEvent.change(searchInput, { target: { value: '' } }); - clock.tick(210); + await new Promise(resolve => setTimeout(resolve, 210)); expect(storeSpy).toHaveBeenCalledTimes(2); @@ -276,10 +269,6 @@ describe('useInfiniteListController', () => { ); expect(children).toHaveBeenCalledTimes(2); }); - - afterEach(() => { - clock.uninstall(); - }); }); describe('showFilter', () => { diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.ts b/packages/ra-core/src/controller/list/useInfiniteListController.ts index ade2de08d57..bede07d7556 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.ts +++ b/packages/ra-core/src/controller/list/useInfiniteListController.ts @@ -1,15 +1,16 @@ import { isValidElement, useEffect, useMemo } from 'react'; import { - UseInfiniteQueryOptions, InfiniteQueryObserverBaseResult, InfiniteData, - QueryKey, } from '@tanstack/react-query'; import { useAuthenticated } from '../../auth'; import { useTranslate } from '../../i18n'; import { useNotify } from '../../notification'; -import { useInfiniteGetList } from '../../dataProvider'; +import { + UseInfiniteGetListOptions, + useInfiniteGetList, +} from '../../dataProvider'; import { defaultExporter } from '../../export'; import { RaRecord, @@ -214,17 +215,7 @@ export interface InfiniteListControllerProps< filterDefaultValues?: object; perPage?: number; // FIXME: Make it generic, but Parameters>[2] doesn't work - queryOptions?: Omit< - UseInfiniteQueryOptions< - GetInfiniteListResult, - Error, - InfiniteData>, - GetInfiniteListResult, - QueryKey, - number - >, - 'queryFn' | 'queryKey' - > & { meta?: any }; + queryOptions?: UseInfiniteGetListOptions; resource?: string; sort?: SortPayload; storeKey?: string | false; From e33f02b0e95474bb4387d6cd6f7377bf4450335d Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:24:55 +0100 Subject: [PATCH 029/103] Fix ReferenceManyField --- .../ra-ui-materialui/src/field/ReferenceManyField.spec.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx index 9fc0314298e..76a48bafa16 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx @@ -51,7 +51,7 @@ describe('', () => { await waitFor(() => { expect(screen.queryAllByRole('progressbar')).toHaveLength(0); }); - const links = screen.queryAllByRole('link'); + const links = await screen.findAllByRole('link'); expect(links).toHaveLength(2); expect(links[0].textContent).toEqual('hello'); expect(links[1].textContent).toEqual('world'); @@ -143,7 +143,7 @@ describe('', () => { await waitFor(() => { expect(screen.queryAllByRole('progressbar')).toHaveLength(0); }); - const links = screen.queryAllByRole('link'); + const links = await screen.findAllByRole('link'); expect(links).toHaveLength(2); expect(links[0].textContent).toEqual('hello'); expect(links[1].textContent).toEqual('world'); @@ -174,7 +174,7 @@ describe('', () => { await waitFor(() => { expect(screen.queryAllByRole('progressbar')).toHaveLength(0); }); - const links = screen.queryAllByRole('link'); + const links = await screen.findAllByRole('link'); expect(links).toHaveLength(2); expect(links[0].textContent).toEqual('hello'); expect(links[1].textContent).toEqual('world'); From 37c45786aa3cd5d0e2866cb63b393824436aec6f Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:51:09 +0100 Subject: [PATCH 030/103] Fix some ra-ui-materialui tests --- .../src/button/PrevNextButtons.spec.tsx | 4 +- .../src/field/NumberField.spec.tsx | 2 +- .../src/input/AutocompleteArrayInput.spec.tsx | 78 ++++++++++++------- .../src/input/AutocompleteInput.spec.tsx | 6 +- .../src/list/ListGuesser.spec.tsx | 6 +- .../datagrid/SelectColumnsButton.spec.tsx | 8 +- .../src/list/filter/FilterListItem.spec.tsx | 20 +++-- 7 files changed, 78 insertions(+), 46 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/PrevNextButtons.spec.tsx b/packages/ra-ui-materialui/src/button/PrevNextButtons.spec.tsx index 64ce6799d6b..639cea7b43c 100644 --- a/packages/ra-ui-materialui/src/button/PrevNextButtons.spec.tsx +++ b/packages/ra-ui-materialui/src/button/PrevNextButtons.spec.tsx @@ -83,7 +83,9 @@ describe('', () => { it('should render a loading UI in case of slow data provider response', async () => { render(); - const progress = await screen.findByRole('progressbar'); + const progress = await screen.findByRole('progressbar', undefined, { + timeout: 5000, + }); expect(progress).toBeDefined(); }); diff --git a/packages/ra-ui-materialui/src/field/NumberField.spec.tsx b/packages/ra-ui-materialui/src/field/NumberField.spec.tsx index 31f60ac7930..cae785283aa 100644 --- a/packages/ra-ui-materialui/src/field/NumberField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/NumberField.spec.tsx @@ -59,7 +59,7 @@ describe('', () => { options={{ minimumFractionDigits: 2 }} /> ); - expect(queryByText('2.10')).not.toBeNull(); + expect(queryByText(/2[.,]10/)).not.toBeNull(); }); it('should use record from RecordContext', () => { diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx index 521c5d54af4..beb7a997ec5 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx @@ -23,7 +23,7 @@ describe('', () => { resource: 'posts', }; - it('should extract suggestions from choices', () => { + it('should extract suggestions from choices', async () => { render( @@ -42,12 +42,15 @@ describe('', () => { screen.getByLabelText('resources.posts.fields.tags'), 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); expect(screen.getByText('Technical')).not.toBeNull(); expect(screen.getByText('Programming')).not.toBeNull(); }); - it('should use optionText with a string value as text identifier', () => { + it('should use optionText with a string value as text identifier', async () => { render( @@ -68,12 +71,14 @@ describe('', () => { 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); expect(screen.getByText('Technical')).not.toBeNull(); expect(screen.getByText('Programming')).not.toBeNull(); }); - it('should use optionText with a string value including "." as text identifier', () => { + it('should use optionText with a string value including "." as text identifier', async () => { render( @@ -94,12 +99,14 @@ describe('', () => { 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); expect(screen.getByText('Technical')).not.toBeNull(); expect(screen.getByText('Programming')).not.toBeNull(); }); - it('should use optionText with a function value as text identifier', () => { + it('should use optionText with a function value as text identifier', async () => { render( @@ -120,12 +127,14 @@ describe('', () => { 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); expect(screen.getByText('Technical')).not.toBeNull(); expect(screen.getByText('Programming')).not.toBeNull(); }); - it('should translate the choices by default', () => { + it('should translate the choices by default', async () => { render( `**${x}**`}> @@ -147,12 +156,14 @@ describe('', () => { 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); expect(screen.getByText('**Technical**')).not.toBeNull(); expect(screen.getByText('**Programming**')).not.toBeNull(); }); - it('should not translate the choices if translateChoice is false', () => { + it('should not translate the choices if translateChoice is false', async () => { render( `**${x}**`}> @@ -175,7 +186,9 @@ describe('', () => { 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); expect(screen.getByText('Technical')).not.toBeNull(); expect(screen.getByText('Programming')).not.toBeNull(); }); @@ -202,7 +215,9 @@ describe('', () => { userEvent.type(input, 'fooo'); userEvent.type(input, 'foooo'); await new Promise(resolve => setTimeout(resolve, 300)); - expect(setFilter).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(setFilter).toHaveBeenCalledTimes(1); + }); }); it('should respect shouldRenderSuggestions over default if passed in', async () => { @@ -274,10 +289,12 @@ describe('', () => { expect(screen.queryAllByRole('option')).toHaveLength(0); fireEvent.blur(input); userEvent.type(input, 'a'); - expect(screen.queryAllByRole('option')).toHaveLength(1); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(1); + }); }); - it('should not rerender searchText while having focus and new choices arrive', () => { + it('should not rerender searchText while having focus and new choices arrive', async () => { const { rerender } = render( @@ -294,7 +311,7 @@ describe('', () => { fireEvent.focus(input); userEvent.type(input, 'foo'); - expect(input.value).toEqual('foo'); + await screen.findByDisplayValue('foo'); expect(screen.queryAllByRole('option')).toHaveLength(0); rerender( @@ -385,8 +402,9 @@ describe('', () => { ); const input = screen.getByLabelText('resources.posts.fields.tags'); userEvent.type(input, 'p'); - await new Promise(resolve => setTimeout(resolve, 300)); - expect(setFilter).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(setFilter).toHaveBeenCalledTimes(1); + }); expect(setFilter).toHaveBeenCalledWith('p'); rerender( @@ -460,7 +478,7 @@ describe('', () => { expect(setFilter).toHaveBeenCalledWith(''); }); - it('should allow customized rendering of suggesting item', () => { + it('should allow customized rendering of suggesting item', async () => { const SuggestionItem = props => { const record = useRecordContext(); return
; @@ -486,8 +504,8 @@ describe('', () => { screen.getByLabelText('resources.posts.fields.tags'), 'a' ); - expect(screen.getByLabelText('Technical')).not.toBeNull(); - expect(screen.getByLabelText('Programming')).not.toBeNull(); + await screen.findByLabelText('Technical'); + await screen.findByLabelText('Programming'); }); it('should display helperText', () => { @@ -598,7 +616,7 @@ describe('', () => { expect(onChange).not.toHaveBeenCalled(); }); - it('should limit suggestions when suggestionLimit is passed', () => { + it('should limit suggestions when suggestionLimit is passed', async () => { render( @@ -615,7 +633,9 @@ describe('', () => { ); const input = screen.getByLabelText('resources.posts.fields.tags'); userEvent.type(input, 'a'); - expect(screen.queryAllByRole('option')).toHaveLength(1); + await waitFor(() => + expect(screen.queryAllByRole('option')).toHaveLength(1) + ); }); it('should support creation of a new choice through the onCreate event', async () => { @@ -814,7 +834,7 @@ describe('', () => { expect(screen.getByText('Programming')).not.toBeNull(); }); - it('should use optionText with a string value including "." as text identifier when a create element is passed', () => { + it('should use optionText with a string value including "." as text identifier when a create element is passed', async () => { const choices = [ { id: 't', foobar: { name: 'Technical' } }, { id: 'p', foobar: { name: 'Programming' } }, @@ -851,7 +871,9 @@ describe('', () => { screen.getByLabelText('resources.posts.fields.tags'), 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(3); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(3); + }); expect(screen.getByText('Technical')).not.toBeNull(); expect(screen.getByText('Programming')).not.toBeNull(); expect(screen.getByText('ra.action.create_item')).not.toBeNull(); @@ -911,7 +933,7 @@ describe('', () => { ).not.toBeNull(); }); - it('should show the suggestions when the input value is empty and the input is focused and choices arrived late', () => { + it('should show the suggestions when the input value is empty and the input is focused and choices arrived late', async () => { const { rerender } = render( @@ -937,7 +959,9 @@ describe('', () => { screen.getByLabelText('resources.posts.fields.tags'), 'a' ); - expect(screen.queryAllByRole('option')).toHaveLength(2); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(2); + }); }); it('should display "No options" and not throw any error inside a ReferenceArrayInput field when referenced list is empty', async () => { diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx index c23f8e7855b..907acea2f6b 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx @@ -95,7 +95,7 @@ describe('', () => { fireEvent.focus(input); userEvent.type(input, '{end}'); userEvent.type(input, '2'); - expect(input.value).toEqual('foo2'); + await screen.findByDisplayValue('foo2'); userEvent.type(input, '{backspace}'); await waitFor(() => { expect(input.value).toEqual('foo'); @@ -1440,7 +1440,7 @@ describe('', () => { }); }); - it("should allow to edit the input if it's inside a FormDataConsumer", () => { + it("should allow to edit the input if it's inside a FormDataConsumer", async () => { render( ', () => { }) as HTMLInputElement; fireEvent.focus(input); userEvent.type(input, 'Hello World!'); - expect(input.value).toEqual('Hello World!'); + await screen.findByDisplayValue('Hello World!'); }); it('should display "No options" and not throw any error inside a ReferenceArrayInput field when referenced list is empty', async () => { diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx index b90f27b749b..7f54993b472 100644 --- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx +++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import expect from 'expect'; import { render, screen, waitFor } from '@testing-library/react'; -import { CoreAdminContext } from 'ra-core'; +import { CoreAdminContext, testDataProvider } from 'ra-core'; import { ListGuesser } from './ListGuesser'; import { ThemeProvider } from '../theme/ThemeProvider'; @@ -9,7 +9,7 @@ import { ThemeProvider } from '../theme/ThemeProvider'; describe('', () => { it('should log the guessed List view based on the fetched records', async () => { const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - const dataProvider = { + const dataProvider = testDataProvider({ getList: () => Promise.resolve({ data: [ @@ -25,7 +25,7 @@ describe('', () => { ], total: 1, }), - }; + }); render( diff --git a/packages/ra-ui-materialui/src/list/datagrid/SelectColumnsButton.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/SelectColumnsButton.spec.tsx index e20bd9793cc..5b123e29019 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/SelectColumnsButton.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/SelectColumnsButton.spec.tsx @@ -9,9 +9,9 @@ describe('', () => { render(); screen.getByText('Columns').click(); expect(screen.queryByText('1869')).not.toBeNull(); - screen.getByLabelText('Year').click(); + (await screen.findByLabelText(/Year/, { exact: false })).click(); expect(screen.queryByText('1869')).toBeNull(); - screen.getByLabelText('Year').click(); + (await screen.findByLabelText(/Year/, { exact: false })).click(); expect(screen.queryByText('1869')).not.toBeNull(); }); @@ -19,9 +19,9 @@ describe('', () => { render(); screen.getByText('Columns').click(); expect(screen.queryByText('1869')).not.toBeNull(); - screen.getByLabelText('Year').click(); + (await screen.findByLabelText(/Year/, { exact: false })).click(); expect(screen.queryByText('1869')).toBeNull(); - screen.getByLabelText('Year').click(); + (await screen.findByLabelText(/Year/, { exact: false })).click(); expect(screen.queryByText('1869')).not.toBeNull(); }); }); diff --git a/packages/ra-ui-materialui/src/list/filter/FilterListItem.spec.tsx b/packages/ra-ui-materialui/src/list/filter/FilterListItem.spec.tsx index 6b2660454a7..89661e7fc9e 100644 --- a/packages/ra-ui-materialui/src/list/filter/FilterListItem.spec.tsx +++ b/packages/ra-ui-materialui/src/list/filter/FilterListItem.spec.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import expect from 'expect'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { ListContextProvider, ListControllerResult } from 'ra-core'; import GoogleIcon from '@mui/icons-material/Google'; @@ -166,7 +166,7 @@ describe('', () => { ); }); - it('should allow to customize isSelected and toggleFilter', () => { + it('should allow to customize isSelected and toggleFilter', async () => { const { container } = render(); expect(getSelectedItemsLabels(container)).toEqual([ @@ -177,18 +177,24 @@ describe('', () => { screen.getByText('News').click(); - expect(getSelectedItemsLabels(container)).toEqual(['Tutorials']); - screen.getByText(JSON.stringify({ category: ['tutorials'] })); + await waitFor(() => + expect(getSelectedItemsLabels(container)).toEqual(['Tutorials']) + ); + await screen.findByText(JSON.stringify({ category: ['tutorials'] })); screen.getByText('Tutorials').click(); - expect(getSelectedItemsLabels(container)).toEqual([]); + await waitFor(() => + expect(getSelectedItemsLabels(container)).toEqual([]) + ); expect(screen.getAllByText(JSON.stringify({})).length).toBe(2); screen.getByText('Tests').click(); - expect(getSelectedItemsLabels(container)).toEqual(['Tests']); - screen.getByText(JSON.stringify({ category: ['tests'] })); + await waitFor(() => + expect(getSelectedItemsLabels(container)).toEqual(['Tests']) + ); + await screen.findByText(JSON.stringify({ category: ['tests'] })); }); }); From 76f3b39fca9ff8ef777ba2caaf8fdd9f76723d25 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 12:12:44 +0100 Subject: [PATCH 031/103] Upgrade MUI --- examples/crm/package.json | 4 +- examples/demo/package.json | 4 +- examples/no-code/package.json | 2 +- examples/simple/package.json | 4 +- packages/ra-input-rich-text/package.json | 8 +- packages/ra-no-code/package.json | 4 +- packages/ra-ui-materialui/package.json | 8 +- packages/react-admin/package.json | 4 +- yarn.lock | 356 ++++++++++++++--------- 9 files changed, 231 insertions(+), 163 deletions(-) diff --git a/examples/crm/package.json b/examples/crm/package.json index ba576bb767f..dc6923f1ae2 100644 --- a/examples/crm/package.json +++ b/examples/crm/package.json @@ -4,8 +4,8 @@ "private": true, "dependencies": { "@hello-pangea/dnd": "^16.3.0", - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "@nivo/bar": "^0.80.0", "@nivo/core": "^0.80.0", "clsx": "^1.1.1", diff --git a/examples/demo/package.json b/examples/demo/package.json index 9061bfa0cd8..b55921c1a25 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -4,8 +4,8 @@ "private": true, "dependencies": { "@apollo/client": "^3.3.19", - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "@types/inflection": "^1.5.28", "@types/recharts": "^1.8.10", "@vitejs/plugin-react": "^2.2.0", diff --git a/examples/no-code/package.json b/examples/no-code/package.json index a611032a72b..b7c536592a8 100644 --- a/examples/no-code/package.json +++ b/examples/no-code/package.json @@ -8,7 +8,7 @@ "serve": "vite preview" }, "dependencies": { - "@mui/material": "^5.0.2", + "@mui/material": "^5.14.18", "ra-data-local-storage": "^4.12.0", "ra-no-code": "^4.12.0", "react": "^18.0.0", diff --git a/examples/simple/package.json b/examples/simple/package.json index d0f3277a513..bfe84121f86 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -11,8 +11,8 @@ "dependencies": { "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "@tanstack/react-query": "^5.8.4", "@tanstack/react-query-devtools": "^5.8.4", "jsonexport": "^3.2.0", diff --git a/packages/ra-input-rich-text/package.json b/packages/ra-input-rich-text/package.json index 0a20d909de1..c5a0c7df06d 100644 --- a/packages/ra-input-rich-text/package.json +++ b/packages/ra-input-rich-text/package.json @@ -38,16 +38,16 @@ "clsx": "^1.1.1" }, "peerDependencies": { - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "ra-core": "^4.0.0", "ra-ui-materialui": "^4.0.0", "react": "^16.9.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0" }, "devDependencies": { - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "@testing-library/react": "^14.1.2", "@tiptap/extension-mention": "^2.0.3", "@tiptap/suggestion": "^2.0.3", diff --git a/packages/ra-no-code/package.json b/packages/ra-no-code/package.json index f6b7ec39ee9..1ca57b76166 100644 --- a/packages/ra-no-code/package.json +++ b/packages/ra-no-code/package.json @@ -40,8 +40,8 @@ "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "@tanstack/react-query": "^5.8.4", "clsx": "^1.1.1", "date-fns": "^2.19.0", diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index d40809989de..81d2fac6cd3 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -26,8 +26,8 @@ "watch": "tsc --outDir dist/esm --module es2015 --watch" }, "devDependencies": { - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "@testing-library/react": "^14.1.2", "@types/dompurify": "^3.0.2", "@types/react": "^18.2.37", @@ -52,8 +52,8 @@ "typescript": "^5.1.3" }, "peerDependencies": { - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "ra-core": "^4.0.0", "react": "^16.9.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0", diff --git a/packages/react-admin/package.json b/packages/react-admin/package.json index 3f4b240f5c3..bf7ebbdb56c 100644 --- a/packages/react-admin/package.json +++ b/packages/react-admin/package.json @@ -38,8 +38,8 @@ "dependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "@mui/icons-material": "^5.0.1", - "@mui/material": "^5.0.2", + "@mui/icons-material": "^5.14.18", + "@mui/material": "^5.14.18", "history": "^5.1.0", "ra-core": "^4.15.5", "ra-i18n-polyglot": "^4.15.5", diff --git a/yarn.lock b/yarn.lock index 20aaf24cf7e..dc4ea87c3c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1667,6 +1667,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.2": + version: 7.23.4 + resolution: "@babel/runtime@npm:7.23.4" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: db2bf183cd0119599b903ca51ca0aeea8e0ab478a16be1aae10dd90473ed614159d3e5adfdd8f8f3d840402428ce0d90b5c01aae95da9e45a2dd83e02d85ca27 + languageName: node + linkType: hard + "@babel/template@npm:^7.22.15, @babel/template@npm:^7.22.5, @babel/template@npm:^7.3.3": version: 7.22.15 resolution: "@babel/template@npm:7.22.15" @@ -1786,6 +1795,19 @@ __metadata: languageName: node linkType: hard +"@emotion/cache@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/cache@npm:11.11.0" + dependencies: + "@emotion/memoize": ^0.8.1 + "@emotion/sheet": ^1.2.2 + "@emotion/utils": ^1.2.1 + "@emotion/weak-memoize": ^0.3.1 + stylis: 4.2.0 + checksum: a23ab5ab2fd08e904698106d58ad3536fed51cc1aa0ef228e95bb640eaf11f560dbd91a395477b0d84e1e3c20150263764b4558517cf6576a89d2d6cc5253688 + languageName: node + linkType: hard + "@emotion/cache@npm:^11.7.1": version: 11.7.1 resolution: "@emotion/cache@npm:11.7.1" @@ -1822,6 +1844,13 @@ __metadata: languageName: node linkType: hard +"@emotion/memoize@npm:^0.8.1": + version: 0.8.1 + resolution: "@emotion/memoize@npm:0.8.1" + checksum: dffed372fc3b9fa2ba411e76af22b6bb686fb0cb07694fdfaa6dd2baeb0d5e4968c1a7caa472bfcf06a5997d5e7c7d16b90e993f9a6ffae79a2c3dbdc76dfe78 + languageName: node + linkType: hard + "@emotion/react@npm:^11.1.5, @emotion/react@npm:^11.4.1, @emotion/react@npm:^11.7.1": version: 11.7.1 resolution: "@emotion/react@npm:11.7.1" @@ -1865,6 +1894,13 @@ __metadata: languageName: node linkType: hard +"@emotion/sheet@npm:^1.2.2": + version: 1.2.2 + resolution: "@emotion/sheet@npm:1.2.2" + checksum: 69827a1bfa43d7b188f1d8cea42163143a36312543fdade5257c459a2b3efd7ce386aac84ba152bc2517a4f7e54384c04800b26adb382bb284ac7e4ad40e584b + languageName: node + linkType: hard + "@emotion/styled@npm:^11.3.0, @emotion/styled@npm:^11.6.0": version: 11.6.0 resolution: "@emotion/styled@npm:11.6.0" @@ -1910,6 +1946,13 @@ __metadata: languageName: node linkType: hard +"@emotion/utils@npm:^1.2.1": + version: 1.2.1 + resolution: "@emotion/utils@npm:1.2.1" + checksum: db43ca803361740c14dfb1cca1464d10d27f4c8b40d3e8864e6932ccf375d1450778ff4e4eadee03fb97f2aeb18de9fae98294905596a12ff7d4cd1910414d8d + languageName: node + linkType: hard + "@emotion/weak-memoize@npm:^0.2.5": version: 0.2.5 resolution: "@emotion/weak-memoize@npm:0.2.5" @@ -1917,6 +1960,13 @@ __metadata: languageName: node linkType: hard +"@emotion/weak-memoize@npm:^0.3.1": + version: 0.3.1 + resolution: "@emotion/weak-memoize@npm:0.3.1" + checksum: ed514b3cb94bbacece4ac2450d98898066c0a0698bdeda256e312405ca53634cb83c75889b25cd8bbbe185c80f4c05a1f0a0091e1875460ba6be61d0334f0b8a + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm64@npm:0.17.19" @@ -2298,6 +2348,18 @@ __metadata: languageName: node linkType: hard +"@floating-ui/react-dom@npm:^2.0.4": + version: 2.0.4 + resolution: "@floating-ui/react-dom@npm:2.0.4" + dependencies: + "@floating-ui/dom": ^1.5.1 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 5d597a7939e484428452cee775884f6c14055783d811a1abedf03151eb8825ecf42a544553efecdc502f30ca2a6b3e6630485367c39473d259e74f5f1331bc0a + languageName: node + linkType: hard + "@floating-ui/utils@npm:^0.1.3": version: 0.1.6 resolution: "@floating-ui/utils@npm:0.1.6" @@ -2857,66 +2919,73 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-alpha.64": - version: 5.0.0-alpha.64 - resolution: "@mui/base@npm:5.0.0-alpha.64" +"@mui/base@npm:5.0.0-beta.24": + version: 5.0.0-beta.24 + resolution: "@mui/base@npm:5.0.0-beta.24" dependencies: - "@babel/runtime": ^7.16.3 - "@emotion/is-prop-valid": ^1.1.1 - "@mui/utils": ^5.2.3 - "@popperjs/core": ^2.4.4 - clsx: ^1.1.1 - prop-types: ^15.7.2 - react-is: ^17.0.2 + "@babel/runtime": ^7.23.2 + "@floating-ui/react-dom": ^2.0.4 + "@mui/types": ^7.2.9 + "@mui/utils": ^5.14.18 + "@popperjs/core": ^2.11.8 + clsx: ^2.0.0 + prop-types: ^15.8.1 peerDependencies: - "@types/react": ^16.8.6 || ^17.0.0 - react: ^17.0.2 - react-dom: ^17.0.2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 5e91036773ecdef831cc1c196843208e19349aba0937d349ef7b53707db5581384609f929b600828610bf7f05964a6dab3d240ab85975ab16f9b6617faadcd60 + checksum: cae18d4d9043ef73746f441dfada021ce2db7c2e6f11662a9e010e8f899c9eb8e3f3c2e6cef77f08745ecbc633972721925cfeb93909fa4aa9b153f53f059b38 languageName: node linkType: hard -"@mui/icons-material@npm:^5.0.1": - version: 5.2.5 - resolution: "@mui/icons-material@npm:5.2.5" +"@mui/core-downloads-tracker@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/core-downloads-tracker@npm:5.14.18" + checksum: 28ef7a9d45a1612398b2c27750b8759b2f5dfd82e923c4a3002748c9cbd8e1095d3dd28bece426a2ec5543cbc2e02ea1c39f68e1f28d6b9a93d537aa9cbcc5cc + languageName: node + linkType: hard + +"@mui/icons-material@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/icons-material@npm:5.14.18" dependencies: - "@babel/runtime": ^7.16.3 + "@babel/runtime": ^7.23.2 peerDependencies: "@mui/material": ^5.0.0 - "@types/react": ^16.8.6 || ^17.0.0 - react: ^17.0.2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 0e7296527d1c4970c1857c25b4fb517303094e7a4fa731531c4d052c1fc56a5785d90b3be682e0dd84364f8ba79ed6db9b88de78de992af1ca13360726ccbb44 + checksum: 7d45d5990dccc0a0d356be8b994a384b59104576005d01d84fed02b32dd7a7b1ea536c5fb8583f93674eb8ad7bb13742a6968db3c01cbace10a5c21ca42c50b2 languageName: node linkType: hard -"@mui/material@npm:^5.0.2": - version: 5.2.8 - resolution: "@mui/material@npm:5.2.8" +"@mui/material@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/material@npm:5.14.18" dependencies: - "@babel/runtime": ^7.16.3 - "@mui/base": 5.0.0-alpha.64 - "@mui/system": ^5.2.8 - "@mui/types": ^7.1.0 - "@mui/utils": ^5.2.3 - "@types/react-transition-group": ^4.4.4 - clsx: ^1.1.1 - csstype: ^3.0.10 - hoist-non-react-statics: ^3.3.2 - prop-types: ^15.7.2 - react-is: ^17.0.2 - react-transition-group: ^4.4.2 + "@babel/runtime": ^7.23.2 + "@mui/base": 5.0.0-beta.24 + "@mui/core-downloads-tracker": ^5.14.18 + "@mui/system": ^5.14.18 + "@mui/types": ^7.2.9 + "@mui/utils": ^5.14.18 + "@types/react-transition-group": ^4.4.8 + clsx: ^2.0.0 + csstype: ^3.1.2 + prop-types: ^15.8.1 + react-is: ^18.2.0 + react-transition-group: ^4.4.5 peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@types/react": ^16.8.6 || ^17.0.0 - react: ^17.0.2 - react-dom: ^17.0.2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@emotion/react": optional: true @@ -2924,64 +2993,65 @@ __metadata: optional: true "@types/react": optional: true - checksum: eceb56f752e114aaa53c41c8c1f5fed88f3df8a1fbdd62de8d19131faf1dc5b64b04c5631a1a77468ae928abe11450b866b13647d29dc2d9ceccc4f225c16832 + checksum: 9c6f49097c5f44c92b0d13c10b7e9abc001b231b6cc3dbd9585d328741be5d75d06d7b41e3d1464be9fb7674ff9ed5cee1f0d129dbc8cc93b734e639ec625751 languageName: node linkType: hard -"@mui/private-theming@npm:^5.2.3": - version: 5.2.3 - resolution: "@mui/private-theming@npm:5.2.3" +"@mui/private-theming@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/private-theming@npm:5.14.18" dependencies: - "@babel/runtime": ^7.16.3 - "@mui/utils": ^5.2.3 - prop-types: ^15.7.2 + "@babel/runtime": ^7.23.2 + "@mui/utils": ^5.14.18 + prop-types: ^15.8.1 peerDependencies: - "@types/react": ^16.8.6 || ^17.0.0 - react: ^17.0.2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 477da8719479c9b1223adf04e195db9845be6201041e9eea98f670e2baea1ad1651edf778ed4f7ff0aa5bb5444970150d52afe21d3762ad6d39d327504bcd46e + checksum: 36bb2ecb1e853addbe60a2b54265a74daba154d678dee89e09b2f2e17a5bd1305a0148ed290216d916e12644f24a1c96e4750ab6f3bb6f08acde71d3d8ee0a34 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.2.6": - version: 5.2.6 - resolution: "@mui/styled-engine@npm:5.2.6" +"@mui/styled-engine@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/styled-engine@npm:5.14.18" dependencies: - "@babel/runtime": ^7.16.3 - "@emotion/cache": ^11.7.1 - prop-types: ^15.7.2 + "@babel/runtime": ^7.23.2 + "@emotion/cache": ^11.11.0 + csstype: ^3.1.2 + prop-types: ^15.8.1 peerDependencies: "@emotion/react": ^11.4.1 "@emotion/styled": ^11.3.0 - react: ^17.0.2 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@emotion/react": optional: true "@emotion/styled": optional: true - checksum: 8fad62e312643ac3cb7bcdabf1b0164f05da71585b85b1d7a38ed27f22c834d54a0e225319796beaf7244820915faecfbd2242944bcdb821a53502c146c8e279 + checksum: fbe0dfc265be09672b8df315c96385024286d6f3ae2adb61af2f7c018553c7eb933addfc48f8dfae16dfb5eba88a41d9a21928e2b4dd073ab6dec634f190fd03 languageName: node linkType: hard -"@mui/system@npm:^5.2.8": - version: 5.2.8 - resolution: "@mui/system@npm:5.2.8" +"@mui/system@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/system@npm:5.14.18" dependencies: - "@babel/runtime": ^7.16.3 - "@mui/private-theming": ^5.2.3 - "@mui/styled-engine": ^5.2.6 - "@mui/types": ^7.1.0 - "@mui/utils": ^5.2.3 - clsx: ^1.1.1 - csstype: ^3.0.10 - prop-types: ^15.7.2 + "@babel/runtime": ^7.23.2 + "@mui/private-theming": ^5.14.18 + "@mui/styled-engine": ^5.14.18 + "@mui/types": ^7.2.9 + "@mui/utils": ^5.14.18 + clsx: ^2.0.0 + csstype: ^3.1.2 + prop-types: ^15.8.1 peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@types/react": ^16.8.6 || ^17.0.0 - react: ^17.0.2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@emotion/react": optional: true @@ -2989,34 +3059,37 @@ __metadata: optional: true "@types/react": optional: true - checksum: 72fab31a8071fd3fabb5c90087b8cadf8655cd16d70a8578911173409a0e4ee10564f9fb51e351dbfbb503ec1d5f51958906250869abb458ac2ce6609ced426d + checksum: 47f8dfed1ed7f3395794d1772bc8ed4e3b0959d27682b03033fe39ddd365edae15cc53887ab37bd2f0b641308e9d67f4248d5e5c6a8de0a13741270594b5b0b9 languageName: node linkType: hard -"@mui/types@npm:^7.1.0": - version: 7.1.0 - resolution: "@mui/types@npm:7.1.0" +"@mui/types@npm:^7.2.9": + version: 7.2.9 + resolution: "@mui/types@npm:7.2.9" peerDependencies: - "@types/react": "*" + "@types/react": ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 0027e0092cc4674d971c1218dea6d6ec33c8f98ede0fb793e5bcec27c9d8897343adc2d8e45704ab54841c6792ff9c5df7601ad968da58123551d48c3faa307f + checksum: 8c3258762820a65c5bc66ab7a3f12d264ea20db63635a3343dad93cde5727a97aeb37c2d02fb16d67f7969a2029de7f82bb40ac72e8fceceae9b8914fb782077 languageName: node linkType: hard -"@mui/utils@npm:^5.2.3": - version: 5.2.3 - resolution: "@mui/utils@npm:5.2.3" +"@mui/utils@npm:^5.14.18": + version: 5.14.18 + resolution: "@mui/utils@npm:5.14.18" dependencies: - "@babel/runtime": ^7.16.3 - "@types/prop-types": ^15.7.4 - "@types/react-is": ^16.7.1 || ^17.0.0 - prop-types: ^15.7.2 - react-is: ^17.0.2 + "@babel/runtime": ^7.23.2 + "@types/prop-types": ^15.7.10 + prop-types: ^15.8.1 + react-is: ^18.2.0 peerDependencies: - react: ^17.0.2 - checksum: 0a8f52dbb8f4f2b6d743c5d419907d6bfcea8648a5b65fefc3ff96f0dc967448867357057c127cb746a762a226c2b1d79481bfc45896ad997a41d75a25a111d2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 659d8f275b0a376ad4df8fcd0a7722771d7814d10aa26b385c772404930bc798431a7ef11e68b0fdd89951ddcb6fe6279baf3a925cddc92782e799ec6f7c3095 languageName: node linkType: hard @@ -3631,7 +3704,14 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.4.4, @popperjs/core@npm:^2.9.0": +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: 4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.9.0": version: 2.11.2 resolution: "@popperjs/core@npm:2.11.2" checksum: b06f65fcef9dbfe7487fd53e38f12684267e91c8bc667a6c9621c207f42ee97d48059039ee8dae0327df509234b6f09bf5b940b34b71ee453d307f53d84e99b2 @@ -6336,14 +6416,14 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.4": +"@types/prop-types@npm:*": version: 15.7.4 resolution: "@types/prop-types@npm:15.7.4" checksum: 014bb826592fab01499931259969aafc21d5a8ff4ece3e3fb8e2b5186bed17656f7dcdccf9a98c27fee74d7d0697aa3f53ea971a72679597f0ca0c3d5ca585d3 languageName: node linkType: hard -"@types/prop-types@npm:^15.7.11": +"@types/prop-types@npm:^15.7.10, @types/prop-types@npm:^15.7.11": version: 15.7.11 resolution: "@types/prop-types@npm:15.7.11" checksum: e53423cf9d510515ef8b47ff42f4f1b65a7b7b37c8704e2dbfcb9a60defe0c0e1f3cb1acfdeb466bad44ca938d7c79bffdd51b48ffb659df2432169d0b27a132 @@ -6373,25 +6453,7 @@ __metadata: languageName: node linkType: hard -"@types/react-is@npm:^16.7.1 || ^17.0.0": - version: 17.0.3 - resolution: "@types/react-is@npm:17.0.3" - dependencies: - "@types/react": "*" - checksum: 839382b66b2b2e3023647f5ba0c382ddc6aa01c1bc9f64608f82bbc871a905ba9b988838619914d8348c2a511717c6bd3701cb866bb9e4abfabdbe544efb695b - languageName: node - linkType: hard - -"@types/react-transition-group@npm:^4.4.4": - version: 4.4.4 - resolution: "@types/react-transition-group@npm:4.4.4" - dependencies: - "@types/react": "*" - checksum: 0e593863c60550002bb7a5bf60dad719de78580017f87eadf32661d1870345a4ef22e2e37902e05d05955494f2c0cae5ef46510032005e8e969273eee0ae8519 - languageName: node - linkType: hard - -"@types/react-transition-group@npm:^4.4.9": +"@types/react-transition-group@npm:^4.4.8, @types/react-transition-group@npm:^4.4.9": version: 4.4.9 resolution: "@types/react-transition-group@npm:4.4.9" dependencies: @@ -8774,6 +8836,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.0.0": + version: 2.0.0 + resolution: "clsx@npm:2.0.0" + checksum: c09f43b3144a0b7826b6b11b6a111b2c7440831004eecc02d333533c5e58ef0aa5f2dce071d3b25fbb8c8ea97b45df96c74bcc1d51c8c2027eb981931107b0cd + languageName: node + linkType: hard + "cmd-shim@npm:6.0.1": version: 6.0.1 resolution: "cmd-shim@npm:6.0.1" @@ -9472,13 +9541,20 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.10, csstype@npm:^3.0.2": +"csstype@npm:^3.0.2": version: 3.0.10 resolution: "csstype@npm:3.0.10" checksum: f0fff671ab368a863946859ad96be0be66afeb83566215d6494be840ffedfaef4945b48d1b0ce1a19f9983af772e0ce38c7be91a1ad46fe7ecd641937c5a99f7 languageName: node linkType: hard +"csstype@npm:^3.1.2": + version: 3.1.2 + resolution: "csstype@npm:3.1.2" + checksum: 32c038af259897c807ac738d9eab16b3d86747c72b09d5c740978e06f067f9b7b1737e1b75e407c7ab1fe1543dc95f20e202b4786aeb1b8d3bdf5d5ce655e6c6 + languageName: node + linkType: hard + "cypress-plugin-tab@npm:^1.0.5": version: 1.0.5 resolution: "cypress-plugin-tab@npm:1.0.5" @@ -9962,8 +10038,8 @@ __metadata: resolution: "demo@workspace:examples/demo" dependencies: "@apollo/client": ^3.3.19 - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 "@types/fetch-mock": ^7.3.2 "@types/inflection": ^1.5.28 "@types/jest": ^29.5.2 @@ -16286,7 +16362,7 @@ __metadata: version: 0.0.0-use.local resolution: "no-code@workspace:examples/no-code" dependencies: - "@mui/material": ^5.0.2 + "@mui/material": ^5.14.18 "@vitejs/plugin-react": ^2.2.0 ra-data-local-storage: ^4.12.0 ra-no-code: ^4.12.0 @@ -18399,8 +18475,8 @@ __metadata: version: 0.0.0-use.local resolution: "ra-input-rich-text@workspace:packages/ra-input-rich-text" dependencies: - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 "@testing-library/react": ^14.1.2 "@tiptap/core": ^2.0.3 "@tiptap/extension-color": ^2.0.3 @@ -18428,8 +18504,8 @@ __metadata: tippy.js: ^6.3.7 typescript: ^5.1.3 peerDependencies: - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 ra-core: ^4.0.0 ra-ui-materialui: ^4.0.0 react: ^16.9.0 || ^17.0.0 || ^18.0.0 @@ -18461,8 +18537,8 @@ __metadata: version: 0.0.0-use.local resolution: "ra-no-code@workspace:packages/ra-no-code" dependencies: - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 "@tanstack/react-query": ^5.8.4 "@testing-library/react": ^14.1.2 "@testing-library/user-event": ^14.5.1 @@ -18492,8 +18568,8 @@ __metadata: version: 0.0.0-use.local resolution: "ra-ui-materialui@workspace:packages/ra-ui-materialui" dependencies: - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 "@tanstack/react-query": ^5.8.4 "@testing-library/react": ^14.1.2 "@types/dompurify": ^3.0.2 @@ -18531,8 +18607,8 @@ __metadata: rimraf: ^3.0.2 typescript: ^5.1.3 peerDependencies: - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 ra-core: ^4.0.0 react: ^16.9.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 @@ -18611,8 +18687,8 @@ __metadata: resolution: "react-admin-crm@workspace:examples/crm" dependencies: "@hello-pangea/dnd": ^16.3.0 - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 "@nivo/bar": ^0.80.0 "@nivo/core": ^0.80.0 "@testing-library/jest-dom": ^6.1.4 @@ -18700,8 +18776,8 @@ __metadata: dependencies: "@emotion/react": ^11.4.1 "@emotion/styled": ^11.3.0 - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 cross-env: ^5.2.0 expect: ^27.4.6 history: ^5.1.0 @@ -18904,7 +18980,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^17.0.1, react-is@npm:^17.0.2": +"react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 @@ -19144,21 +19220,6 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:^4.4.2": - version: 4.4.2 - resolution: "react-transition-group@npm:4.4.2" - dependencies: - "@babel/runtime": ^7.5.5 - dom-helpers: ^5.0.1 - loose-envify: ^1.4.0 - prop-types: ^15.6.2 - peerDependencies: - react: ">=16.6.0" - react-dom: ">=16.6.0" - checksum: afaf835854526065d246532714a3833a7c5fbcf21303e1479008ff6f1ec1ae44ecd151f74f357c60511a1e49de65cb9b81bf4d7858b9ee19e636b9a62a6daaa4 - languageName: node - linkType: hard - "react-transition-group@npm:^4.4.5": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" @@ -20237,8 +20298,8 @@ __metadata: "@emotion/react": ^11.7.1 "@emotion/styled": ^11.6.0 "@hookform/devtools": ^4.0.2 - "@mui/icons-material": ^5.0.1 - "@mui/material": ^5.0.2 + "@mui/icons-material": ^5.14.18 + "@mui/material": ^5.14.18 "@tanstack/react-query": ^5.8.4 "@tanstack/react-query-devtools": ^5.8.4 "@vitejs/plugin-react": ^2.2.0 @@ -20869,6 +20930,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" From 34359e8229ff41556f408c89d817597818a8884c Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 12:12:54 +0100 Subject: [PATCH 032/103] Fix remaining tests --- .../src/button/ToggleThemeButton.spec.tsx | 12 ++++--- .../button/UpdateWithConfirmButton.spec.tsx | 15 ++++++--- .../src/field/ReferenceArrayField.spec.tsx | 2 +- .../src/field/ReferenceField.spec.tsx | 2 +- .../src/input/ArrayInput/ArrayInput.spec.tsx | 16 ++++++---- .../src/input/AutocompleteArrayInput.spec.tsx | 24 ++++++++------ .../src/input/AutocompleteInput.spec.tsx | 32 +++++++++---------- .../src/input/CheckboxGroupInput.spec.tsx | 2 +- .../src/input/RadioButtonGroupInput.spec.tsx | 2 +- .../src/input/ReferenceInput.spec.tsx | 27 ++++++++++------ .../src/input/SelectArrayInput.spec.tsx | 28 ++++++++++------ .../src/input/SelectInput.spec.tsx | 2 +- .../src/layout/DeviceTestWrapper.tsx | 20 ++++++------ .../src/layout/ThemeTestWrapper.tsx | 2 ++ .../src/list/datagrid/DatagridRow.spec.tsx | 9 ++++-- .../src/list/filter/FilterButton.spec.tsx | 16 ++++++---- .../src/preferences/Configurable.spec.tsx | 14 ++++++-- .../src/preferences/Inspector.spec.tsx | 26 +++++++++------ 18 files changed, 153 insertions(+), 98 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/ToggleThemeButton.spec.tsx b/packages/ra-ui-materialui/src/button/ToggleThemeButton.spec.tsx index c34190128fc..ddd631572eb 100644 --- a/packages/ra-ui-materialui/src/button/ToggleThemeButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/ToggleThemeButton.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import expect from 'expect'; import { Basic } from './ToggleThemeButton.stories'; @@ -9,7 +9,7 @@ describe('ToggleThemeButton', () => { render(); screen.getByLabelText('Toggle light/dark mode'); }); - it('should allow to change the theme between light and dark', () => { + it('should allow to change the theme between light and dark', async () => { const { container } = render(); const root = container.querySelector( '.MuiScopedCssBaseline-root' @@ -19,8 +19,12 @@ describe('ToggleThemeButton', () => { } expect(getComputedStyle(root).colorScheme).toBe('light'); screen.getByLabelText('Toggle light/dark mode').click(); - expect(getComputedStyle(root).colorScheme).toBe('dark'); + await waitFor(() => { + expect(getComputedStyle(root).colorScheme).toBe('dark'); + }); screen.getByLabelText('Toggle light/dark mode').click(); - expect(getComputedStyle(root).colorScheme).toBe('light'); + await waitFor(() => { + expect(getComputedStyle(root).colorScheme).toBe('light'); + }); }); }); diff --git a/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.spec.tsx b/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.spec.tsx index b1a5230d52b..b7d52b81922 100644 --- a/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.spec.tsx @@ -69,15 +69,15 @@ describe('', () => { ); render( - - + + }> - - + + ); // waitFor for the dataProvider.getOne() return await waitFor(() => { @@ -247,7 +247,12 @@ describe('', () => { it('should close the dialog even with custom success side effect', async () => { render(); fireEvent.click(await screen.findByLabelText('Reset views')); - await screen.findByText('Are you sure you want to update this post?'); + await screen.findByRole('dialog'); + await screen.findByText( + 'Are you sure you want to update this post?', + undefined, + { timeout: 4000 } + ); fireEvent.click(screen.getByText('Confirm')); await screen.findByText('Reset views success', undefined, { timeout: 2000, diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx index 6b3121cdefa..a9c299dd0dc 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx @@ -47,7 +47,7 @@ describe('', () => { ); await new Promise(resolve => setTimeout(resolve, 1001)); - expect(queryAllByRole('progressbar')).toHaveLength(1); + await screen.findByRole('progressbar'); }); it('should render a list of the child component', () => { diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx index 5f0fa9d2bdf..499a6041baa 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx @@ -107,7 +107,7 @@ describe('', () => { ); await new Promise(resolve => setTimeout(resolve, 1001)); - expect(screen.queryByRole('progressbar')).not.toBeNull(); + await screen.findByRole('progressbar'); expect(screen.queryAllByRole('link')).toHaveLength(0); }); diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx index f1b4b7ee444..393b1a4e605 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx @@ -346,14 +346,16 @@ describe('', () => { }); it('should turn form tab in red if the array is required and empty', async () => { render(); - const input = screen.getByLabelText('Title'); - userEvent.type(input, 'a'); - const SaveButton = screen.getByText('Save'); - fireEvent.click(SaveButton); + userEvent.type(screen.getByLabelText('Title'), 'a'); + await screen.findByDisplayValue('a'); + fireEvent.click(screen.getByText('Save')); const formTab = await screen.findByText('Main'); - expect( - formTab.classList.contains('RaTabbedForm-errorTabButton') - ).toBe(true); + await screen.findByText('Required'); + await waitFor(() => { + expect( + formTab.classList.contains('RaTabbedForm-errorTabButton') + ).toBe(true); + }); expect(formTab.classList.contains('error')).toBe(true); }); }); diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx index beb7a997ec5..20c0ed399d9 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx @@ -292,6 +292,10 @@ describe('', () => { await waitFor(() => { expect(screen.queryAllByRole('option')).toHaveLength(1); }); + fireEvent.blur(input); + await waitFor(() => { + expect(screen.queryAllByRole('option')).toHaveLength(1); + }); }); it('should not rerender searchText while having focus and new choices arrive', async () => { @@ -311,7 +315,7 @@ describe('', () => { fireEvent.focus(input); userEvent.type(input, 'foo'); - await screen.findByDisplayValue('foo'); + await screen.findByDisplayValue('foo', undefined, { timeout: 4000 }); expect(screen.queryAllByRole('option')).toHaveLength(0); rerender( @@ -971,9 +975,9 @@ describe('', () => { await waitFor(() => { screen.getByText('Author'); }); - screen.getByRole('textbox').focus(); + screen.getByRole('combobox').focus(); fireEvent.click(screen.getByLabelText('Clear value')); - fireEvent.change(screen.getByRole('textbox'), { + fireEvent.change(screen.getByRole('combobox'), { target: { value: 'plop' }, }); await waitFor( @@ -991,9 +995,9 @@ describe('', () => { await waitFor(() => { screen.getByText('Author'); }); - screen.getByRole('textbox').focus(); + screen.getByRole('combobox').focus(); fireEvent.click(screen.getByLabelText('Clear value')); - fireEvent.change(screen.getByRole('textbox'), { + fireEvent.change(screen.getByRole('combobox'), { target: { value: 'Vic' }, }); @@ -1038,7 +1042,7 @@ describe('', () => { ); - expect(screen.queryByRole('textbox')).not.toBeNull(); + expect(screen.queryByRole('combobox')).not.toBeNull(); }); it('should not crash if its value is not an array and is empty', () => { @@ -1056,7 +1060,7 @@ describe('', () => { ); - expect(screen.queryByRole('textbox')).not.toBeNull(); + expect(screen.queryByRole('combobox')).not.toBeNull(); }); it('should include full records when calling onChange', async () => { @@ -1064,7 +1068,7 @@ describe('', () => { render(); await screen.findByText('Editor'); await screen.findByText('Reviewer'); - screen.getByRole('textbox').focus(); + screen.getByRole('combobox').focus(); fireEvent.click(await screen.findByText('Admin')); await waitFor(() => { expect(onChange).toHaveBeenCalledWith( @@ -1090,7 +1094,7 @@ describe('', () => { it('should include full records when calling onChange inside ReferenceArrayInput', async () => { const onChange = jest.fn(); render(); - (await screen.findByRole('textbox')).focus(); + (await screen.findByRole('combobox')).focus(); fireEvent.click(await screen.findByText('Leo Tolstoy')); await waitFor(() => { expect(onChange).toHaveBeenCalledWith( @@ -1105,7 +1109,7 @@ describe('', () => { ); }); expect(screen.getByDisplayValue('Russian')).not.toBeNull(); - screen.getAllByRole('textbox')[0].focus(); + screen.getAllByRole('combobox')[0].focus(); fireEvent.click(await screen.findByText('Victor Hugo')); await waitFor(() => { expect(onChange).toHaveBeenCalledWith( diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx index 907acea2f6b..2996d515075 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx @@ -123,7 +123,7 @@ describe('', () => { expect(screen.queryAllByRole('option').length).toEqual(1); - const input = screen.getByRole('textbox') as HTMLInputElement; + const input = screen.getByRole('combobox') as HTMLInputElement; expect(input.value).toEqual('Default'); }); @@ -1269,10 +1269,10 @@ describe('', () => { render(); await waitFor(() => { expect( - (screen.getByRole('textbox') as HTMLInputElement).value + (screen.getByRole('combobox') as HTMLInputElement).value ).toBe('Leo Tolstoy'); }); - screen.getByRole('textbox').focus(); + screen.getByRole('combobox').focus(); fireEvent.click(await screen.findByText('Victor Hugo')); await waitFor(() => { expect(onChange).toHaveBeenCalledWith(2, { @@ -1287,15 +1287,15 @@ describe('', () => { render(); await waitFor(() => { expect( - (screen.getByRole('textbox') as HTMLInputElement).value + (screen.getByRole('combobox') as HTMLInputElement).value ).toBe('Leo Tolstoy'); }); - screen.getByRole('textbox').focus(); + screen.getByRole('combobox').focus(); fireEvent.click(screen.getByLabelText('Clear value')); await waitFor(() => { expect(screen.getByRole('listbox').children).toHaveLength(5); }); - fireEvent.change(screen.getByRole('textbox'), { + fireEvent.change(screen.getByRole('combobox'), { target: { value: 'Vic' }, }); await waitFor( @@ -1313,7 +1313,7 @@ describe('', () => { render(); await waitFor(() => { expect( - (screen.getByRole('textbox') as HTMLInputElement).value + (screen.getByRole('combobox') as HTMLInputElement).value ).toBe('Leo Tolstoy'); }); fireEvent.click(screen.getByLabelText('Clear value')); @@ -1323,7 +1323,7 @@ describe('', () => { await new Promise(resolve => setTimeout(resolve, 2000)); await waitFor(() => { expect( - (screen.getByRole('textbox') as HTMLInputElement).value + (screen.getByRole('combobox') as HTMLInputElement).value ).toEqual(''); }); expect(screen.queryByText('Leo Tolstoy')).toBeNull(); @@ -1402,17 +1402,17 @@ describe('', () => { await waitFor( () => { expect( - (screen.getByRole('textbox') as HTMLInputElement).value + (screen.getByRole('combobox') as HTMLInputElement).value ).toBe('Leo Tolstoy - Russian'); }, { timeout: 2000 } ); - screen.getByRole('textbox').focus(); + screen.getByRole('combobox').focus(); fireEvent.click(screen.getByLabelText('Clear value')); await waitFor(() => { expect(screen.getByRole('listbox').children).toHaveLength(5); }); - fireEvent.change(screen.getByRole('textbox'), { + fireEvent.change(screen.getByRole('combobox'), { target: { value: 'French' }, }); await waitFor( @@ -1427,7 +1427,7 @@ describe('', () => { it('should include full record when calling onChange', async () => { const onChange = jest.fn(); render(); - (await screen.findAllByRole('textbox'))[0].focus(); + (await screen.findAllByRole('combobox'))[0].focus(); fireEvent.click(await screen.findByText('Victor Hugo')); await waitFor(() => { expect(onChange).toHaveBeenCalledWith(2, { @@ -1511,12 +1511,10 @@ describe('', () => { it('should allow a very large number of choices', async () => { render(); - await waitFor(() => { - expect(screen.getByRole('textbox')); - }); + await screen.findByRole('combobox'); - screen.getByRole('textbox').click(); - userEvent.type(screen.getByRole('textbox'), '1050'); + screen.getByRole('combobox').click(); + userEvent.type(screen.getByRole('combobox'), '1050'); await waitFor(() => { screen.getByText(/Dalmatian #1050/); }); diff --git a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.spec.tsx b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.spec.tsx index 5e413ed27e8..0bd70b9283c 100644 --- a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.spec.tsx @@ -370,7 +370,7 @@ describe('', () => { await new Promise(resolve => setTimeout(resolve, 1001)); - expect(screen.queryByRole('progressbar')).not.toBeNull(); + await screen.findByRole('progressbar'); }); it('should not render a LinearProgress if loading is false', () => { diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx index 15be249890c..97e47a11938 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.spec.tsx @@ -454,7 +454,7 @@ describe('', () => { await new Promise(resolve => setTimeout(resolve, 1001)); - expect(screen.queryByRole('progressbar')).not.toBeNull(); + await screen.findByRole('progressbar'); }); it('should not render a LinearProgress if isLoading is false', () => { diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx index c2faeacd097..bbd4575f535 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx @@ -181,23 +181,32 @@ describe('', () => { ), }; render(); - await screen.findByDisplayValue('Leo Tolstoy'); - const input = screen.getByLabelText('Author') as HTMLInputElement; + const input = (await screen.findByDisplayValue( + 'Leo Tolstoy' + )) as HTMLInputElement; input.focus(); screen.getByLabelText('Clear value').click(); + await screen.findByDisplayValue(''); screen.getByLabelText('Save').click(); await waitFor(() => { expect( (screen.getByLabelText('Save') as HTMLButtonElement).disabled ).toBeTruthy(); }); - expect(dataProvider.update).toHaveBeenCalledWith( - 'books', - expect.objectContaining({ - data: expect.objectContaining({ - author: null, - }), - }) + await waitFor( + () => { + expect(dataProvider.update).toHaveBeenCalledWith( + 'books', + expect.objectContaining({ + data: expect.objectContaining({ + author: null, + }), + }) + ); + }, + { + timeout: 4000, + } ); }); diff --git a/packages/ra-ui-materialui/src/input/SelectArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/SelectArrayInput.spec.tsx index 6b429e7fec5..806dc111174 100644 --- a/packages/ra-ui-materialui/src/input/SelectArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/SelectArrayInput.spec.tsx @@ -328,7 +328,7 @@ describe('', () => { await new Promise(resolve => setTimeout(resolve, 1001)); - expect(screen.queryByRole('progressbar')).not.toBeNull(); + await screen.findByRole('progressbar'); }); it('should not render a LinearProgress if loading is false', () => { @@ -370,8 +370,10 @@ describe('', () => { fireEvent.click(screen.getByText('ra.action.create')); await new Promise(resolve => setTimeout(resolve)); - // 2 because there is both the chip for the new selected item and the option (event if hidden) - expect(screen.queryAllByText(newChoice.name).length).toEqual(2); + await waitFor(() => { + // 2 because there is both the chip for the new selected item and the option (event if hidden) + expect(screen.queryAllByText(newChoice.name).length).toEqual(2); + }); }); it('should support creation of a new choice through the onCreate event with a promise', async () => { @@ -447,8 +449,10 @@ describe('', () => { fireEvent.click(screen.getByText('ra.action.create')); await new Promise(resolve => setTimeout(resolve)); input.blur(); - // 2 because there is both the chip for the new selected item and the option (event if hidden) - expect(screen.queryAllByText(newChoice.name.en).length).toEqual(2); + await waitFor(() => { + // 2 because there is both the chip for the new selected item and the option (event if hidden) + expect(screen.queryAllByText(newChoice.name.en).length).toEqual(2); + }); }); it('should support creation of a new choice with function optionText', async () => { @@ -479,8 +483,10 @@ describe('', () => { fireEvent.click(screen.getByText('ra.action.create')); await new Promise(resolve => setTimeout(resolve)); input.blur(); - // 2 because there is both the chip for the new selected item and the option (event if hidden) - expect(screen.queryAllByText(newChoice.name).length).toEqual(2); + await waitFor(() => { + // 2 because there is both the chip for the new selected item and the option (event if hidden) + expect(screen.queryAllByText(newChoice.name).length).toEqual(2); + }); }); it('should support creation of a new choice through the create element', async () => { @@ -518,11 +524,13 @@ describe('', () => { fireEvent.click(screen.getByText('Get the kid')); input.blur(); - // 2 because there is both the chip for the new selected item and the option (event if hidden) - expect(screen.queryAllByText(newChoice.name).length).toEqual(2); + await waitFor(() => { + // 2 because there is both the chip for the new selected item and the option (event if hidden) + expect(screen.queryAllByText(newChoice.name).length).toEqual(2); + }); }); - it('should recive an event object on change', async () => { + it('should receive an event object on change', async () => { const choices = [...defaultProps.choices]; const onChange = jest.fn(); diff --git a/packages/ra-ui-materialui/src/input/SelectInput.spec.tsx b/packages/ra-ui-materialui/src/input/SelectInput.spec.tsx index ce38c3c2368..0d240387f10 100644 --- a/packages/ra-ui-materialui/src/input/SelectInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/SelectInput.spec.tsx @@ -480,7 +480,7 @@ describe('', () => { await new Promise(resolve => setTimeout(resolve, 1001)); - expect(screen.queryByRole('progressbar')).not.toBeNull(); + await screen.findByRole('progressbar'); }); it('should not render a LinearProgress if isLoading is false', () => { diff --git a/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx b/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx index d06f1034b73..b93e79b1cc4 100644 --- a/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx +++ b/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx @@ -2,6 +2,16 @@ import * as React from 'react'; import mediaQuery from 'css-mediaquery'; import { createTheme, ThemeProvider } from '@mui/material/styles'; +function createMatchMedia(width) { + return query => ({ + matches: mediaQuery.match(query, { + width, + }), + addListener: () => {}, + removeListener: () => {}, + }); +} + /** * Test utility to simulate a device form factor for server-side mediaQueries * @@ -19,15 +29,7 @@ export const DeviceTestWrapper = ({ }: DeviceTestWrapperProps): JSX.Element => { const theme = createTheme(); // Use https://github.com/ericf/css-mediaquery as polyfill. - const ssrMatchMedia = query => ({ - matches: mediaQuery.match(query, { - // The estimated CSS width of the browser. - // For the sake of this demo, we are using a fixed value. - // In production, you can look into client-hint https://caniuse.com/#search=client%20hint - // or user-agent resolution. - width: theme.breakpoints.values[width], - }), - }); + const ssrMatchMedia = createMatchMedia(theme.breakpoints.values[width]); return ( {}, + removeListener: () => {}, }); return ( diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index 660b49e10d4..dca080196a8 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -3,6 +3,7 @@ import { render as baseRender, fireEvent, screen, + waitFor, } from '@testing-library/react'; import { useLocation } from 'react-router-dom'; import { ResourceDefinitionContextProvider, useRecordContext } from 'ra-core'; @@ -193,9 +194,11 @@ describe('', () => { ); fireEvent.click(screen.getByText('hello')); await new Promise(resolve => setTimeout(resolve)); // waitFor one tick - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ pathname: '/bar/foo' }) - ); + await waitFor(() => { + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ pathname: '/bar/foo' }) + ); + }); }); it('should not call push if onRowClick is false', () => { diff --git a/packages/ra-ui-materialui/src/list/filter/FilterButton.spec.tsx b/packages/ra-ui-materialui/src/list/filter/FilterButton.spec.tsx index 81ca93fb1b5..ba8f8ce9e1b 100644 --- a/packages/ra-ui-materialui/src/list/filter/FilterButton.spec.tsx +++ b/packages/ra-ui-materialui/src/list/filter/FilterButton.spec.tsx @@ -195,11 +195,10 @@ describe('', () => { fireEvent.click(screen.getByLabelText('Open')); fireEvent.click(screen.getByText('Sint...')); + await screen.findByLabelText('Add filter'); await waitFor(() => { - screen.getByLabelText('Add filter'); expect(screen.getAllByRole('checkbox')).toHaveLength(2); }); - fireEvent.click(screen.getByLabelText('Add filter')); fireEvent.click(screen.getByText('Remove all filters')); @@ -210,11 +209,14 @@ describe('', () => { fireEvent.click(screen.getByLabelText('Open')); fireEvent.click(screen.getByText('Sint...')); - await waitFor(() => { - expect( - screen.getAllByTestId('CheckBoxOutlineBlankIcon') - ).toHaveLength(2); - }); + await waitFor( + () => { + expect(screen.getAllByRole('checkbox')).toHaveLength(2); + }, + { + timeout: 4000, + } + ); expect(screen.queryByText('Save current query...')).toBeNull(); }, 20000); diff --git a/packages/ra-ui-materialui/src/preferences/Configurable.spec.tsx b/packages/ra-ui-materialui/src/preferences/Configurable.spec.tsx index fbf5c71d984..e73a53e755c 100644 --- a/packages/ra-ui-materialui/src/preferences/Configurable.spec.tsx +++ b/packages/ra-ui-materialui/src/preferences/Configurable.spec.tsx @@ -20,6 +20,7 @@ describe('Configurable', () => { await screen.findByText('Inspector'); fireEvent.mouseOver(screen.getByText('Lorem ipsum')); screen.getByTitle('ra.configurable.customize').click(); + await screen.findByText('Background color'); expect( (screen.getByLabelText('Background color') as HTMLInputElement) .value @@ -30,6 +31,7 @@ describe('Configurable', () => { render(); screen.getByText('Today'); screen.getByLabelText('Configure mode').click(); + await screen.findByText('Inspector'); fireEvent.mouseOver(screen.getByText('Sales')); screen.getByTitle('ra.configurable.customize').click(); await screen.findByText('Sales block'); @@ -41,8 +43,14 @@ describe('Configurable', () => { render(); screen.getByText('Today'); screen.getByLabelText('Configure mode').click(); + await screen.findByText('Inspector'); + fireEvent.mouseOver(screen.getByText('Sales')); - screen.getByTitle('ra.configurable.customize').click(); + ( + await screen.findByTitle('ra.configurable.customize', undefined, { + timeout: 4000, + }) + ).click(); await screen.findByText('Sales block'); screen.getByLabelText('Show date').click(); screen.getByLabelText('ra.action.close').click(); @@ -60,7 +68,9 @@ describe('Configurable', () => { await waitFor(() => { expect(screen.queryByText('Lorem ipsum')).toBeNull(); }); - expect(screen.queryByText('Text block')).toBeNull(); + await waitFor(() => { + expect(screen.queryByText('Text block')).toBeNull(); + }); }); it('should not remove the editor when unmounting another configurable element', async () => { diff --git a/packages/ra-ui-materialui/src/preferences/Inspector.spec.tsx b/packages/ra-ui-materialui/src/preferences/Inspector.spec.tsx index f4e24b59ddd..39d217fff45 100644 --- a/packages/ra-ui-materialui/src/preferences/Inspector.spec.tsx +++ b/packages/ra-ui-materialui/src/preferences/Inspector.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import expect from 'expect'; import { Inspector } from './Inspector.stories'; @@ -13,10 +13,14 @@ describe('Inspector', () => { ) ).toBeNull(); }); - it('should render when edit mode is turned on', () => { + it('should render when edit mode is turned on', async () => { render(); screen.getByLabelText('Configure mode').click(); - screen.getByText('Hover the application UI elements to configure them'); + await waitFor(() => { + screen.getByText( + 'Hover the application UI elements to configure them' + ); + }); }); it('should disappear when edit mode is turned off', () => { render(); @@ -28,14 +32,16 @@ describe('Inspector', () => { ) ).toBeNull(); }); - it('should disappear when closed by the user', () => { + it('should disappear when closed by the user', async () => { render(); screen.getByLabelText('Configure mode').click(); - screen.getByLabelText('ra.action.close').click(); - expect( - screen.queryByText( - 'Hover the application UI elements to configure them' - ) - ).toBeNull(); + (await screen.findByLabelText('ra.action.close')).click(); + await waitFor(() => { + expect( + screen.queryByText( + 'Hover the application UI elements to configure them' + ) + ).toBeNull(); + }); }); }); From 8d3703cfa8005768e0083127225b8b4e576e59e0 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 12:29:22 +0100 Subject: [PATCH 033/103] Fix build --- packages/ra-ui-materialui/package.json | 1 + yarn.lock | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index 81d2fac6cd3..f8ca2595121 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -32,6 +32,7 @@ "@types/dompurify": "^3.0.2", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.16", + "@types/react-is": "^18.2.4", "@types/react-transition-group": "^4.4.9", "cross-env": "^5.2.0", "expect": "^27.4.6", diff --git a/yarn.lock b/yarn.lock index dc4ea87c3c5..da4967d9ff3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6453,6 +6453,15 @@ __metadata: languageName: node linkType: hard +"@types/react-is@npm:^18.2.4": + version: 18.2.4 + resolution: "@types/react-is@npm:18.2.4" + dependencies: + "@types/react": "*" + checksum: f1085c3c27aed16b89fac137ee3771ce161b6faa63be90e47f91a3867864c4612df24e9937d2eb621feaa84d29cb8829cf2c42f69f611fad8abc15929377e5db + languageName: node + linkType: hard + "@types/react-transition-group@npm:^4.4.8, @types/react-transition-group@npm:^4.4.9": version: 4.4.9 resolution: "@types/react-transition-group@npm:4.4.9" @@ -18575,6 +18584,7 @@ __metadata: "@types/dompurify": ^3.0.2 "@types/react": ^18.2.37 "@types/react-dom": ^18.2.16 + "@types/react-is": ^18.2.4 "@types/react-transition-group": ^4.4.9 autosuggest-highlight: ^3.1.1 clsx: ^1.1.1 From 782c7c9d5fd57b47ec640e6e207c0f33b0b6f453 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:16:07 +0100 Subject: [PATCH 034/103] Fix data and error checks --- packages/ra-core/src/auth/useAuthState.ts | 4 ++-- packages/ra-core/src/auth/useGetIdentity.ts | 4 ++-- packages/ra-core/src/auth/usePermissions.ts | 4 ++-- packages/ra-core/src/dataProvider/useGetList.ts | 6 +++--- packages/ra-core/src/dataProvider/useGetMany.ts | 4 ++-- packages/ra-core/src/dataProvider/useGetManyAggregate.ts | 4 ++-- packages/ra-core/src/dataProvider/useGetManyReference.ts | 4 ++-- packages/ra-core/src/dataProvider/useGetOne.ts | 4 ++-- packages/ra-core/src/dataProvider/useInfiniteGetList.ts | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts index 14bb0bb3774..558fa628723 100644 --- a/packages/ra-core/src/auth/useAuthState.ts +++ b/packages/ra-core/src/auth/useAuthState.ts @@ -74,13 +74,13 @@ const useAuthState = ( }); useEffect(() => { - if (result.data && onSuccess) { + if (result.data != null && onSuccess) { onSuccess(result.data); } }, [onSuccess, result.data]); useEffect(() => { - if (result.error) { + if (result.error != null) { if (onError) { return onError(result.error); } diff --git a/packages/ra-core/src/auth/useGetIdentity.ts b/packages/ra-core/src/auth/useGetIdentity.ts index 8b16d2faf9c..fa0de08f670 100644 --- a/packages/ra-core/src/auth/useGetIdentity.ts +++ b/packages/ra-core/src/auth/useGetIdentity.ts @@ -63,13 +63,13 @@ export const useGetIdentity = ( }); useEffect(() => { - if (result.data && onSuccess) { + if (result.data != null && onSuccess) { onSuccess(result.data); } }, [onSuccess, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); diff --git a/packages/ra-core/src/auth/usePermissions.ts b/packages/ra-core/src/auth/usePermissions.ts index 4f674f6761b..c27cc44305d 100644 --- a/packages/ra-core/src/auth/usePermissions.ts +++ b/packages/ra-core/src/auth/usePermissions.ts @@ -56,13 +56,13 @@ const usePermissions = ( }); useEffect(() => { - if (result.data && onSuccess) { + if (result.data != null && onSuccess) { onSuccess(result.data); } }, [onSuccess, result.data]); useEffect(() => { - if (result.error) { + if (result.error != null) { if (onError) { return onError(result.error); } diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts index 03167328ee2..75eaf360278 100644 --- a/packages/ra-core/src/dataProvider/useGetList.ts +++ b/packages/ra-core/src/dataProvider/useGetList.ts @@ -92,7 +92,7 @@ export const useGetList = ( useEffect(() => { // optimistically populate the getOne cache if ( - result.data && + result.data != null && result.data?.data && result.data.data.length <= MAX_DATA_LENGTH_TO_CACHE ) { @@ -104,13 +104,13 @@ export const useGetList = ( }); } // execute call-time onSuccess if provided - if (result.data && onSuccess) { + if (result.data != null && onSuccess) { onSuccess(result.data); } }, [meta, onSuccess, queryClient, resource, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index 054e80b6f32..57410640e18 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -103,7 +103,7 @@ export const useGetMany = ( }); useEffect(() => { - if (result.data) { + if (result.data != null) { // optimistically populate the getOne cache result.data.forEach(record => { queryClient.setQueryData( @@ -120,7 +120,7 @@ export const useGetMany = ( }, [queryClient, meta, onSuccess, resource, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); diff --git a/packages/ra-core/src/dataProvider/useGetManyAggregate.ts b/packages/ra-core/src/dataProvider/useGetManyAggregate.ts index 4a7cd658fe9..b7b443ca5f6 100644 --- a/packages/ra-core/src/dataProvider/useGetManyAggregate.ts +++ b/packages/ra-core/src/dataProvider/useGetManyAggregate.ts @@ -123,7 +123,7 @@ export const useGetManyAggregate = ( }); useEffect(() => { - if (result.data) { + if (result.data != null) { // optimistically populate the getOne cache (result.data ?? []).forEach(record => { queryClient.setQueryData( @@ -140,7 +140,7 @@ export const useGetManyAggregate = ( }, [queryClient, meta, onSuccess, resource, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); diff --git a/packages/ra-core/src/dataProvider/useGetManyReference.ts b/packages/ra-core/src/dataProvider/useGetManyReference.ts index 41ea2323932..b3b135f0767 100644 --- a/packages/ra-core/src/dataProvider/useGetManyReference.ts +++ b/packages/ra-core/src/dataProvider/useGetManyReference.ts @@ -110,7 +110,7 @@ export const useGetManyReference = ( }); useEffect(() => { - if (result.data) { + if (result.data != null) { // optimistically populate the getOne cache result.data?.data?.forEach(record => { queryClient.setQueryData( @@ -127,7 +127,7 @@ export const useGetManyReference = ( }, [queryClient, meta, onSuccess, resource, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); diff --git a/packages/ra-core/src/dataProvider/useGetOne.ts b/packages/ra-core/src/dataProvider/useGetOne.ts index 3f0fc592547..35190fb6739 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.ts +++ b/packages/ra-core/src/dataProvider/useGetOne.ts @@ -67,13 +67,13 @@ export const useGetOne = ( }); useEffect(() => { - if (result.data && onSuccess) { + if (result.data != null && onSuccess) { onSuccess(result.data); } }, [onSuccess, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); diff --git a/packages/ra-core/src/dataProvider/useInfiniteGetList.ts b/packages/ra-core/src/dataProvider/useInfiniteGetList.ts index 0575e7969d7..35bf4dd1822 100644 --- a/packages/ra-core/src/dataProvider/useInfiniteGetList.ts +++ b/packages/ra-core/src/dataProvider/useInfiniteGetList.ts @@ -137,7 +137,7 @@ export const useInfiniteGetList = ( }); useEffect(() => { - if (result.data) { + if (result.data != null) { // optimistically populate the getOne cache result.data.pages.forEach(page => { page.data.forEach(record => { @@ -155,7 +155,7 @@ export const useInfiniteGetList = ( }, [meta, onSuccess, queryClient, resource, result.data]); useEffect(() => { - if (result.error && onError) { + if (result.error != null && onError) { onError(result.error); } }, [onError, result.error]); From ce8fe48dddeae035456f97473a031a743395194a Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:22:51 +0100 Subject: [PATCH 035/103] Fix side effects --- .../button/useDeleteWithConfirmController.tsx | 71 +++++----- .../button/useDeleteWithUndoController.tsx | 65 +++++----- .../src/controller/edit/useEditController.ts | 122 ++++++++---------- .../ra-core/src/dataProvider/useUpdate.ts | 45 +++---- 4 files changed, 141 insertions(+), 162 deletions(-) diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx index 552a4ea0dfc..81a65f6bc5e 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx @@ -79,7 +79,42 @@ const useDeleteWithConfirmController = ( const notify = useNotify(); const unselect = useUnselect(resource); const redirect = useRedirect(); - const [deleteOne, { isPending }] = useDelete(); + const [deleteOne, { isPending }] = useDelete( + resource, + { id: record?.id }, + { + onSuccess: () => { + setOpen(false); + notify('ra.notification.deleted', { + type: 'info', + messageArgs: { smart_count: 1 }, + undoable: mutationMode === 'undoable', + }); + unselect([record.id]); + redirect(redirectTo, resource); + }, + onError: (error: Error) => { + setOpen(false); + + notify( + typeof error === 'string' + ? error + : error.message || 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : error && error.message + ? error.message + : undefined, + }, + } + ); + }, + } + ); const handleDialogOpen = e => { setOpen(true); @@ -102,36 +137,6 @@ const useDeleteWithConfirmController = ( meta: mutationMeta, }, { - onSuccess: () => { - setOpen(false); - notify('ra.notification.deleted', { - type: 'info', - messageArgs: { smart_count: 1 }, - undoable: mutationMode === 'undoable', - }); - unselect([record.id]); - redirect(redirectTo, resource); - }, - onError: (error: Error) => { - setOpen(false); - - notify( - typeof error === 'string' - ? error - : error.message || 'ra.notification.http_error', - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : error && error.message - ? error.message - : undefined, - }, - } - ); - }, mutationMode, ...otherMutationOptions, } @@ -145,13 +150,9 @@ const useDeleteWithConfirmController = ( mutationMeta, mutationMode, otherMutationOptions, - notify, onClick, record, - redirect, - redirectTo, resource, - unselect, ] ); diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx index 824c5c085ee..5a12377bac3 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx @@ -57,7 +57,39 @@ const useDeleteWithUndoController = ( const notify = useNotify(); const unselect = useUnselect(resource); const redirect = useRedirect(); - const [deleteOne, { isPending }] = useDelete(); + const [deleteOne, { isPending }] = useDelete( + resource, + { id: record?.id }, + { + onSuccess: () => { + notify('ra.notification.deleted', { + type: 'info', + messageArgs: { smart_count: 1 }, + undoable: true, + }); + unselect([record.id]); + redirect(redirectTo, resource); + }, + onError: (error: Error) => { + notify( + typeof error === 'string' + ? error + : error.message || 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : error && error.message + ? error.message + : undefined, + }, + } + ); + }, + } + ); const handleDelete = useCallback( event => { @@ -70,33 +102,6 @@ const useDeleteWithUndoController = ( meta: mutationMeta, }, { - onSuccess: () => { - notify('ra.notification.deleted', { - type: 'info', - messageArgs: { smart_count: 1 }, - undoable: true, - }); - unselect([record.id]); - redirect(redirectTo, resource); - }, - onError: (error: Error) => { - notify( - typeof error === 'string' - ? error - : error.message || 'ra.notification.http_error', - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : error && error.message - ? error.message - : undefined, - }, - } - ); - }, mutationMode: 'undoable', ...otherMutationOptions, } @@ -109,13 +114,9 @@ const useDeleteWithUndoController = ( deleteOne, mutationMeta, otherMutationOptions, - notify, onClick, record, - redirect, - redirectTo, resource, - unselect, ] ); diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts index 0d324300ea4..94534e6542c 100644 --- a/packages/ra-core/src/controller/edit/useEditController.ts +++ b/packages/ra-core/src/controller/edit/useEditController.ts @@ -48,11 +48,8 @@ import { * return ; * } */ -export const useEditController = < - RecordType extends RaRecord = any, - MutationOptionsError = unknown ->( - props: EditControllerProps = {} +export const useEditController = ( + props: EditControllerProps = {} ): EditControllerResult => { const { disableAuthentication, @@ -125,14 +122,48 @@ export const useEditController = < const recordCached = { id, previousData: record }; - const [update, { isPending: saving }] = useUpdate< - RecordType, - MutationOptionsError - >(resource, recordCached, { - ...otherMutationOptions, - mutationMode, - returnPromise: mutationMode === 'pessimistic', - }); + const [update, { isPending: saving }] = useUpdate( + resource, + recordCached, + { + onSuccess: async (data, variables, context) => { + if (onSuccess) { + return onSuccess(data, variables, context); + } + + notify('ra.notification.updated', { + type: 'info', + messageArgs: { smart_count: 1 }, + undoable: mutationMode === 'undoable', + }); + redirect(redirectTo, resource, data.id, data); + }, + onError: (error, variables, context) => { + if (onError) { + return onError(error, variables, context); + } + notify( + typeof error === 'string' + ? error + : error.message || 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : error && error.message + ? error.message + : undefined, + }, + } + ); + }, + ...otherMutationOptions, + mutationMode, + returnPromise: mutationMode === 'pessimistic', + } + ); const save = useCallback( ( @@ -160,52 +191,14 @@ export const useEditController = < try { await mutate( resource, - { id, data, meta: metaFromSave ?? mutationMeta }, { - onSuccess: async (data, variables, context) => { - if (onSuccessFromSave) { - return onSuccessFromSave( - data, - variables, - context - ); - } - - if (onSuccess) { - return onSuccess(data, variables, context); - } - - notify('ra.notification.updated', { - type: 'info', - messageArgs: { smart_count: 1 }, - undoable: mutationMode === 'undoable', - }); - redirect(redirectTo, resource, data.id, data); - }, - onError: onErrorFromSave - ? onErrorFromSave - : onError - ? onError - : (error: Error | string) => { - notify( - typeof error === 'string' - ? error - : error.message || - 'ra.notification.http_error', - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : error && - error.message - ? error.message - : undefined, - }, - } - ); - }, + id, + data, + meta: metaFromSave ?? mutationMeta, + }, + { + onSuccess: onSuccessFromSave, + oneError: onErrorFromSave, } ); } catch (error) { @@ -218,12 +211,6 @@ export const useEditController = < id, getMutateWithMiddlewares, mutationMeta, - mutationMode, - notify, - onError, - onSuccess, - redirect, - redirectTo, resource, transform, update, @@ -248,16 +235,13 @@ export const useEditController = < }; }; -export interface EditControllerProps< - RecordType extends RaRecord = any, - MutationOptionsError = unknown -> { +export interface EditControllerProps { disableAuthentication?: boolean; id?: RecordType['id']; mutationMode?: MutationMode; mutationOptions?: UseMutationOptions< RecordType, - MutationOptionsError, + Error, UseUpdateMutateParams > & { meta?: any }; queryOptions?: Omit, 'queryFn' | 'queryKey'> & { diff --git a/packages/ra-core/src/dataProvider/useUpdate.ts b/packages/ra-core/src/dataProvider/useUpdate.ts index 9db23e70715..17cde2263ae 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.ts +++ b/packages/ra-core/src/dataProvider/useUpdate.ts @@ -83,14 +83,11 @@ import { useEvent } from '../util'; * const [update, { data }] = useUpdate('products', { id, data: diff, previousData: product }); * \-- data is Product */ -export const useUpdate = < - RecordType extends RaRecord = any, - MutationError = unknown ->( +export const useUpdate = ( resource?: string, params: Partial> = {}, - options: UseUpdateOptions = {} -): UseUpdateResult => { + options: UseUpdateOptions = {} +): UseUpdateResult => { const dataProvider = useDataProvider(); const queryClient = useQueryClient(); const { id, data, meta } = params; @@ -172,7 +169,7 @@ export const useUpdate = < const mutation = useMutation< RecordType, - MutationError, + Error, Partial> >({ mutationFn: ({ @@ -207,11 +204,7 @@ export const useUpdate = < return { snapshot: snapshot.current }; } }, - onError: ( - error: MutationError, - variables: Partial> = {}, - context: { snapshot: Snapshot } - ) => { + onError: (error, variables = {}, context: { snapshot: Snapshot }) => { if (mode.current === 'optimistic' || mode.current === 'undoable') { // If the mutation fails, use the context returned from onMutate to rollback context.snapshot.forEach(([key, value]) => { @@ -248,9 +241,9 @@ export const useUpdate = < } }, onSettled: ( - data: RecordType, - error: MutationError, - variables: Partial> = {}, + data, + error, + variables = {}, context: { snapshot: Snapshot } ) => { if (mode.current === 'optimistic' || mode.current === 'undoable') { @@ -421,7 +414,10 @@ export const useUpdate = < // call the mutate method without success side effects mutation.mutate( { resource: callTimeResource, ...callTimeParams }, - { onSettled, onError } + { + onSettled, + onError, + } ); } }); @@ -442,24 +438,22 @@ export interface UseUpdateMutateParams { } export type UseUpdateOptions< - RecordType extends RaRecord = any, - MutationError = unknown + RecordType extends RaRecord = any > = UseMutationOptions< RecordType, - MutationError, + Error, Partial, 'mutationFn'>> > & { mutationMode?: MutationMode; returnPromise?: boolean }; export type UpdateMutationFunction< RecordType extends RaRecord = any, - TReturnPromise extends boolean = boolean, - MutationError = unknown + TReturnPromise extends boolean = boolean > = ( resource?: string, params?: Partial>, options?: MutateOptions< RecordType, - MutationError, + Error, Partial>, unknown > & { mutationMode?: MutationMode; returnPromise?: TReturnPromise } @@ -467,13 +461,12 @@ export type UpdateMutationFunction< export type UseUpdateResult< RecordType extends RaRecord = any, - TReturnPromise extends boolean = boolean, - MutationError = unknown + TReturnPromise extends boolean = boolean > = [ - UpdateMutationFunction, + UpdateMutationFunction, UseMutationResult< RecordType, - MutationError, + Error, Partial & { resource?: string }>, unknown > From 927711fd7bfeb96458d0ee8a92a0a41a290bff52 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:23:00 +0100 Subject: [PATCH 036/103] Fix e2e tests --- cypress/e2e/list.cy.js | 3 +++ cypress/e2e/mobile.cy.js | 6 +++--- cypress/support/CustomFormPage.js | 2 +- cypress/support/ListPage.js | 2 +- examples/simple/src/Layout.tsx | 7 ++++++- examples/simple/src/authProvider.tsx | 6 +++--- examples/simple/src/dataProvider.tsx | 3 +++ examples/simple/src/index.tsx | 2 ++ examples/simple/src/posts/PostCreate.tsx | 2 +- examples/simple/src/posts/PostEdit.tsx | 2 +- examples/simple/src/queryClient.ts | 3 +++ examples/simple/src/users/UserCreate.tsx | 2 +- examples/simple/src/users/UserEdit.tsx | 2 +- examples/simple/src/users/UserList.tsx | 2 +- examples/simple/src/users/UserShow.tsx | 2 +- 15 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 examples/simple/src/queryClient.ts diff --git a/cypress/e2e/list.cy.js b/cypress/e2e/list.cy.js index 9426e8c9571..ef10f40c9a5 100644 --- a/cypress/e2e/list.cy.js +++ b/cypress/e2e/list.cy.js @@ -106,6 +106,9 @@ describe('List Page', () => { LoginPage.login('admin', 'password'); ListPagePosts.navigate(); ListPagePosts.showFilter('title'); + cy.get(ListPagePosts.elements.filter('title')).should(el => + expect(el).to.have.value('Qui tempore rerum et voluptates') + ); ListPagePosts.setFilterValue( 'title', 'Omnis voluptate enim similique est possimus' diff --git a/cypress/e2e/mobile.cy.js b/cypress/e2e/mobile.cy.js index 7203edebc30..6834597eba4 100644 --- a/cypress/e2e/mobile.cy.js +++ b/cypress/e2e/mobile.cy.js @@ -9,12 +9,12 @@ describe('Mobile UI', () => { }); describe('Infinite Scroll', () => { - it.only('should load more items when scrolling to the bottom of the page', () => { + it('should load more items when scrolling to the bottom of the page', () => { ListPagePosts.navigate(); + cy.contains('Sint dignissimos in architecto aut'); cy.contains('Sed quo et et fugiat modi').should('not.exist'); - cy.scrollTo('bottom'); cy.wait(500); - cy.scrollTo('bottom'); + cy.contains('Sint dignissimos in architecto aut').scrollIntoView(); cy.contains('Sed quo et et fugiat modi'); }); }); diff --git a/cypress/support/CustomFormPage.js b/cypress/support/CustomFormPage.js index ccf1b79439b..b6c4fba8774 100644 --- a/cypress/support/CustomFormPage.js +++ b/cypress/support/CustomFormPage.js @@ -8,7 +8,7 @@ export default url => ({ "[data-testid='dialog-add-post'] button[type='submit']", submitAndAddButton: ".create-page form>div:last-child button[type='button']", - postSelect: '.ra-input-post_id [role="button"]', + postSelect: '.ra-input-post_id', postItem: id => `li[data-value="${id}"]`, showPostCreateModalButton: '[data-value="@@ra-create"]', showPostPreviewModalButton: '[data-testid="button-show-post"]', diff --git a/cypress/support/ListPage.js b/cypress/support/ListPage.js index f79452bc429..785b9b388fb 100644 --- a/cypress/support/ListPage.js +++ b/cypress/support/ListPage.js @@ -6,7 +6,7 @@ export default url => ({ filter: name => `.filter-field[data-source='${name}'] input`, filterButton: name => `.filter-field[data-source='${name}']`, filterMenuItems: `.new-filter-item`, - menuItems: `[role=menuitem]`, + menuItems: `a[role=menuitem]`, filterMenuItem: source => `.new-filter-item[data-key="${source}"]`, hideFilterButton: source => `.filter-field[data-source="${source}"] .hide-filter`, diff --git a/examples/simple/src/Layout.tsx b/examples/simple/src/Layout.tsx index e65ed0a1dfa..c099c9e40b6 100644 --- a/examples/simple/src/Layout.tsx +++ b/examples/simple/src/Layout.tsx @@ -12,6 +12,11 @@ const MyAppBar = () => ( export default props => ( <> - + {process.env.NODE_ENV === 'development' ? ( + + ) : null} ); diff --git a/examples/simple/src/authProvider.tsx b/examples/simple/src/authProvider.tsx index 7222f8cf945..4183ff98bb7 100644 --- a/examples/simple/src/authProvider.tsx +++ b/examples/simple/src/authProvider.tsx @@ -47,17 +47,17 @@ export default { }, checkError: ({ status }) => { return status === 401 || status === 403 - ? Promise.reject() + ? Promise.reject('') : Promise.resolve(); }, checkAuth: () => { return localStorage.getItem('not_authenticated') - ? Promise.reject() + ? Promise.reject('') : Promise.resolve(); }, getPermissions: () => { const role = localStorage.getItem('role'); - return Promise.resolve(role); + return Promise.resolve(role ?? ''); }, getIdentity: () => { return Promise.resolve({ diff --git a/examples/simple/src/dataProvider.tsx b/examples/simple/src/dataProvider.tsx index 935c4e102ef..1f3d064eb23 100644 --- a/examples/simple/src/dataProvider.tsx +++ b/examples/simple/src/dataProvider.tsx @@ -3,6 +3,7 @@ import { DataProvider, withLifecycleCallbacks, HttpError } from 'react-admin'; import get from 'lodash/get'; import data from './data'; import addUploadFeature from './addUploadFeature'; +import { queryClient } from './queryClient'; const dataProvider = withLifecycleCallbacks(fakeRestProvider(data, true), [ { @@ -17,6 +18,8 @@ const dataProvider = withLifecycleCallbacks(fakeRestProvider(data, true), [ await dp.deleteMany('comments', { ids: comments.map(comment => comment.id), }); + // The queryClient would be unaware of the deleted comments without this. + queryClient.invalidateQueries({ queryKey: ['comments'] }); return { id }; }, }, diff --git a/examples/simple/src/index.tsx b/examples/simple/src/index.tsx index 7d6afa78fbc..2e1dc277b4c 100644 --- a/examples/simple/src/index.tsx +++ b/examples/simple/src/index.tsx @@ -14,6 +14,7 @@ import Layout from './Layout'; import posts from './posts'; import users from './users'; import tags from './tags'; +import { queryClient } from './queryClient'; const container = document.getElementById('root'); const root = createRoot(container); @@ -24,6 +25,7 @@ root.render( authProvider={authProvider} dataProvider={dataProvider} i18nProvider={i18nProvider} + queryClient={queryClient} title="Example Admin" layout={Layout} > diff --git a/examples/simple/src/posts/PostCreate.tsx b/examples/simple/src/posts/PostCreate.tsx index f8acc6ee04e..69487092983 100644 --- a/examples/simple/src/posts/PostCreate.tsx +++ b/examples/simple/src/posts/PostCreate.tsx @@ -101,7 +101,7 @@ const PostCreate = () => { }), [] ); - const { permissions } = usePermissions(); + const { data: permissions } = usePermissions(); const dateDefaultValue = useMemo(() => new Date(), []); return ( diff --git a/examples/simple/src/posts/PostEdit.tsx b/examples/simple/src/posts/PostEdit.tsx index 68f742fc0dd..17dfaade2ac 100644 --- a/examples/simple/src/posts/PostEdit.tsx +++ b/examples/simple/src/posts/PostEdit.tsx @@ -101,7 +101,7 @@ const categories = [ ]; const PostEdit = () => { - const { permissions } = usePermissions(); + const { data: permissions } = usePermissions(); return ( } actions={}> ); const UserCreate = () => { - const { permissions } = usePermissions(); + const { data: permissions } = usePermissions(); const unique = useUnique(); return ( } redirect="show"> diff --git a/examples/simple/src/users/UserEdit.tsx b/examples/simple/src/users/UserEdit.tsx index 39adb35b301..0266f8dd530 100644 --- a/examples/simple/src/users/UserEdit.tsx +++ b/examples/simple/src/users/UserEdit.tsx @@ -43,7 +43,7 @@ const EditActions = () => ( ); const UserEditForm = () => { - const { permissions } = usePermissions(); + const { data: permissions } = usePermissions(); const { save } = useSaveContext(); const newSave = values => diff --git a/examples/simple/src/users/UserList.tsx b/examples/simple/src/users/UserList.tsx index 8d61c0bdf16..f30bf38203b 100644 --- a/examples/simple/src/users/UserList.tsx +++ b/examples/simple/src/users/UserList.tsx @@ -36,7 +36,7 @@ const rowClick = memoize(permissions => () => { }); const UserList = () => { - const { permissions } = usePermissions(); + const { data: permissions } = usePermissions(); return ( { - const { permissions } = usePermissions(); + const { data: permissions } = usePermissions(); return ( From 3b569f0313aa5bc28cde38127374259b04cd7e82 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 18:03:33 +0100 Subject: [PATCH 037/103] Fix side effects management in useEditController --- .../src/controller/edit/useEditController.ts | 105 ++++++++++++------ 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts index 94534e6542c..792cd77b0ca 100644 --- a/packages/ra-core/src/controller/edit/useEditController.ts +++ b/packages/ra-core/src/controller/edit/useEditController.ts @@ -1,6 +1,7 @@ -import { useCallback } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; import { useParams } from 'react-router-dom'; import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'; +import merge from 'lodash/merge'; import { useAuthenticated } from '../../auth'; import { RaRecord, MutationMode, TransformData } from '../../types'; @@ -25,6 +26,7 @@ import { SaveHandlerCallbacks, useMutationMiddlewares, } from '../saveContext'; +import { on } from 'events'; /** * Prepare data for the Edit view. @@ -71,9 +73,9 @@ export const useEditController = ( const id = propsId != null ? propsId : decodeURIComponent(routeId); const { meta: queryMeta, ...otherQueryOptions } = queryOptions; const { + meta: mutationMeta, onSuccess, onError, - meta: mutationMeta, ...otherMutationOptions } = mutationOptions; const { @@ -122,43 +124,64 @@ export const useEditController = ( const recordCached = { id, previousData: record }; - const [update, { isPending: saving }] = useUpdate( - resource, - recordCached, - { + const hasOverridenOnSuccess = useRef(false); + const hasOverridenOnError = useRef(false); + + const defaultSideEffects = useMemo( + () => ({ onSuccess: async (data, variables, context) => { - if (onSuccess) { - return onSuccess(data, variables, context); + if (!hasOverridenOnSuccess.current) { + if (onSuccess) { + return onSuccess(data, variables, context); + } + notify('ra.notification.updated', { + type: 'info', + messageArgs: { smart_count: 1 }, + undoable: mutationMode === 'undoable', + }); + redirect(redirectTo, resource, data.id, data); } - - notify('ra.notification.updated', { - type: 'info', - messageArgs: { smart_count: 1 }, - undoable: mutationMode === 'undoable', - }); - redirect(redirectTo, resource, data.id, data); }, onError: (error, variables, context) => { - if (onError) { - return onError(error, variables, context); - } - notify( - typeof error === 'string' - ? error - : error.message || 'ra.notification.http_error', - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : error && error.message - ? error.message - : undefined, - }, + if (!hasOverridenOnError.current) { + if (onError) { + return onError(error, variables, context); } - ); + notify( + typeof error === 'string' + ? error + : error.message || 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : error && error.message + ? error.message + : undefined, + }, + } + ); + } }, + }), + [ + mutationMode, + notify, + onSuccess, + onError, + redirect, + redirectTo, + resource, + ] + ); + + const [update, { isPending: saving }] = useUpdate( + resource, + recordCached, + { + ...(mutationMode !== 'pessimistic' ? defaultSideEffects : {}), ...otherMutationOptions, mutationMode, returnPromise: mutationMode === 'pessimistic', @@ -186,8 +209,17 @@ export const useEditController = ( }) : data ).then(async (data: Partial) => { + hasOverridenOnSuccess.current = !!onSuccessFromSave; + hasOverridenOnError.current = !!onErrorFromSave; + const mutate = getMutateWithMiddlewares(update); + let options = merge( + {}, + mutationMode === 'pessimistic' ? defaultSideEffects : {}, + { onError: onErrorFromSave, onSuccess: onSuccessFromSave } + ); + try { await mutate( resource, @@ -196,10 +228,7 @@ export const useEditController = ( data, meta: metaFromSave ?? mutationMeta, }, - { - onSuccess: onSuccessFromSave, - oneError: onErrorFromSave, - } + options ); } catch (error) { if ((error as HttpError).body?.errors != null) { @@ -208,6 +237,8 @@ export const useEditController = ( } }), [ + defaultSideEffects, + mutationMode, id, getMutateWithMiddlewares, mutationMeta, From d33e455c5a3352aee16631d183ebfde8811d184c Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 18:19:50 +0100 Subject: [PATCH 038/103] Fix lint error --- packages/ra-core/src/controller/edit/useEditController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts index 792cd77b0ca..a9e37e95741 100644 --- a/packages/ra-core/src/controller/edit/useEditController.ts +++ b/packages/ra-core/src/controller/edit/useEditController.ts @@ -26,7 +26,6 @@ import { SaveHandlerCallbacks, useMutationMiddlewares, } from '../saveContext'; -import { on } from 'events'; /** * Prepare data for the Edit view. From 011ca150d340de528818bf55ba2d8835aec78523 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 18:44:45 +0100 Subject: [PATCH 039/103] Fix unstable test --- packages/ra-ui-materialui/src/list/filter/FilterForm.spec.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ra-ui-materialui/src/list/filter/FilterForm.spec.tsx b/packages/ra-ui-materialui/src/list/filter/FilterForm.spec.tsx index bd92c31652b..9635f95fbf4 100644 --- a/packages/ra-ui-materialui/src/list/filter/FilterForm.spec.tsx +++ b/packages/ra-ui-materialui/src/list/filter/FilterForm.spec.tsx @@ -190,7 +190,9 @@ describe('', () => { fireEvent.click(await screen.findByTitle('Remove this filter')); await screen.findByText('1-10 of 13'); - expect(screen.queryByText('Nested')).toBeNull(); + await waitFor(() => { + expect(screen.queryByText('Nested')).toBeNull(); + }); expect(screen.queryByLabelText('Nested')).toBeNull(); }); From 5271d9740a8956a73909b5d105a52053d5db0151 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 18:59:07 +0100 Subject: [PATCH 040/103] Stabilize more tests --- .../src/controller/list/useInfiniteListController.spec.tsx | 4 +++- .../ra-core/src/controller/list/useListController.spec.tsx | 4 +++- .../ra-ui-materialui/src/input/AutocompleteInput.spec.tsx | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx index a3680dac4f5..1d99fa016bb 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx @@ -162,7 +162,9 @@ describe('useInfiniteListController', () => { fireEvent.change(searchInput, { target: { value: 'hello' } }); await new Promise(resolve => setTimeout(resolve, 210)); - expect(storeSpy).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(storeSpy).toHaveBeenCalledTimes(1); + }); expect(storeSpy).toHaveBeenCalledWith('posts.listParams', { filter: { q: 'hello' }, order: 'ASC', diff --git a/packages/ra-core/src/controller/list/useListController.spec.tsx b/packages/ra-core/src/controller/list/useListController.spec.tsx index 1e768aefece..565a30496fa 100644 --- a/packages/ra-core/src/controller/list/useListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useListController.spec.tsx @@ -146,7 +146,9 @@ describe('useListController', () => { fireEvent.change(searchInput, { target: { value: 'hello' } }); await new Promise(resolve => setTimeout(resolve, 210)); - expect(storeSpy).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(storeSpy).toHaveBeenCalledTimes(1); + }); expect(storeSpy).toHaveBeenCalledWith('posts.listParams', { filter: { q: 'hello' }, order: 'ASC', diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx index 2996d515075..9efc593f300 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx @@ -1285,6 +1285,7 @@ describe('', () => { describe('Inside ', () => { it('should work inside a ReferenceInput field', async () => { render(); + await screen.findByDisplayValue('Leo Tolstoy'); await waitFor(() => { expect( (screen.getByRole('combobox') as HTMLInputElement).value @@ -1311,6 +1312,7 @@ describe('', () => { it('should allow to clear the value inside a ReferenceInput field', async () => { render(); + await screen.findByDisplayValue('Leo Tolstoy'); await waitFor(() => { expect( (screen.getByRole('combobox') as HTMLInputElement).value From eb91d200db30774f0a601315ab7ed40440014ccf Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:21:09 +0100 Subject: [PATCH 041/103] Cleanup some test logs --- .../controller/edit/useEditController.spec.tsx | 3 +-- .../src/controller/edit/useEditController.ts | 14 ++++---------- .../src/dataProvider/useUpdate.spec.tsx | 18 +++++++++++++----- .../ra-core/src/form/FormDataConsumer.spec.tsx | 2 +- .../src/list/datagrid/Datagrid.spec.tsx | 5 ++++- .../src/list/pagination/Pagination.tsx | 2 ++ .../src/list/pagination/PaginationActions.tsx | 2 ++ 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/ra-core/src/controller/edit/useEditController.spec.tsx b/packages/ra-core/src/controller/edit/useEditController.spec.tsx index 753fd9bd9bd..beb5c1cf4e4 100644 --- a/packages/ra-core/src/controller/edit/useEditController.spec.tsx +++ b/packages/ra-core/src/controller/edit/useEditController.spec.tsx @@ -321,8 +321,7 @@ describe('useEditController', () => { await new Promise(resolve => setTimeout(resolve, 10)); screen.getByText('{"id":12}'); await act(async () => saveCallback({ foo: 'bar' })); - await new Promise(resolve => setTimeout(resolve, 10)); - screen.getByText('{"id":12,"foo":"bar"}'); + await screen.findByText('{"id":12,"foo":"bar"}'); expect(update).toHaveBeenCalledWith('posts', { id: 12, data: { foo: 'bar' }, diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts index a9e37e95741..eda85beddb8 100644 --- a/packages/ra-core/src/controller/edit/useEditController.ts +++ b/packages/ra-core/src/controller/edit/useEditController.ts @@ -1,6 +1,5 @@ import { useCallback, useMemo, useRef } from 'react'; import { useParams } from 'react-router-dom'; -import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'; import merge from 'lodash/merge'; import { useAuthenticated } from '../../auth'; @@ -12,8 +11,9 @@ import { useUpdate, useRefresh, UseGetOneHookValue, - UseUpdateMutateParams, HttpError, + UseGetOneOptions, + UseUpdateOptions, } from '../../dataProvider'; import { useTranslate } from '../../i18n'; import { @@ -269,14 +269,8 @@ export interface EditControllerProps { disableAuthentication?: boolean; id?: RecordType['id']; mutationMode?: MutationMode; - mutationOptions?: UseMutationOptions< - RecordType, - Error, - UseUpdateMutateParams - > & { meta?: any }; - queryOptions?: Omit, 'queryFn' | 'queryKey'> & { - meta?: any; - }; + mutationOptions?: UseUpdateOptions; + queryOptions?: UseGetOneOptions; redirect?: RedirectionSideEffect; resource?: string; transform?: TransformData; diff --git a/packages/ra-core/src/dataProvider/useUpdate.spec.tsx b/packages/ra-core/src/dataProvider/useUpdate.spec.tsx index 029b818c900..dbc03b15f01 100644 --- a/packages/ra-core/src/dataProvider/useUpdate.spec.tsx +++ b/packages/ra-core/src/dataProvider/useUpdate.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import expect from 'expect'; import { CoreAdminContext } from '../core'; @@ -260,13 +260,17 @@ describe('useUpdate', () => { it('when undoable, displays result and success side effects right away and fetched on confirm', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); render(); - screen.getByText('Update title').click(); + act(() => { + screen.getByText('Update title').click(); + }); await waitFor(() => { expect(screen.queryByText('success')).not.toBeNull(); expect(screen.queryByText('Hello World')).not.toBeNull(); expect(screen.queryByText('mutating')).toBeNull(); }); - screen.getByText('Confirm').click(); + act(() => { + screen.getByText('Confirm').click(); + }); await waitFor(() => { expect(screen.queryByText('success')).not.toBeNull(); expect(screen.queryByText('Hello World')).not.toBeNull(); @@ -285,13 +289,17 @@ describe('useUpdate', () => { jest.spyOn(console, 'log').mockImplementation(() => {}); render(); await screen.findByText('Hello'); - screen.getByText('Update title').click(); + act(() => { + screen.getByText('Update title').click(); + }); await waitFor(() => { expect(screen.queryByText('success')).not.toBeNull(); expect(screen.queryByText('Hello World')).not.toBeNull(); expect(screen.queryByText('mutating')).toBeNull(); }); - screen.getByText('Cancel').click(); + act(() => { + screen.getByText('Cancel').click(); + }); await waitFor(() => { expect(screen.queryByText('Hello World')).toBeNull(); }); diff --git a/packages/ra-core/src/form/FormDataConsumer.spec.tsx b/packages/ra-core/src/form/FormDataConsumer.spec.tsx index abec345a043..5f3fccd8e0d 100644 --- a/packages/ra-core/src/form/FormDataConsumer.spec.tsx +++ b/packages/ra-core/src/form/FormDataConsumer.spec.tsx @@ -66,7 +66,7 @@ describe('FormDataConsumerView', () => { - {({ formData, ...rest }) => { + {({ formData, getSource, ...rest }) => { globalFormData = formData; return ; diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx index 89012b47525..c01dae43975 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx @@ -8,6 +8,7 @@ import { } from 'ra-core'; import { ThemeProvider, createTheme } from '@mui/material'; import { Datagrid } from './Datagrid'; +import { TextField } from '../../field/TextField'; const TitleField = (): JSX.Element => { const record = useRecordContext(); @@ -272,7 +273,9 @@ describe('', () => { it('should display a message when there is no result', () => { render( - + + + ); expect(screen.queryByText('ra.navigation.no_results')).not.toBeNull(); diff --git a/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx b/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx index a73e3abb358..e1ce9976f77 100644 --- a/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx +++ b/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx @@ -23,6 +23,8 @@ export const Pagination: FC = memo(props => { rowsPerPageOptions = DefaultRowsPerPageOptions, actions, limit = null, + hasNextPage: _hasNextPage, + hasPreviousPage: _hasPreviousPage, ...rest } = props; const { diff --git a/packages/ra-ui-materialui/src/list/pagination/PaginationActions.tsx b/packages/ra-ui-materialui/src/list/pagination/PaginationActions.tsx index 851c5f4048b..d452e29fbd2 100644 --- a/packages/ra-ui-materialui/src/list/pagination/PaginationActions.tsx +++ b/packages/ra-ui-materialui/src/list/pagination/PaginationActions.tsx @@ -13,6 +13,8 @@ export const PaginationActions: FC = memo(props => { onPageChange, size = 'small', className, + // @ts-ignore + slotProps, ...rest } = props; const translate = useTranslate(); From 00f25a5e99790a757c7ca67be4560e8c96cc972d Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Fri, 24 Nov 2023 11:11:15 +0100 Subject: [PATCH 042/103] Add upgrade guide --- docs/Upgrade.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/docs/Upgrade.md b/docs/Upgrade.md index a9fefcf3694..9e753e83fe6 100644 --- a/docs/Upgrade.md +++ b/docs/Upgrade.md @@ -5,6 +5,112 @@ title: "Upgrading to v5" # Upgrading to v5 +React-admin v5 has upgraded all its dependencies to their latest major version. Some major dependencies were swapped (`@tanstack/react-query` instead of `react-query`). + +## `@tanstack/react-query` instead of `react-query` + +We upgraded `react-query` to version 5. If you used some features directly from this library, you'll have to follow their migration guide for [v4](https://tanstack.com/query/v5/docs/react/guides/migrating-to-react-query-4) and [v5](https://tanstack.com/query/v5/docs/react/guides/migrating-to-v5). Here's a list of the most important things: + + - The package has been renamed to `@tanstack/react-query` so you'll have to change your imports: + + ```diff + -import { useQuery } from 'react-query'; + +import { useQuery } from '@tanstack/react-query'; + ``` + +- `isLoading` has been renamed to `isPending`: + + ```diff + import * as React from 'react'; + import { useUpdate, useRecordContext, Button } from 'react-admin'; + + const ApproveButton = () => { + const record = useRecordContext(); + - const [approve, { isLoading }] = useUpdate('comments', { id: record.id, data: { isApproved: true }, previousData: record }); + + const [approve, { isPending }] = useUpdate('comments', { id: record.id, data: { isApproved: true }, previousData: record }); + - return * { - * const { isLoading, handleDelete } = useDeleteWithUndoController({ + * const { isPending, handleDelete } = useDeleteWithUndoController({ * resource, * record, * redirect, @@ -34,7 +34,7 @@ import { useResourceContext } from '../../core'; * return ( *