Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix warning when passing partial props to useListContext and other view context hooks #5802

Merged
merged 8 commits into from
Feb 13, 2021
30 changes: 23 additions & 7 deletions examples/simple/src/customRouteLayout.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import * as React from 'react';
import { useGetList, useAuthenticated, Title } from 'react-admin';
import {
useGetList,
useAuthenticated,
Datagrid,
TextField,
Title,
} from 'react-admin';

const currentSort = { field: 'published_at', order: 'DESC' };

const CustomRouteLayout = () => {
useAuthenticated();

const { ids, data, total, loaded } = useGetList(
'posts',
{ page: 1, perPage: 10 },
{ field: 'published_at', order: 'DESC' }
currentSort
);

return loaded ? (
Expand All @@ -16,11 +25,18 @@ const CustomRouteLayout = () => {
<p>
Found <span className="total">{total}</span> posts !
</p>
<ul>
{ids.map(id => (
<li key={id}>{data[id].title}</li>
))}
</ul>
<Datagrid
basePath=""
currentSort={currentSort}
data={data}
ids={ids}
selectedIds={[]}
loaded={loaded}
total={total}
>
<TextField source="id" sortable={false} />
<TextField source="title" sortable={false} />
</Datagrid>
</div>
) : null;
};
Expand Down
94 changes: 75 additions & 19 deletions packages/ra-core/src/controller/details/useCreateContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import merge from 'lodash/merge';

import { Record } from '../../types';
import { CreateContext } from './CreateContext';
import { CreateControllerProps } from './useCreateController';

/**
* Hook to read the create controller props from the CreateContext.
*
* Mostly used within a <CreateContext.Provider> (e.g. as a descendent of <Create>).
*
* But you can also use it without a <CreateContext.Provider>. In this case, it is up to you
* to pass all the necessary props.
*
* The given props will take precedence over context values.
*
* @typedef {Object} CreateControllerProps
*
* @returns {CreateControllerProps} create controller props
*
* @see useCreateController for how it is filled
*
*/
export const useCreateContext = <
RecordType extends Omit<Record, 'id'> = Omit<Record, 'id'>
>(
Expand All @@ -13,22 +32,59 @@ export const useCreateContext = <
// @ts-ignore
CreateContext
);

if (!context.resource) {
/**
* The element isn't inside a <CreateContext.Provider>
* To avoid breakage in that case, fallback to props
*
* @deprecated - to be removed in 4.0
*/
if (process.env.NODE_ENV !== 'production') {
console.log(
"Create components must be used inside a <CreateContext.Provider>. Relying on props rather than context to get Create data and callbacks is deprecated and won't be supported in the next major version of react-admin."
);
}

return props;
}

return context;
// Props take precedence over the context
return useMemo(
() =>
merge(
{},
context,
props != null ? extractCreateContextProps(props) : {}
),
[context, props]
);
};

/**
* Extract only the create controller props
*
* @param {Object} props props passed to the useCreateContext hook
*
* @returns {CreateControllerProps} create controller props
*/
const extractCreateContextProps = ({
basePath,
record,
defaultTitle,
onFailureRef,
onSuccessRef,
transformRef,
loaded,
loading,
redirect,
setOnFailure,
setOnSuccess,
setTransform,
resource,
save,
saving,
successMessage,
version,
}: any) => ({
basePath,
record,
defaultTitle,
onFailureRef,
onSuccessRef,
transformRef,
loaded,
loading,
redirect,
setOnFailure,
setOnSuccess,
setTransform,
resource,
save,
saving,
successMessage,
version,
});
104 changes: 80 additions & 24 deletions packages/ra-core/src/controller/details/useEditContext.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,91 @@
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import merge from 'lodash/merge';

import { Record } from '../../types';
import { EditContext } from './EditContext';
import { EditControllerProps } from './useEditController';

