From e6a43f3efaaf4d97b19be1d6c9ddf7a63bdf43e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Tue, 8 Nov 2022 23:31:16 +0100 Subject: [PATCH 1/8] Add ability to refresh the user identity --- docs/useGetIdentity.md | 52 +++++++++ packages/ra-core/src/auth/index.ts | 4 +- .../ra-core/src/auth/useGetIdentity.spec.tsx | 25 +++++ .../src/auth/useGetIdentity.stories.tsx | 106 ++++++++++++++++++ packages/ra-core/src/auth/useGetIdentity.ts | 80 +++++++------ packages/ra-core/src/auth/usePermissions.ts | 9 +- .../ra-core/src/auth/useRefreshIdentity.ts | 36 ++++++ 7 files changed, 270 insertions(+), 42 deletions(-) create mode 100644 packages/ra-core/src/auth/useGetIdentity.spec.tsx create mode 100644 packages/ra-core/src/auth/useGetIdentity.stories.tsx create mode 100644 packages/ra-core/src/auth/useRefreshIdentity.ts diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index 421a27ae6ac..a38da6690e5 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -7,6 +7,20 @@ title: "useGetIdentity" You may want to use the current user name, avatar, or id in your code. for that purpose, call the `useGetIdentity()` hook, which calls `authProvider.getIdentity()` on mount. It returns an object containing the loading state, the error state, and the identity. +## Syntax + +```jsx +const { identity, isLoading, error } = useGetIdentity(); +``` + +Once loaded, the `identity` object contains the following properties: + +```jsx +const { id, fullName, avatar } = identity; +``` + +## Usage + Here is an example Edit component, which falls back to a Show component if the record is locked for edition by another user: ```jsx @@ -25,3 +39,41 @@ const PostDetail = ({ id }) => { } } ``` + +## Refreshing The Identity + +If your application contains a form letting the current user update their name and/or avatar, you may want to refresh the identity after the form is submitted. To do so, you should invalidate the react-query query cache for the `['auth', 'getIdentity']` key: + +```jsx +const IdentityForm = () => { + const { isLoading, error, identity } = useGetIdentity(); + const [newIdentity, setNewIdentity] = useState(''); + const queryClient = useQueryClient(); + + const handleChange = event => { + setNewIdentity(event.target.value); + }; + const handleSubmit = (e) => { + e.preventDefault(); + if (!newIdentity) return; + fetch('/update_identity', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ identity: newIdentity }) + }).then(() => { + // invalidate the react-query query cache for the useGetIdentity call + queryClient.invalidateQueries(['auth', 'getIdentity']); + }); + }; + + if (isLoading) return <>Loading; + if (error) return <>Error; + + return ( +
+ + +
+ ); +}; +``` \ No newline at end of file diff --git a/packages/ra-core/src/auth/index.ts b/packages/ra-core/src/auth/index.ts index ba3c9abcdf7..20e26b702bf 100644 --- a/packages/ra-core/src/auth/index.ts +++ b/packages/ra-core/src/auth/index.ts @@ -6,7 +6,6 @@ import usePermissionsOptimized from './usePermissionsOptimized'; import WithPermissions, { WithPermissionsProps } from './WithPermissions'; import useLogin from './useLogin'; import useLogout from './useLogout'; -import useGetIdentity from './useGetIdentity'; import useGetPermissions from './useGetPermissions'; import useLogoutIfAccessDenied from './useLogoutIfAccessDenied'; import convertLegacyAuthProvider from './convertLegacyAuthProvider'; @@ -15,6 +14,8 @@ export * from './Authenticated'; export * from './types'; export * from './useAuthenticated'; export * from './useCheckAuth'; +export * from './useGetIdentity'; +export * from './useRefreshIdentity'; export { AuthContext, @@ -23,7 +24,6 @@ export { // low-level hooks for calling a particular verb on the authProvider useLogin, useLogout, - useGetIdentity, useGetPermissions, // hooks with state management usePermissions, diff --git a/packages/ra-core/src/auth/useGetIdentity.spec.tsx b/packages/ra-core/src/auth/useGetIdentity.spec.tsx new file mode 100644 index 00000000000..f14429a88ce --- /dev/null +++ b/packages/ra-core/src/auth/useGetIdentity.spec.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; + +import { Basic, ErrorCase, ResetIdentity } from './useGetIdentity.stories'; + +describe('useGetIdentity', () => { + it('should return the identity', async () => { + render(); + await screen.findByText('John Doe'); + }); + it('should return the authProvider error', async () => { + jest.spyOn(console, 'error').mockImplementationOnce(() => {}); + render(); + await screen.findByText('Error'); + }); + it('should allow to invalidate the query cache', async () => { + render(); + expect(await screen.findByText('John Doe')).not.toBeNull(); + const input = screen.getByDisplayValue('John Doe'); + fireEvent.change(input, { target: { value: 'Jane Doe' } }); + fireEvent.click(screen.getByText('Save')); + await screen.findByText('Jane Doe'); + expect(screen.queryByText('John Doe')).toBeNull(); + }); +}); diff --git a/packages/ra-core/src/auth/useGetIdentity.stories.tsx b/packages/ra-core/src/auth/useGetIdentity.stories.tsx new file mode 100644 index 00000000000..346ea414082 --- /dev/null +++ b/packages/ra-core/src/auth/useGetIdentity.stories.tsx @@ -0,0 +1,106 @@ +import * as React from 'react'; +import { QueryClientProvider, QueryClient, useQueryClient } from 'react-query'; +import { useGetIdentity } from './useGetIdentity'; +import AuthContext from './AuthContext'; + +export default { + title: 'ra-core/auth/useGetIdentity', +}; + +const authProvider = { + login: () => Promise.resolve(), + logout: () => Promise.resolve(), + checkAuth: () => Promise.resolve(), + checkError: () => Promise.resolve(), + getPermissions: () => Promise.resolve(), + getIdentity: () => Promise.resolve({ id: 1, fullName: 'John Doe' }), +}; + +const Identity = () => { + const { identity, error, isLoading } = useGetIdentity(); + return isLoading ? ( + <>Loading + ) : error ? ( + <>Error + ) : ( + <>{identity.fullName} + ); +}; + +export const Basic = () => ( + + + + + +); + +export const ErrorCase = () => ( + + Promise.reject(new Error('Error')), + }} + > + + + +); + +export const ResetIdentity = () => { + let fullName = 'John Doe'; + + const IdentityForm = () => { + const { isLoading, error, identity } = useGetIdentity(); + const [newIdentity, setNewIdentity] = React.useState(''); + const queryClient = useQueryClient(); + + const handleChange = event => { + setNewIdentity(event.target.value); + }; + const handleSubmit = e => { + e.preventDefault(); + if (!newIdentity) return; + fullName = newIdentity; + queryClient.invalidateQueries(['auth', 'getIdentity']); + }; + + if (isLoading) return <>Loading; + if (error) return <>Error; + + return ( +
+ + +
+ ); + }; + + return ( + + Promise.resolve({ id: 1, fullName }), + }} + > + + + + + ); +}; diff --git a/packages/ra-core/src/auth/useGetIdentity.ts b/packages/ra-core/src/auth/useGetIdentity.ts index 24a11cbeda8..36f6a96a1f6 100644 --- a/packages/ra-core/src/auth/useGetIdentity.ts +++ b/packages/ra-core/src/auth/useGetIdentity.ts @@ -1,12 +1,16 @@ -import { useEffect } from 'react'; +import { useMemo } from 'react'; +import { useQuery, UseQueryOptions } from 'react-query'; + import useAuthProvider from './useAuthProvider'; import { UserIdentity } from '../types'; -import { useSafeSetState } from '../util/hooks'; const defaultIdentity = { id: '', fullName: null, }; +const defaultQueryParams = { + staleTime: 5 * 60 * 1000, +}; /** * Return the current user identity by calling authProvider.getIdentity() on mount @@ -38,42 +42,46 @@ const defaultIdentity = { * } * } */ -const useGetIdentity = () => { - const [state, setState] = useSafeSetState({ - isLoading: true, - }); +export const useGetIdentity = ( + queryParams: UseQueryOptions = defaultQueryParams +): UseGetIdentityResult => { const authProvider = useAuthProvider(); - useEffect(() => { - if (authProvider && typeof authProvider.getIdentity === 'function') { - const callAuthProvider = async () => { - try { - const identity = await authProvider.getIdentity(); - setState({ - isLoading: false, - identity: identity || defaultIdentity, - }); - } catch (error) { - setState({ - isLoading: false, - error, - }); - } - }; - callAuthProvider(); - } else { - setState({ - isLoading: false, - identity: defaultIdentity, - }); - } - }, [authProvider, setState]); - return state; + + const result = useQuery( + ['auth', 'getIdentity'], + authProvider + ? () => authProvider.getIdentity() + : async () => defaultIdentity, + queryParams + ); + + return useMemo( + () => + result.isLoading + ? { isLoading: true } + : result.error + ? { error: result.error, isLoading: false } + : { identity: result.data, isLoading: false }, + + [result] + ); }; -interface State { - isLoading: boolean; - identity?: UserIdentity; - error?: any; -} +export type UseGetIdentityResult = + | { + isLoading: true; + identity?: undefined; + error?: undefined; + } + | { + isLoading: false; + identity?: undefined; + error: Error; + } + | { + isLoading: false; + identity: UserIdentity; + error?: undefined; + }; export default useGetIdentity; diff --git a/packages/ra-core/src/auth/usePermissions.ts b/packages/ra-core/src/auth/usePermissions.ts index 10386ef12aa..d293c3cdf61 100644 --- a/packages/ra-core/src/auth/usePermissions.ts +++ b/packages/ra-core/src/auth/usePermissions.ts @@ -49,13 +49,14 @@ const usePermissions = ( queryParams ); - return useMemo(() => { - return { + return useMemo( + () => ({ permissions: result.data, isLoading: result.isLoading, error: result.error, - }; - }, [result]); + }), + [result] + ); }; export default usePermissions; diff --git a/packages/ra-core/src/auth/useRefreshIdentity.ts b/packages/ra-core/src/auth/useRefreshIdentity.ts new file mode 100644 index 00000000000..9e081e254f4 --- /dev/null +++ b/packages/ra-core/src/auth/useRefreshIdentity.ts @@ -0,0 +1,36 @@ +import { useCallback } from 'react'; +import { useQueryClient } from 'react-query'; + +/** + * Get a callback to force a refetch of the current user identity + * + * @example + * const IdentityForm = () => { + * const { isLoading, error, identity } = useGetIdentity(); + * const [newIdentity, setNewIdentity] = useState(''); + * const refresh = useRefreshIdentity(); + * const handleSubmit = (e) => { + * e.preventDefault(); + * if (!newIdentity) return; + * fetch('/update_identity', { + * method: 'POST', + * headers: { 'Content-Type': 'application/json' }, + * body: JSON.stringify({ identity: newIdentity }) + * }).then(refresh); + * }; + * if (isLoading) return <>Loading; + * if (error) return <>Error; + * return ( + *
+ * setNewIdentity(e.target.value)} /> + * + *
+ * ); + * }; + */ +export const useRefreshIdentity = () => { + const queryClient = useQueryClient(); + return useCallback(() => { + queryClient.invalidateQueries(['auth', 'getIdentity']); + }, [queryClient]); +}; From e7d18a8c2f84ab1c12b4684489cdc484f2b2affb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Wed, 9 Nov 2022 07:52:41 +0100 Subject: [PATCH 2/8] Add refetch to the return of useGetIdentity --- docs/useGetIdentity.md | 18 +++++----- packages/ra-core/src/auth/index.ts | 1 - .../src/auth/useGetIdentity.stories.tsx | 12 +++---- packages/ra-core/src/auth/useGetIdentity.ts | 16 ++++++--- .../ra-core/src/auth/useRefreshIdentity.ts | 36 ------------------- 5 files changed, 26 insertions(+), 57 deletions(-) delete mode 100644 packages/ra-core/src/auth/useRefreshIdentity.ts diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index a38da6690e5..da107b56160 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -42,13 +42,15 @@ const PostDetail = ({ id }) => { ## Refreshing The Identity -If your application contains a form letting the current user update their name and/or avatar, you may want to refresh the identity after the form is submitted. To do so, you should invalidate the react-query query cache for the `['auth', 'getIdentity']` key: +If your application contains a form letting the current user update their name and/or avatar, you may want to refresh the identity after the form is submitted. As `useGetIdentity` uses [react-query's `useQuery` hook](https://react-query-v3.tanstack.com/reference/useQuery) to call the `authProvider`, you can take advantage of the `refetch` function to do so: ```jsx const IdentityForm = () => { - const { isLoading, error, identity } = useGetIdentity(); + const { isLoading, error, identity, refetch } = useGetIdentity(); const [newIdentity, setNewIdentity] = useState(''); - const queryClient = useQueryClient(); + + if (isLoading) return <>Loading; + if (error) return <>Error; const handleChange = event => { setNewIdentity(event.target.value); @@ -61,14 +63,12 @@ const IdentityForm = () => { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: newIdentity }) }).then(() => { - // invalidate the react-query query cache for the useGetIdentity call - queryClient.invalidateQueries(['auth', 'getIdentity']); + // call authProvider.getIdentity() again and notify the listeners of the result, + // including the UserMenu in the AppBar + refetch() }); }; - - if (isLoading) return <>Loading; - if (error) return <>Error; - + return (
diff --git a/packages/ra-core/src/auth/index.ts b/packages/ra-core/src/auth/index.ts index 20e26b702bf..fec06d41d5f 100644 --- a/packages/ra-core/src/auth/index.ts +++ b/packages/ra-core/src/auth/index.ts @@ -15,7 +15,6 @@ export * from './types'; export * from './useAuthenticated'; export * from './useCheckAuth'; export * from './useGetIdentity'; -export * from './useRefreshIdentity'; export { AuthContext, diff --git a/packages/ra-core/src/auth/useGetIdentity.stories.tsx b/packages/ra-core/src/auth/useGetIdentity.stories.tsx index 346ea414082..4a98fb62e51 100644 --- a/packages/ra-core/src/auth/useGetIdentity.stories.tsx +++ b/packages/ra-core/src/auth/useGetIdentity.stories.tsx @@ -62,23 +62,23 @@ export const ResetIdentity = () => { let fullName = 'John Doe'; const IdentityForm = () => { - const { isLoading, error, identity } = useGetIdentity(); + const { isLoading, error, identity, refetch } = useGetIdentity(); const [newIdentity, setNewIdentity] = React.useState(''); - const queryClient = useQueryClient(); + + if (isLoading) return <>Loading; + if (error) return <>Error; const handleChange = event => { setNewIdentity(event.target.value); }; + const handleSubmit = e => { e.preventDefault(); if (!newIdentity) return; fullName = newIdentity; - queryClient.invalidateQueries(['auth', 'getIdentity']); + refetch(); }; - if (isLoading) return <>Loading; - if (error) return <>Error; - return ( {}, isLoading: false } * - error: { error: Error, isLoading: false } * * The implementation is left to the authProvider. * - * @returns The current user identity. Destructure as { identity, error, isLoading }. + * @returns The current user identity. Destructure as { isLoading, identity, error, refetch }. * * @example - * * import { useGetIdentity, useGetOne } from 'react-admin'; * * const PostDetail = ({ id }) => { @@ -61,7 +60,11 @@ export const useGetIdentity = ( ? { isLoading: true } : result.error ? { error: result.error, isLoading: false } - : { identity: result.data, isLoading: false }, + : { + identity: result.data, + refetch: result.refetch, + isLoading: false, + }, [result] ); @@ -72,16 +75,19 @@ export type UseGetIdentityResult = isLoading: true; identity?: undefined; error?: undefined; + refetch?: undefined; } | { isLoading: false; identity?: undefined; error: Error; + refetch?: undefined; } | { isLoading: false; identity: UserIdentity; error?: undefined; + refetch: () => Promise>; }; export default useGetIdentity; diff --git a/packages/ra-core/src/auth/useRefreshIdentity.ts b/packages/ra-core/src/auth/useRefreshIdentity.ts deleted file mode 100644 index 9e081e254f4..00000000000 --- a/packages/ra-core/src/auth/useRefreshIdentity.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useCallback } from 'react'; -import { useQueryClient } from 'react-query'; - -/** - * Get a callback to force a refetch of the current user identity - * - * @example - * const IdentityForm = () => { - * const { isLoading, error, identity } = useGetIdentity(); - * const [newIdentity, setNewIdentity] = useState(''); - * const refresh = useRefreshIdentity(); - * const handleSubmit = (e) => { - * e.preventDefault(); - * if (!newIdentity) return; - * fetch('/update_identity', { - * method: 'POST', - * headers: { 'Content-Type': 'application/json' }, - * body: JSON.stringify({ identity: newIdentity }) - * }).then(refresh); - * }; - * if (isLoading) return <>Loading; - * if (error) return <>Error; - * return ( - * - * setNewIdentity(e.target.value)} /> - * - * - * ); - * }; - */ -export const useRefreshIdentity = () => { - const queryClient = useQueryClient(); - return useCallback(() => { - queryClient.invalidateQueries(['auth', 'getIdentity']); - }, [queryClient]); -}; From 82534c2e20f8ac6f873dcf899547c3f6574d4903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Wed, 9 Nov 2022 07:55:12 +0100 Subject: [PATCH 3/8] Fix typo --- docs/useGetIdentity.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index da107b56160..ca1e15cff70 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -48,13 +48,14 @@ If your application contains a form letting the current user update their name a const IdentityForm = () => { const { isLoading, error, identity, refetch } = useGetIdentity(); const [newIdentity, setNewIdentity] = useState(''); - + if (isLoading) return <>Loading; if (error) return <>Error; const handleChange = event => { setNewIdentity(event.target.value); }; + const handleSubmit = (e) => { e.preventDefault(); if (!newIdentity) return; @@ -65,10 +66,10 @@ const IdentityForm = () => { }).then(() => { // call authProvider.getIdentity() again and notify the listeners of the result, // including the UserMenu in the AppBar - refetch() + refetch(); }); }; - + return (
From 5d4b3972e79a4eab3b10bace8845fd9b1b642791 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Wed, 9 Nov 2022 08:58:06 +0100 Subject: [PATCH 4/8] Improve doc --- docs/useGetIdentity.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index ca1e15cff70..a95ac53fda7 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -5,10 +5,14 @@ title: "useGetIdentity" # `useGetIdentity` -You may want to use the current user name, avatar, or id in your code. for that purpose, call the `useGetIdentity()` hook, which calls `authProvider.getIdentity()` on mount. It returns an object containing the loading state, the error state, and the identity. +React-admin calls `authProvider.getIdentity()` to retrieve and display the current logged in user name and avatar. The logic for calling this method is packaged into a custom hook, `useGetIdentity`, which you can use in your own code. + +![identity](./img/identity.png) ## Syntax +`useGetIdentity()` calls `authProvider.getIdentity()` on mount. It returns an object containing the loading state, the error state, and the identity. + ```jsx const { identity, isLoading, error } = useGetIdentity(); ``` @@ -19,6 +23,8 @@ Once loaded, the `identity` object contains the following properties: const { id, fullName, avatar } = identity; ``` + `useGetIdentity` uses [react-query's `useQuery` hook](https://react-query-v3.tanstack.com/reference/useQuery) to call the `authProvider`. + ## Usage Here is an example Edit component, which falls back to a Show component if the record is locked for edition by another user: From e17371179f70e4507f2a3d55825b436f0554af82 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Wed, 9 Nov 2022 09:02:54 +0100 Subject: [PATCH 5/8] Standardize the useGetIdentity return to react-query signature --- docs/useGetIdentity.md | 12 ++++++------ .../ra-core/src/auth/useGetIdentity.stories.tsx | 17 ++++------------- packages/ra-core/src/auth/useGetIdentity.ts | 13 ++++++++++--- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index a95ac53fda7..2a8611c0cb2 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -14,16 +14,16 @@ React-admin calls `authProvider.getIdentity()` to retrieve and display the curre `useGetIdentity()` calls `authProvider.getIdentity()` on mount. It returns an object containing the loading state, the error state, and the identity. ```jsx -const { identity, isLoading, error } = useGetIdentity(); +const { data, isLoading, error } = useGetIdentity(); ``` -Once loaded, the `identity` object contains the following properties: +Once loaded, the `data` object contains the following properties: ```jsx const { id, fullName, avatar } = identity; ``` - `useGetIdentity` uses [react-query's `useQuery` hook](https://react-query-v3.tanstack.com/reference/useQuery) to call the `authProvider`. +`useGetIdentity` uses [react-query's `useQuery` hook](https://react-query-v3.tanstack.com/reference/useQuery) to call the `authProvider`. ## Usage @@ -34,7 +34,7 @@ import { useGetIdentity, useGetOne } from 'react-admin'; const PostDetail = ({ id }) => { const { data: post, isLoading: postLoading } = useGetOne('posts', { id }); - const { identity, isLoading: identityLoading } = useGetIdentity(); + const { data: identity, isLoading: identityLoading } = useGetIdentity(); if (postLoading || identityLoading) return <>Loading...; if (!post.lockedBy || post.lockedBy === identity.id) { // post isn't locked, or is locked by me @@ -52,7 +52,7 @@ If your application contains a form letting the current user update their name a ```jsx const IdentityForm = () => { - const { isLoading, error, identity, refetch } = useGetIdentity(); + const { isLoading, error, data, refetch } = useGetIdentity(); const [newIdentity, setNewIdentity] = useState(''); if (isLoading) return <>Loading; @@ -78,7 +78,7 @@ const IdentityForm = () => { return ( - + ); diff --git a/packages/ra-core/src/auth/useGetIdentity.stories.tsx b/packages/ra-core/src/auth/useGetIdentity.stories.tsx index 4a98fb62e51..bef3244484b 100644 --- a/packages/ra-core/src/auth/useGetIdentity.stories.tsx +++ b/packages/ra-core/src/auth/useGetIdentity.stories.tsx @@ -17,14 +17,8 @@ const authProvider = { }; const Identity = () => { - const { identity, error, isLoading } = useGetIdentity(); - return isLoading ? ( - <>Loading - ) : error ? ( - <>Error - ) : ( - <>{identity.fullName} - ); + const { data, error, isLoading } = useGetIdentity(); + return isLoading ? <>Loading : error ? <>Error : <>{data.fullName}; }; export const Basic = () => ( @@ -62,7 +56,7 @@ export const ResetIdentity = () => { let fullName = 'John Doe'; const IdentityForm = () => { - const { isLoading, error, identity, refetch } = useGetIdentity(); + const { isLoading, error, data, refetch } = useGetIdentity(); const [newIdentity, setNewIdentity] = React.useState(''); if (isLoading) return <>Loading; @@ -81,10 +75,7 @@ export const ResetIdentity = () => { return (
- +
); diff --git a/packages/ra-core/src/auth/useGetIdentity.ts b/packages/ra-core/src/auth/useGetIdentity.ts index c73c3ca36ce..efa3096d856 100644 --- a/packages/ra-core/src/auth/useGetIdentity.ts +++ b/packages/ra-core/src/auth/useGetIdentity.ts @@ -18,19 +18,19 @@ const defaultQueryParams = { * The return value updates according to the call state: * * - mount: { isLoading: true } - * - success: { identity: Identity, refetch: () => {}, isLoading: false } + * - success: { data: Identity, refetch: () => {}, isLoading: false } * - error: { error: Error, isLoading: false } * * The implementation is left to the authProvider. * - * @returns The current user identity. Destructure as { isLoading, identity, error, refetch }. + * @returns The current user identity. Destructure as { isLoading, data, error, refetch }. * * @example * import { useGetIdentity, useGetOne } from 'react-admin'; * * const PostDetail = ({ id }) => { * const { data: post, isLoading: postLoading } = useGetOne('posts', { id }); - * const { identity, isLoading: identityLoading } = useGetIdentity(); + * const { data: identity, isLoading: identityLoading } = useGetIdentity(); * if (postLoading || identityLoading) return <>Loading...; * if (!post.lockedBy || post.lockedBy === identity.id) { * // post isn't locked, or is locked by me @@ -61,6 +61,7 @@ export const useGetIdentity = ( : result.error ? { error: result.error, isLoading: false } : { + data: result.data, identity: result.data, refetch: result.refetch, isLoading: false, @@ -73,18 +74,24 @@ export const useGetIdentity = ( 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>; From 6e16fe1dd445f57dab97c89a4f57b5b1ca45a535 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Wed, 9 Nov 2022 09:05:46 +0100 Subject: [PATCH 6/8] Add mention about future cleanup --- packages/ra-core/src/auth/useGetIdentity.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ra-core/src/auth/useGetIdentity.ts b/packages/ra-core/src/auth/useGetIdentity.ts index efa3096d856..a29255e6903 100644 --- a/packages/ra-core/src/auth/useGetIdentity.ts +++ b/packages/ra-core/src/auth/useGetIdentity.ts @@ -54,6 +54,7 @@ export const useGetIdentity = ( queryParams ); + // @FIXME: return useQuery's result directly by removing identity prop (BC break - to be done in v5) return useMemo( () => result.isLoading From 29812ca7d94fcaab9e22656dcadda13eafd2dac8 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Wed, 9 Nov 2022 14:27:39 +0100 Subject: [PATCH 7/8] Update docs/useGetIdentity.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: AnĂ­bal Svarcas --- docs/useGetIdentity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index 2a8611c0cb2..8b00f49c191 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -5,7 +5,7 @@ title: "useGetIdentity" # `useGetIdentity` -React-admin calls `authProvider.getIdentity()` to retrieve and display the current logged in user name and avatar. The logic for calling this method is packaged into a custom hook, `useGetIdentity`, which you can use in your own code. +React-admin calls `authProvider.getIdentity()` to retrieve and display the current logged-in username and avatar. The logic for calling this method is packaged into a custom hook, `useGetIdentity`, which you can use in your own code. ![identity](./img/identity.png) From f6c65619f420d7d7ab771aae8e61b0507f646986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Zaninotto?= Date: Mon, 14 Nov 2022 10:01:26 +0100 Subject: [PATCH 8/8] Review --- docs/useGetIdentity.md | 2 +- packages/ra-core/src/auth/useGetIdentity.spec.tsx | 2 +- packages/ra-core/src/auth/useGetIdentity.stories.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/useGetIdentity.md b/docs/useGetIdentity.md index 8b00f49c191..e25673c59b3 100644 --- a/docs/useGetIdentity.md +++ b/docs/useGetIdentity.md @@ -20,7 +20,7 @@ const { data, isLoading, error } = useGetIdentity(); Once loaded, the `data` object contains the following properties: ```jsx -const { id, fullName, avatar } = identity; +const { id, fullName, avatar } = data; ``` `useGetIdentity` uses [react-query's `useQuery` hook](https://react-query-v3.tanstack.com/reference/useQuery) to call the `authProvider`. diff --git a/packages/ra-core/src/auth/useGetIdentity.spec.tsx b/packages/ra-core/src/auth/useGetIdentity.spec.tsx index f14429a88ce..ca85b34dc1c 100644 --- a/packages/ra-core/src/auth/useGetIdentity.spec.tsx +++ b/packages/ra-core/src/auth/useGetIdentity.spec.tsx @@ -13,7 +13,7 @@ describe('useGetIdentity', () => { render(); await screen.findByText('Error'); }); - it('should allow to invalidate the query cache', async () => { + it('should allow to update the identity after a change', async () => { render(); expect(await screen.findByText('John Doe')).not.toBeNull(); const input = screen.getByDisplayValue('John Doe'); diff --git a/packages/ra-core/src/auth/useGetIdentity.stories.tsx b/packages/ra-core/src/auth/useGetIdentity.stories.tsx index bef3244484b..9e80e008dbc 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, useQueryClient } from 'react-query'; +import { QueryClientProvider, QueryClient } from 'react-query'; import { useGetIdentity } from './useGetIdentity'; import AuthContext from './AuthContext';