Skip to content

Commit a0aa570

Browse files
authoredDec 1, 2020
Merge pull request #5607 from marmelab/fix-usePermissions-rerender
Fix usePermissions always triggers a re-render even though the permissions are unchanged
2 parents 0e8ba40 + 6d58053 commit a0aa570

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed
 

‎packages/ra-core/src/auth/WithPermissions.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Location } from 'history';
99

1010
import warning from '../util/warning';
1111
import useAuthenticated from './useAuthenticated';
12-
import usePermissions from './usePermissions';
12+
import usePermissionsOptimized from './usePermissionsOptimized';
1313

1414
export interface WithPermissionsChildrenParams {
1515
permissions: any;
@@ -81,7 +81,7 @@ const WithPermissions: FunctionComponent<Props> = ({
8181
);
8282

8383
useAuthenticated(authParams);
84-
const { permissions } = usePermissions(authParams);
84+
const { permissions } = usePermissionsOptimized(authParams);
8585
// render even though the usePermissions() call isn't finished (optimistic rendering)
8686
if (component) {
8787
return createElement(component, { permissions, ...props });

‎packages/ra-core/src/auth/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import AuthContext from './AuthContext';
33
import useAuthProvider from './useAuthProvider';
44
import useAuthState from './useAuthState';
55
import usePermissions from './usePermissions';
6+
import usePermissionsOptimized from './usePermissionsOptimized';
67
import useAuthenticated from './useAuthenticated';
78
import WithPermissions from './WithPermissions';
89
import useLogin from './useLogin';
@@ -26,6 +27,7 @@ export {
2627
useGetPermissions,
2728
// hooks with state management
2829
usePermissions,
30+
usePermissionsOptimized,
2931
useAuthState,
3032
// hook with immediate effect
3133
useAuthenticated,

‎packages/ra-core/src/auth/usePermissions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const emptyParams = {};
1515
/**
1616
* Hook for getting user permissions
1717
*
18-
* Calls the authProvider.getPrmissions() method asynchronously.
18+
* Calls the authProvider.getPermissions() method asynchronously.
1919
* If the authProvider returns a rejected promise, returns empty permissions.
2020
*
2121
* The return value updates according to the request state:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useEffect } from 'react';
2+
import isEqual from 'lodash/isEqual';
3+
4+
import useGetPermissions from './useGetPermissions';
5+
import { useSafeSetState } from '../util/hooks';
6+
7+
interface State {
8+
permissions?: any;
9+
error?: any;
10+
}
11+
12+
const emptyParams = {};
13+
14+
// keep a cache of already fetched permissions to initialize state for new
15+
// components and avoid a useless rerender if the permissions haven't changed
16+
const alreadyFetchedPermissions = { '{}': [] };
17+
18+
/**
19+
* Hook for getting user permissions without the loading state.
20+
*
21+
* When compared to usePermissions, this hook doesn't cause a re-render
22+
* when the permissions haven't changed since the last call.
23+
*
24+
* This hook doesn't handle the loading state.
25+
*
26+
* @see usePermissions
27+
*
28+
* Calls the authProvider.getPermissions() method asynchronously.
29+
* If the authProvider returns a rejected promise, returns empty permissions.
30+
*
31+
* The return value updates according to the request state:
32+
*
33+
* - start: { permissions: [previously fetched permissions for these params] }
34+
* - success: { permissions: [permissions returned by the authProvider (usually the same as on start)] }
35+
* - error: { error: [error from provider] }
36+
*
37+
* Useful to enable features based on user permissions
38+
*
39+
* @param {Object} params Any params you want to pass to the authProvider
40+
*
41+
* @returns The current auth check state. Destructure as { permissions, error }.
42+
*
43+
* @example
44+
* import { usePermissionsOptimized } from 'react-admin';
45+
*
46+
* const PostDetail = props => {
47+
* const { permissions } = usePermissionsOptimized();
48+
* if (permissions !== 'editor') {
49+
* return <Redirect to={`posts/${props.id}/show`} />
50+
* } else {
51+
* return <PostEdit {...props} />
52+
* }
53+
* };
54+
*/
55+
const usePermissionsOptimized = (params = emptyParams) => {
56+
const [state, setState] = useSafeSetState<State>({
57+
permissions: alreadyFetchedPermissions[JSON.stringify(params)],
58+
});
59+
const getPermissions = useGetPermissions();
60+
useEffect(() => {
61+
getPermissions(params)
62+
.then(permissions => {
63+
const key = JSON.stringify(params);
64+
if (!isEqual(permissions, alreadyFetchedPermissions[key])) {
65+
alreadyFetchedPermissions[key] = permissions;
66+
setState({ permissions });
67+
}
68+
})
69+
.catch(error => {
70+
setState({
71+
error,
72+
});
73+
});
74+
}, [getPermissions, params, setState]);
75+
76+
return state;
77+
};
78+
79+
export default usePermissionsOptimized;

0 commit comments

Comments
 (0)
Please sign in to comment.