/**
* Hook to read the edit controller props from the CreateContext.
*
* Mostly used within a <EditContext.Provider> (e.g. as a descendent of <Edit>).
*
* But you can also use it without a <EditContext.Provider>. In this case, it is up to you
* to pass all the necessary props.
*
* The given props will take precedence over context values.
*
* @typedef {Object} EditControllerProps
*
* @returns {EditControllerProps} edit controller props
*
* @see useEditController for how it is filled
*
*/
export const useEditContext = <RecordType extends Record = Record>(
props?: Partial<EditControllerProps<RecordType>>
): Partial<EditControllerProps<RecordType>> => {
// Can't find a way to specify the RecordType when CreateContext is declared
// Can't find a way to specify the RecordType when EditContext is declared
// @ts-ignore
const context = useContext<EditControllerProps<RecordType>>(EditContext);

if (!context.resource) {
/**
* The element isn't inside a <EditContext.Provider>
* To avoid breakage in that case, fallback to props
*
* @deprecated - to be removed in 4.0
*/
if (process.env.NODE_ENV !== 'production') {
console.log(
"Edit components must be used inside a <EditContext.Provider>. Relying on props rather than context to get Edit data and callbacks is deprecated and won't be supported in the next major version of react-admin."
);
}
// Necessary for actions (EditActions) which expect a data prop containing the record
// @deprecated - to be removed in 4.0d
return {
...props,
record: props.record || props.data,
data: props.record || props.data,
};
}

return context;
// Props take precedence over the context
return useMemo(
() =>
merge(
{},
context,
props != null ? extractEditContextProps(props) : {}
),
[context, props]
);
};

/**
* Extract only the edit controller props
*
* @param {Object} props props passed to the useEditContext hook
*
* @returns {EditControllerProps} edit controller props
*/
const extractEditContextProps = ({
basePath,
data,
record,
defaultTitle,
onFailureRef,
onSuccessRef,
transformRef,
loaded,
loading,
redirect,
setOnFailure,
setOnSuccess,
setTransform,
resource,
save,
saving,
successMessage,
version,
}: any) => ({
basePath,
// Necessary for actions (EditActions) which expect a data prop containing the record
// @deprecated - to be removed in 4.0d
data: record || data,
record: record || data,
defaultTitle,
onFailureRef,
onSuccessRef,
transformRef,
loaded,
loading,
redirect,
setOnFailure,
setOnSuccess,
setTransform,
resource,
save,
saving,
successMessage,
version,
});
84 changes: 60 additions & 24 deletions packages/ra-core/src/controller/details/useShowContext.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,71 @@
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import merge from 'lodash/merge';

import { Record } from '../../types';
import { ShowContext } from './ShowContext';
import { ShowControllerProps } from './useShowController';

/**
* Hook to read the show controller props from the ShowContext.
*
* Mostly used within a <ShowContext.Provider> (e.g. as a descendent of <Show>).
*
* But you can also use it without a <ShowContext.Provider>. In this case, it is up to you
* to pass all the necessary props.
*
* The given props will take precedence over context values.
*
* @typedef {Object} ShowControllerProps
*
* @returns {ShowControllerProps} create controller props
*
* @see useShowController for how it is filled
*
*/
export const useShowContext = <RecordType extends Record = Record>(
props?: Partial<ShowControllerProps<RecordType>>
): Partial<ShowControllerProps<RecordType>> => {
// Can't find a way to specify the RecordType when CreateContext is declared
// Can't find a way to specify the RecordType when ShowContext is declared
// @ts-ignore
const context = useContext<ShowControllerProps<RecordType>>(ShowContext);

if (!context.resource) {
/**
* The element isn't inside a <ShowContext.Provider>
* To avoid breakage in that case, fallback to props
*
* @deprecated - to be removed in 4.0
*/
if (process.env.NODE_ENV !== 'production') {
console.log(
"Show components must be used inside a <ShowContext.Provider>. Relying on props rather than context to get Show data and callbacks is deprecated and won't be supported in the next major version of react-admin."
);
}
// Necessary for actions (EditActions) which expect a data prop containing the record
// @deprecated - to be removed in 4.0d
return {
...props,
record: props.record || props.data,
data: props.record || props.data,
};
}

return context;
// Props take precedence over the context
return useMemo(
() =>
merge(
{},
context,
props != null ? extractShowContextProps(props) : {}
),
[context, props]
);
};

/**
* Extract only the show controller props
*
* @param {Object} props props passed to the useShowContext hook
*
* @returns {ShowControllerProps} show controller props
*/
const extractShowContextProps = ({
basePath,
record,
data,
defaultTitle,
loaded,
loading,
resource,
version,
}: any) => ({
basePath,
// Necessary for actions (EditActions) which expect a data prop containing the record
// @deprecated - to be removed in 4.0d
record: record || data,
data: record || data,
defaultTitle,
loaded,
loading,
resource,
version,
});
Loading