Skip to content

Commit

Permalink
Merge pull request #5802 from marmelab/use-props-over-context
Browse files Browse the repository at this point in the history
Fix warning when passing partial props to useListContext and other view context hooks
  • Loading branch information
fzaninotto committed Feb 13, 2021
2 parents 935f521 + 74d3c5c commit 93c94b7
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 96 deletions.
30 changes: 23 additions & 7 deletions examples/simple/src/customRouteLayout.js
@@ -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
@@ -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
@@ -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
@@ -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,
});

0 comments on commit 93c94b7

Please sign in to comment.