diff --git a/UPGRADE.md b/UPGRADE.md
index b5739bec4e4..a1c6243708c 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -640,6 +640,17 @@ test('MyComponent', () => {
});
```
+## `useAuthenticated` Signature hsa Changed
+
+`useAuthenticated` uses to accept only the parameters passed to the `authProvider.checkAuth` function. It now accept an option object with two properties:
+- `enabled`: whether it should check for an authenticated user
+- `params`: the parameters to pass to `checkAuth`
+
+```diff
+- useAuthenticated('permissions.posts.can_create');
++ useAuthenticated({ params: 'permissions.posts.can_create' })
+```
+
# Upgrade to 3.0
We took advantage of the major release to fix all the problems in react-admin that required a breaking change. As a consequence, you'll need to do many small changes in the code of existing react-admin v2 applications. Follow this step-by-step guide to upgrade to react-admin v3.
diff --git a/docs/Authentication.md b/docs/Authentication.md
index 4c125553a52..9667c0c3a76 100644
--- a/docs/Authentication.md
+++ b/docs/Authentication.md
@@ -701,11 +701,15 @@ const MyPage = () => {
export default MyPage;
```
-If you call `useAuthenticated()` with a parameter, this parameter is passed to the `authProvider` call as second parameter. that allows you to add authentication logic depending on the context of the call:
+`useAuthenticated` accepts an options object as its only argument, with the following properties:
+- `enabled`: whether it should check for an authenticated user (`true` by default)
+- `params`: the parameters to pass to `checkAuth`
+
+If you call `useAuthenticated()` with a `params` option, those parameters are passed to the `authProvider.checkAuth` call. That allows you to add authentication logic depending on the context of the call:
```jsx
const MyPage = () => {
- useAuthenticated({ foo: 'bar' }); // calls authProvider.checkAuth({ foo: 'bar' })
+ useAuthenticated({ params: foo: 'bar' } }); // calls authProvider.checkAuth({ foo: 'bar' })
return (
...
@@ -1165,3 +1169,31 @@ const Menu = ({ onMenuClick, logout }) => {
);
}
```
+
+### Allowing Anonymous Access to Custom Views
+
+You might have custom views that leverage react-admin components or hooks such as:
+
+- `Create`, `CreateBase` `CreateController` and `useCreateController`
+- `Edit`, `EditBase`, `EditController` and `useEditController`
+- `List`, `ListBase`, `ListController` and `useListController`
+- `Show`, `ShowBase`, `ShowController` and `useShowController`
+
+By default, they all redirect anonymous users to the login page. You can disable this behavior by passing the `disableAuthentication` boolean prop:
+
+```jsx
+const MostRecentComments = () => {
+ const { data, loaded } = useListController({
+ disableAuthentication: true,
+ resource: 'comments',
+ sort: { field: 'created_at', order: 'desc' },
+ perPage: 10
+ });
+
+ if (!loaded) {
+ return null;
+ }
+
+ return
+}
+```
diff --git a/examples/simple/src/users/UserCreate.tsx b/examples/simple/src/users/UserCreate.tsx
index 63d6765502b..18806c8d061 100644
--- a/examples/simple/src/users/UserCreate.tsx
+++ b/examples/simple/src/users/UserCreate.tsx
@@ -9,6 +9,7 @@ import {
TextInput,
Toolbar,
required,
+ usePermissions,
} from 'react-admin';
import Aside from './Aside';
@@ -38,32 +39,35 @@ const isValidName = async value =>
)
);
-const UserCreate = ({ permissions, ...props }) => (
-
}>
-
}>
-
-
-
- {permissions === 'admin' && (
-
- {
+ const { permissions } = usePermissions();
+ return (
+ }>
+ }>
+
+
- )}
-
-
-);
+ {permissions === 'admin' && (
+
+
+
+ )}
+
+
+ );
+};
export default UserCreate;
diff --git a/package.json b/package.json
index 54f85a2e30e..64eae7d3e4b 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
- "eslint-plugin-react-hooks": "^4.1.0",
+ "eslint-plugin-react-hooks": "^4.3.0",
"express": "~4.16.3",
"full-icu": "~1.3.1",
"husky": "^2.3.0",
diff --git a/packages/ra-core/src/auth/Authenticated.tsx b/packages/ra-core/src/auth/Authenticated.tsx
index 417000986ed..99d34b396bd 100644
--- a/packages/ra-core/src/auth/Authenticated.tsx
+++ b/packages/ra-core/src/auth/Authenticated.tsx
@@ -1,6 +1,6 @@
import { cloneElement, ReactElement } from 'react';
-import useAuthenticated from './useAuthenticated';
+import { useAuthenticated } from './useAuthenticated';
export interface AuthenticatedProps {
children: ReactElement;
@@ -43,7 +43,7 @@ const Authenticated = (props: AuthenticatedProps) => {
location, // kept for backwards compatibility, unused
...rest
} = props;
- useAuthenticated(authParams);
+ useAuthenticated({ params: authParams });
// render the child even though the useAuthenticated() call isn't finished (optimistic rendering)
// the above hook will log out if the authProvider doesn't validate that the user is authenticated
return cloneElement(children, rest);
diff --git a/packages/ra-core/src/auth/WithPermissions.tsx b/packages/ra-core/src/auth/WithPermissions.tsx
index 69ec032aec6..04461aba66e 100644
--- a/packages/ra-core/src/auth/WithPermissions.tsx
+++ b/packages/ra-core/src/auth/WithPermissions.tsx
@@ -2,7 +2,7 @@ import { Children, ReactElement, ComponentType, createElement } from 'react';
import { Location } from 'history';
import warning from '../util/warning';
-import useAuthenticated from './useAuthenticated';
+import { useAuthenticated } from './useAuthenticated';
import usePermissionsOptimized from './usePermissionsOptimized';
export interface WithPermissionsChildrenParams {
diff --git a/packages/ra-core/src/auth/index.ts b/packages/ra-core/src/auth/index.ts
index 807c1afe08c..a21f211ee59 100644
--- a/packages/ra-core/src/auth/index.ts
+++ b/packages/ra-core/src/auth/index.ts
@@ -4,16 +4,17 @@ import useAuthProvider from './useAuthProvider';
import useAuthState from './useAuthState';
import usePermissions from './usePermissions';
import usePermissionsOptimized from './usePermissionsOptimized';
-import useAuthenticated from './useAuthenticated';
import WithPermissions, { WithPermissionsProps } from './WithPermissions';
import useLogin from './useLogin';
import useLogout from './useLogout';
-import useCheckAuth from './useCheckAuth';
import useGetIdentity from './useGetIdentity';
import useGetPermissions from './useGetPermissions';
import useLogoutIfAccessDenied from './useLogoutIfAccessDenied';
import convertLegacyAuthProvider from './convertLegacyAuthProvider';
+
export * from './types';
+export * from './useAuthenticated';
+export * from './useCheckAuth';
export {
AuthContext,
@@ -22,7 +23,6 @@ export {
// low-level hooks for calling a particular verb on the authProvider
useLogin,
useLogout,
- useCheckAuth,
useGetIdentity,
useGetPermissions,
// hooks with state management
@@ -30,7 +30,6 @@ export {
usePermissionsOptimized,
useAuthState,
// hook with immediate effect
- useAuthenticated,
useLogoutIfAccessDenied,
// components
Authenticated,
diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts
index 6e25e272a2e..b3bfa9e87c8 100644
--- a/packages/ra-core/src/auth/useAuthState.ts
+++ b/packages/ra-core/src/auth/useAuthState.ts
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
-import useCheckAuth from './useCheckAuth';
+import { useCheckAuth } from './useCheckAuth';
import { useSafeSetState } from '../util/hooks';
interface State {
diff --git a/packages/ra-core/src/auth/useAuthenticated.tsx b/packages/ra-core/src/auth/useAuthenticated.ts
similarity index 63%
rename from packages/ra-core/src/auth/useAuthenticated.tsx
rename to packages/ra-core/src/auth/useAuthenticated.ts
index dd6f781acb8..75fa3cfe605 100644
--- a/packages/ra-core/src/auth/useAuthenticated.tsx
+++ b/packages/ra-core/src/auth/useAuthenticated.ts
@@ -1,7 +1,5 @@
import { useEffect } from 'react';
-import useCheckAuth from './useCheckAuth';
-
-const emptyParams = {};
+import { useCheckAuth } from './useCheckAuth';
/**
* Restrict access to authenticated users.
@@ -28,9 +26,21 @@ const emptyParams = {};
*
* );
*/
-export default (params: any = emptyParams) => {
+export const useAuthenticated = (
+ options: UseAuthenticatedOptions = {}
+) => {
+ const { enabled = true, params = emptyParams } = options;
const checkAuth = useCheckAuth();
useEffect(() => {
- checkAuth(params).catch(() => {});
- }, [checkAuth, params]);
+ if (enabled) {
+ checkAuth(params).catch(() => {});
+ }
+ }, [checkAuth, enabled, params]);
+};
+
+export type UseAuthenticatedOptions = {
+ enabled?: boolean;
+ params?: ParamsType;
};
+
+const emptyParams = {};
diff --git a/packages/ra-core/src/auth/useCheckAuth.spec.tsx b/packages/ra-core/src/auth/useCheckAuth.spec.tsx
index 6b4fcd8d422..c609dcd9966 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 { render, waitFor } from '@testing-library/react';
-import useCheckAuth from './useCheckAuth';
+import { useCheckAuth } from './useCheckAuth';
import AuthContext from './AuthContext';
import useLogout from './useLogout';
import useNotify from '../sideEffect/useNotify';
diff --git a/packages/ra-core/src/auth/useCheckAuth.ts b/packages/ra-core/src/auth/useCheckAuth.ts
index fe8e695db06..4f8b5319a8e 100644
--- a/packages/ra-core/src/auth/useCheckAuth.ts
+++ b/packages/ra-core/src/auth/useCheckAuth.ts
@@ -40,7 +40,7 @@ import useNotify from '../sideEffect/useNotify';
* return authenticated ? : ;
* } // tip: use useAuthState() hook instead
*/
-const useCheckAuth = (): CheckAuth => {
+export const useCheckAuth = (): CheckAuth => {
const authProvider = useAuthProvider();
const notify = useNotify();
const logout = useLogout();
@@ -90,7 +90,7 @@ const checkAuthWithoutAuthProvider = () => Promise.resolve();
*
* @return {Promise} Resolved to the authProvider response if the user passes the check, or rejected with an error otherwise
*/
-type CheckAuth = (
+export type CheckAuth = (
params?: any,
logoutOnFailure?: boolean,
redirectTo?: string,
@@ -104,5 +104,3 @@ const getErrorMessage = (error, defaultMessage) =>
: typeof error === 'undefined' || !error.message
? defaultMessage
: error.message;
-
-export default useCheckAuth;
diff --git a/packages/ra-core/src/controller/create/index.ts b/packages/ra-core/src/controller/create/index.ts
index 0a6dddeb990..246c389caaa 100644
--- a/packages/ra-core/src/controller/create/index.ts
+++ b/packages/ra-core/src/controller/create/index.ts
@@ -1,18 +1,6 @@
-import {
- CreateControllerResult,
- useCreateController,
-} from './useCreateController';
-
export * from './CreateBase';
export * from './CreateContext';
export * from './CreateContextProvider';
export * from './CreateController';
-
export * from './useCreateContext';
-export * from '../show/useShowContext';
-
-// We don't want to export CreateProps and EditProps as they should
-// not be used outside ra-core, since it would conflict with ra-ui-materialui types,
-// hence the named imports/exports
-export type { CreateControllerResult as CreateControllerProps };
-export { useCreateController };
+export * from './useCreateController';
diff --git a/packages/ra-core/src/controller/create/useCreateController.spec.tsx b/packages/ra-core/src/controller/create/useCreateController.spec.tsx
index e1042b894bc..58d9c821fe1 100644
--- a/packages/ra-core/src/controller/create/useCreateController.spec.tsx
+++ b/packages/ra-core/src/controller/create/useCreateController.spec.tsx
@@ -1,60 +1,49 @@
import React from 'react';
import expect from 'expect';
import { act } from '@testing-library/react';
+import { Location } from 'history';
-import { getRecord } from './useCreateController';
+import { getRecordFromLocation } from './useCreateController';
import { CreateController } from './CreateController';
import { renderWithRedux } from 'ra-test';
import { DataProviderContext } from '../../dataProvider';
import { DataProvider } from '../../types';
describe('useCreateController', () => {
- describe('getRecord', () => {
- const location = {
+ describe('getRecordFromLocation', () => {
+ const location: Location = {
+ key: 'a_key',
pathname: '/foo',
search: undefined,
state: undefined,
hash: undefined,
};
- it('should return an empty record by default', () => {
- expect(getRecord(location, undefined)).toEqual({});
- });
-
it('should return location state record when set', () => {
expect(
- getRecord(
- {
- ...location,
- state: { record: { foo: 'bar' } },
- },
- undefined
- )
+ getRecordFromLocation({
+ ...location,
+ state: { record: { foo: 'bar' } },
+ })
).toEqual({ foo: 'bar' });
});
it('should return location search when set', () => {
expect(
- getRecord(
- {
- ...location,
- search: '?source={"foo":"baz","array":["1","2"]}',
- },
- undefined
- )
+ getRecordFromLocation({
+ ...location,
+ search: '?source={"foo":"baz","array":["1","2"]}',
+ })
).toEqual({ foo: 'baz', array: ['1', '2'] });
});
it('should return location state record when both state and search are set', () => {
expect(
- getRecord(
- {
- ...location,
- state: { record: { foo: 'bar' } },
- search: '?foo=baz',
- },
- undefined
- )
+ getRecordFromLocation({
+ ...location,
+ state: { record: { foo: 'bar' } },
+ search: '?foo=baz',
+ })
).toEqual({ foo: 'bar' });
});
});
diff --git a/packages/ra-core/src/controller/create/useCreateController.ts b/packages/ra-core/src/controller/create/useCreateController.ts
index 8300e1862b6..e6572bba17d 100644
--- a/packages/ra-core/src/controller/create/useCreateController.ts
+++ b/packages/ra-core/src/controller/create/useCreateController.ts
@@ -2,7 +2,9 @@ import { useCallback, MutableRefObject } from 'react';
// @ts-ignore
import { parse } from 'query-string';
import { useLocation } from 'react-router-dom';
+import { Location } from 'history';
+import { useAuthenticated } from '../../auth';
import { useCreate } from '../../dataProvider';
import {
useNotify,
@@ -49,20 +51,23 @@ export const useCreateController = <
props: CreateControllerProps = {}
): CreateControllerResult => {
const {
- record = {},
- successMessage,
+ disableAuthentication,
onSuccess,
onFailure,
+ record,
+ successMessage,
transform,
} = props;
+ useAuthenticated({ enabled: !disableAuthentication });
const resource = useResourceContext(props);
const { hasEdit, hasShow } = useResourceDefinition(props);
const location = useLocation();
const translate = useTranslate();
const notify = useNotify();
const redirect = useRedirect();
- const recordToUse = getRecord(location, record);
+ const recordToUse =
+ record ?? getRecordFromLocation(location) ?? emptyRecord;
const version = useVersion();
if (process.env.NODE_ENV !== 'production' && successMessage) {
@@ -190,12 +195,13 @@ export const useCreateController = <
export interface CreateControllerProps<
RecordType extends Omit = Record
> {
+ disableAuthentication?: boolean;
record?: Partial;
resource?: string;
onSuccess?: OnSuccess;
onFailure?: OnFailure;
- transform?: TransformData;
successMessage?: string;
+ transform?: TransformData;
}
export interface CreateControllerResult<
@@ -230,7 +236,12 @@ export interface CreateControllerResult<
version: number;
}
-export const getRecord = ({ state, search }, record: any = {}) => {
+const emptyRecord = {};
+/**
+ * Get the initial record from the location, whether it comes from the location
+ * state or is serialized in the url search part.
+ */
+export const getRecordFromLocation = ({ state, search }: Location) => {
if (state && state.record) {
return state.record;
}
@@ -252,7 +263,7 @@ export const getRecord = ({ state, search }, record: any = {}) => {
);
}
}
- return record;
+ return null;
};
const getDefaultRedirectRoute = (hasShow, hasEdit) => {
diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts
index 8a9adfcae45..9993f27fd3f 100644
--- a/packages/ra-core/src/controller/edit/useEditController.ts
+++ b/packages/ra-core/src/controller/edit/useEditController.ts
@@ -2,6 +2,7 @@ import { useCallback, MutableRefObject } from 'react';
import { useParams } from 'react-router-dom';
import { UseQueryOptions, UseMutationOptions } from 'react-query';
+import { useAuthenticated } from '../../auth';
import useVersion from '../useVersion';
import {
Record,
@@ -54,12 +55,14 @@ export const useEditController = (
props: EditControllerProps = {}
): EditControllerResult => {
const {
+ disableAuthentication,
id: propsId,
mutationMode = 'undoable',
transform,
queryOptions = {},
mutationOptions = {},
} = props;
+ useAuthenticated({ enabled: !disableAuthentication });
const resource = useResourceContext(props);
const translate = useTranslate();
const notify = useNotify();
@@ -214,17 +217,18 @@ export const useEditController = (
};
export interface EditControllerProps {
+ disableAuthentication?: boolean;
id?: Identifier;
- resource?: string;
mutationMode?: MutationMode;
- queryOptions?: UseQueryOptions;
mutationOptions?: UseMutationOptions<
RecordType,
unknown,
UpdateParams
>;
- onSuccess?: OnSuccess;
onFailure?: OnFailure;
+ onSuccess?: OnSuccess;
+ queryOptions?: UseQueryOptions;
+ resource?: string;
transform?: TransformData;
[key: string]: any;
}
diff --git a/packages/ra-core/src/controller/list/useListController.ts b/packages/ra-core/src/controller/list/useListController.ts
index 6c90dafc31c..ed753bfcecb 100644
--- a/packages/ra-core/src/controller/list/useListController.ts
+++ b/packages/ra-core/src/controller/list/useListController.ts
@@ -1,6 +1,7 @@
import { isValidElement, ReactElement, useEffect, useMemo } from 'react';
import { Location } from 'history';
+import { useAuthenticated } from '../../auth';
import { useTranslate } from '../../i18n';
import { useNotify } from '../../sideEffect';
import { useGetMainList, Refetch } from '../../dataProvider';
@@ -44,6 +45,7 @@ export const useListController = (
props: ListControllerProps = {}
): ListControllerResult => {
const {
+ disableAuthentication,
exporter = defaultExporter,
filterDefaultValues,
sort = defaultSort,
@@ -52,6 +54,7 @@ export const useListController = (
debounce = 500,
disableSyncWithLocation,
} = props;
+ useAuthenticated({ enabled: !disableAuthentication });
const resource = useResourceContext(props);
const { hasCreate } = useResourceDefinition(props);
@@ -181,6 +184,7 @@ export const useListController = (
};
export interface ListControllerProps {
+ disableAuthentication?: boolean;
// the props you can change
filter?: FilterPayload;
filters?: ReactElement | ReactElement[];
diff --git a/packages/ra-core/src/controller/show/useShowController.ts b/packages/ra-core/src/controller/show/useShowController.ts
index da0fff8f12f..c36d7bc10c6 100644
--- a/packages/ra-core/src/controller/show/useShowController.ts
+++ b/packages/ra-core/src/controller/show/useShowController.ts
@@ -1,6 +1,7 @@
import { useParams } from 'react-router-dom';
import { UseQueryOptions } from 'react-query';
+import { useAuthenticated } from '../../auth';
import useVersion from '../useVersion';
import { Record, Identifier } from '../../types';
import { useGetOne, Refetch } from '../../dataProvider';
@@ -43,7 +44,10 @@ import { useResourceContext, useGetResourceLabel } from '../../core';
export const useShowController = (
props: ShowControllerProps = {}
): ShowControllerResult => {
- const { id: propsId, queryOptions = {} } = props;
+ const { disableAuthentication, id: propsId, queryOptions = {} } = props;
+
+ useAuthenticated({ enabled: !disableAuthentication });
+
const resource = useResourceContext(props);
const translate = useTranslate();
const notify = useNotify();
@@ -94,6 +98,7 @@ export const useShowController = (
};
export interface ShowControllerProps {
+ disableAuthentication?: boolean;
id?: Identifier;
queryOptions?: UseQueryOptions;
resource?: string;
diff --git a/packages/ra-core/src/core/Resource.spec.tsx b/packages/ra-core/src/core/Resource.spec.tsx
index 3b8f21baaf6..20e3b7c786f 100644
--- a/packages/ra-core/src/core/Resource.spec.tsx
+++ b/packages/ra-core/src/core/Resource.spec.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import expect from 'expect';
-import { render, screen, waitFor } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import CoreAdminContext from './CoreAdminContext';
@@ -46,36 +46,4 @@ describe('', () => {
history.push('/create');
expect(screen.getByText('PostCreate')).not.toBeNull();
});
- it('injects permissions to the resource routes', async () => {
- const history = createMemoryHistory();
- const authProvider = {
- login: jest.fn().mockResolvedValue(''),
- logout: jest.fn().mockResolvedValue(''),
- checkAuth: jest.fn().mockResolvedValue(''),
- checkError: jest.fn().mockResolvedValue(''),
- getPermissions: jest.fn().mockResolvedValue('admin'),
- };
-
- const { getByText } = render(
-
- (
- Permissions: {permissions}
- )}
- />
-
- );
- // 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('/');
- await waitFor(() => {
- expect(getByText('Permissions: admin')).not.toBeNull();
- });
- });
});
diff --git a/packages/ra-core/src/core/Resource.tsx b/packages/ra-core/src/core/Resource.tsx
index e43b85f3756..715525df657 100644
--- a/packages/ra-core/src/core/Resource.tsx
+++ b/packages/ra-core/src/core/Resource.tsx
@@ -1,45 +1,20 @@
import * as React from 'react';
-import { useMemo } from 'react';
import { Route, Routes } from 'react-router-dom';
-import WithPermissions from '../auth/WithPermissions';
import { ResourceProps } from '../types';
import { ResourceContextProvider } from './ResourceContextProvider';
export const Resource = (props: ResourceProps) => {
- const { name, list, create, edit, show } = props;
+ const { create: Create, edit: Edit, list: List, name, show: Show } = props;
- // match tends to change even on the same route ; using memo to avoid an extra render
- return useMemo(() => {
- return (
-
-
- {create && (
- }
- />
- )}
- {show && (
- }
- />
- )}
- {edit && (
- }
- />
- )}
- {list && (
- }
- />
- )}
-
-
- );
- }, [name, create, edit, list, show]);
+ return (
+
+
+ {Create && } />}
+ {Show && } />}
+ {Edit && } />}
+ {List && } />}
+
+
+ );
};
diff --git a/packages/ra-ui-materialui/src/detail/Create.tsx b/packages/ra-ui-materialui/src/detail/Create.tsx
index a9304c0e565..d107050228e 100644
--- a/packages/ra-ui-materialui/src/detail/Create.tsx
+++ b/packages/ra-ui-materialui/src/detail/Create.tsx
@@ -3,6 +3,7 @@ import { ReactElement } from 'react';
import PropTypes from 'prop-types';
import {
CreateContextProvider,
+ Record,
ResourceContextProvider,
useCheckMinimumRequiredProps,
useCreateController,
@@ -54,11 +55,11 @@ import { CreateView } from './CreateView';
* );
* export default App;
*/
-export const Create = (
- props: CreateProps & { children: ReactElement }
+export const Create = (
+ props: CreateProps & { children: ReactElement }
): ReactElement => {
useCheckMinimumRequiredProps('Create', ['children'], props);
- const controllerProps = useCreateController(props);
+ const controllerProps = useCreateController(props);
const body = (
diff --git a/packages/ra-ui-materialui/src/detail/CreateView.tsx b/packages/ra-ui-materialui/src/detail/CreateView.tsx
index fc67475775d..fbcf445387f 100644
--- a/packages/ra-ui-materialui/src/detail/CreateView.tsx
+++ b/packages/ra-ui-materialui/src/detail/CreateView.tsx
@@ -98,7 +98,11 @@ CreateView.propTypes = {
hasList: PropTypes.bool,
hasShow: PropTypes.bool,
record: PropTypes.object,
- redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
+ redirect: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ PropTypes.func,
+ ]),
resource: PropTypes.string,
save: PropTypes.func,
title: PropTypes.node,
diff --git a/packages/ra-ui-materialui/src/detail/EditView.tsx b/packages/ra-ui-materialui/src/detail/EditView.tsx
index cb1ab4dc817..1bfd881d955 100644
--- a/packages/ra-ui-materialui/src/detail/EditView.tsx
+++ b/packages/ra-ui-materialui/src/detail/EditView.tsx
@@ -125,7 +125,11 @@ EditView.propTypes = {
hasShow: PropTypes.bool,
mutationMode: PropTypes.oneOf(['pessimistic', 'optimistic', 'undoable']),
record: PropTypes.object,
- redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
+ redirect: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ PropTypes.func,
+ ]),
resource: PropTypes.string,
save: PropTypes.func,
title: PropTypes.node,
diff --git a/packages/ra-ui-materialui/src/types.ts b/packages/ra-ui-materialui/src/types.ts
index 7b506a999d2..0aab3499051 100644
--- a/packages/ra-ui-materialui/src/types.ts
+++ b/packages/ra-ui-materialui/src/types.ts
@@ -9,6 +9,7 @@ import {
MutationMode,
OnSuccess,
OnFailure,
+ TransformData,
UpdateParams,
} from 'ra-core';
import { UseQueryOptions, UseMutationOptions } from 'react-query';
@@ -48,21 +49,21 @@ export interface EditProps {
UpdateParams
>;
resource?: string;
- transform?: (data: RaRecord) => RaRecord | Promise;
+ transform?: TransformData;
title?: string | ReactElement;
}
-export interface CreateProps {
+export interface CreateProps {
actions?: ReactElement | false;
aside?: ReactElement;
classes?: any;
className?: string;
component?: ElementType;
- record?: Partial;
+ record?: Partial;
resource?: string;
onSuccess?: OnSuccess;
onFailure?: OnFailure;
- transform?: (data: RaRecord) => RaRecord | Promise;
+ transform?: TransformData;
title?: string | ReactElement;
}
diff --git a/yarn.lock b/yarn.lock
index 75e33cce15f..836cdc80a8c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11619,16 +11619,16 @@ eslint-plugin-prettier@^3.1.4:
dependencies:
prettier-linter-helpers "^1.0.0"
-eslint-plugin-react-hooks@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.0.tgz#6323fbd5e650e84b2987ba76370523a60f4e7925"
- integrity sha512-36zilUcDwDReiORXmcmTc6rRumu9JIM3WjSvV0nclHoUQ0CNrX866EwONvLR/UqaeqFutbAnVu8PEmctdo2SRQ==
-
eslint-plugin-react-hooks@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==
+eslint-plugin-react-hooks@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172"
+ integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==
+
eslint-plugin-react@^7.20.6:
version "7.20.6"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz#4d7845311a93c463493ccfa0a19c9c5d0fd69f60"