From ef8d28556fde2fa3be5ecdb35abf6f6d4ddedbae Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Mon, 20 May 2019 22:59:05 +0200 Subject: [PATCH 01/18] Change useQuery and useMutation signature to accept a selector --- docs/Actions.md | 65 ++++++++++--------- docs/DataProviders.md | 2 +- examples/demo/src/dashboard/NewCustomers.js | 16 +++-- examples/demo/src/reviews/AcceptButton.js | 12 ++-- packages/ra-core/src/fetch/Mutation.tsx | 2 +- packages/ra-core/src/fetch/Query.tsx | 2 +- packages/ra-core/src/fetch/useDataProvider.ts | 64 +++++++++++++----- packages/ra-core/src/fetch/useMutation.ts | 58 +++++++++++------ packages/ra-core/src/fetch/useQuery.ts | 54 +++++++++++---- 9 files changed, 183 insertions(+), 92 deletions(-) diff --git a/docs/Actions.md b/docs/Actions.md index 41f2ac816f4..77b0d5f2678 100644 --- a/docs/Actions.md +++ b/docs/Actions.md @@ -11,11 +11,11 @@ React-admin provides special hooks to emit read and write queries to the `dataPr ## `useQuery` Hook -Use the `useQuery` hook to emit a read query to the API when a component mounts. The parameters are the same as the ones expected by the [`dataProvider`](./DataProviders.md): +Use the `useQuery` hook to emit a read query to the API when a component mounts. Call it with an object having the same fields as the parameters expected by the [`dataProvider`](./DataProviders.md): - `type`: The Query type, e.g `GET_LIST` - `resource`: The Resource name, e.g. "posts" -- `params`: Query parameters. Depends on the query type. +- `payload`: Query parameters. Depends on the query type. The return value of `useQuery` is an object, which updates according to the request state: @@ -29,11 +29,11 @@ Here is an implementation of a user profile component using the `useQuery` hook: import { useQuery, GET_ONE } from 'react-admin'; const UserProfile = ({ record }) => { - const { loading, error, data } = useQuery( - GET_ONE, - 'users', - { id: record.id } - ); + const { loading, error, data } = useQuery({ + type: GET_ONE, + resource: 'users', + payload: { id: record.id } + }); if (loading) { return ; } if (error) { return

ERROR

; } return
User {data.username}
; @@ -50,14 +50,14 @@ Here is another example usage of `useQuery`, this time to display a list of user import { useQuery, GET_LIST } from 'react-admin'; const UserList = () => { - const { loading, error, data, total } = useQuery( - GET_LIST, - 'users', - { + const { loading, error, data, total } = useQuery({ + type: GET_LIST, + resource: 'users', + payload: { pagination: { page: 1, perPage: 10 }, sort: { field: 'username', order: 'ASC' }, } - ); + }); if (loading) { return ; } if (error) { return

ERROR

; } return ( @@ -86,7 +86,7 @@ You can destructure the return value of the `useQuery` hook as `{ data, total, e ## `useMutation` Hook -`useQuery` emits the request to the `dataProvider` as soon as the component mounts. To emit the request based on a user action, use the `useMutation` hook instead. This hook returns a callback that emits the request when executed, and an object containing the request state: +`useQuery` emits the request to the `dataProvider` as soon as the component mounts. To emit the request based on a user action, use the `useMutation` hook instead. This hook takes the same arguments as `useQuery`, but returns a callback that emits the request when executed, and an object containing the request state: - mount: { loading: false, loaded: false } - mutate called: { loading: true, loaded: false } @@ -100,11 +100,11 @@ Here is an implementation of an "Approve" button: import { useMutation, UPDATE } from 'react-admin'; const ApproveButton = ({ record }) => { - const [approve, { loading }] = useMutation( - UPDATE, - 'comments', - { id: record.id, data: { isApproved: true } } - ); + const [approve, { loading }] = useMutation({ + type: UPDATE, + resource: 'comments', + payload: { id: record.id, data: { isApproved: true } } + }); return ; }; ``` @@ -143,7 +143,7 @@ export const CommentList = (props) => ## Handling Side Effects -Fetching data is called a *side effect*, since it calls the outside world, and is asynchronous. Usual actions may have other side effects, like showing a notification, or redirecting the user to another page. Both `useQuery` and `useMutation` hooks accept a fourth parameter, which lets you describe the options of the query, including success and failure side effects. +Fetching data is called a *side effect*, since it calls the outside world, and is asynchronous. Usual actions may have other side effects, like showing a notification, or redirecting the user to another page. Both `useQuery` and `useMutation` hooks accept a second parameter in addition to the query, which lets you describe the options of the query, including success and failure side effects. Here is how to add notifications and a redirection to the `ApproveButton` component using that fourth parameter: @@ -153,17 +153,22 @@ import { useMutation, UPDATE } from 'react-admin'; const ApproveButton = ({ record }) => { const [approve, { loading }] = useMutation( - UPDATE, - 'comments', - { id: record.id, data: { isApproved: true } }, + { + type: UPDATE, + resource: 'comments', + payload: { id: record.id, data: { isApproved: true } }, + }, + { + onSuccess: { + notification: { body: 'Comment approved', level: 'info' }, + redirectTo: '/comments', + }, -+ onError: { -+ notification: { body: 'Error: comment not approved', level: 'warning' } -+ } ++ onFailure: { ++ notification: { ++ body: 'Error: comment not approved', ++ level: 'warning', ++ }, ++ }, + } ); return ; @@ -195,9 +200,11 @@ import { useMutation, UPDATE } from 'react-admin'; const ApproveButton = ({ record }) => { const [approve, { loading }] = useMutation( - UPDATE, - 'comments', - { id: record.id, data: { isApproved: true } }, + { + type: UPDATE, + resource: 'comments', + payload: { id: record.id, data: { isApproved: true } }, + }, { + undoable: true, onSuccess: { @@ -269,7 +276,7 @@ const Dashboard = () => { } ``` -`useDataProvider` is more low-level than `useQuery` and `useMutation`, as it doesn't handle loading and error states (even though queries from `useDataProvider` trigger the global loading indicator). The `dataProvider` callback that it returns also accepts a fourth options parameter, just like the two other hoows. +`useDataProvider` is more low-level than `useQuery` and `useMutation`, as it doesn't handle loading and error states (even though queries from `useDataProvider` trigger the global loading indicator). The `dataProvider` callback that it returns also accepts a fourth options parameter. ## Legacy Components: ``, ``, and `withDataProvider` diff --git a/docs/DataProviders.md b/docs/DataProviders.md index a3d4d7663fd..0be4d1c872c 100644 --- a/docs/DataProviders.md +++ b/docs/DataProviders.md @@ -24,7 +24,7 @@ The `dataProvider` parameter of the `` component must be a function with * @param {Object} payload Request parameters. Depends on the action type * @returns {Promise} the Promise for a response */ -const dataProvider = (type, resource, params) => new Promise(); +const dataProvider = (type, resource, payload) => new Promise(); ``` You can find a Data Provider example implementation in [`packages/ra-data-simple-rest/src/index.js`](https://github.com/marmelab/react-admin/blob/master/packages/ra-data-simple-rest/src/index.js); diff --git a/examples/demo/src/dashboard/NewCustomers.js b/examples/demo/src/dashboard/NewCustomers.js index 95bf84f23c7..6e8f4c9d411 100644 --- a/examples/demo/src/dashboard/NewCustomers.js +++ b/examples/demo/src/dashboard/NewCustomers.js @@ -43,13 +43,17 @@ const NewCustomers = () => { return date; }, []); - const { loaded, data: visitors } = useQuery(GET_LIST, 'customers', { - filter: { - has_ordered: true, - first_seen_gte: aMonthAgo.toISOString(), + const { loaded, data: visitors } = useQuery({ + type: GET_LIST, + resource: 'customers', + payload: { + filter: { + has_ordered: true, + first_seen_gte: aMonthAgo.toISOString(), + }, + sort: { field: 'first_seen', order: 'DESC' }, + pagination: { page: 1, perPage: 100 }, }, - sort: { field: 'first_seen', order: 'DESC' }, - pagination: { page: 1, perPage: 100 }, }); if (!loaded) return null; diff --git a/examples/demo/src/reviews/AcceptButton.js b/examples/demo/src/reviews/AcceptButton.js index 106320cd3c0..66091b65927 100644 --- a/examples/demo/src/reviews/AcceptButton.js +++ b/examples/demo/src/reviews/AcceptButton.js @@ -6,7 +6,7 @@ import Button from '@material-ui/core/Button'; import ThumbUp from '@material-ui/icons/ThumbUp'; import { useTranslate, useMutation } from 'react-admin'; -const sideEffects = { +const options = { undoable: true, onSuccess: { notification: { @@ -29,10 +29,12 @@ const sideEffects = { const AcceptButton = ({ record }) => { const translate = useTranslate(); const [approve, { loading }] = useMutation( - 'UPDATE', - 'reviews', - { id: record.id, data: { status: 'accepted' } }, - sideEffects + { + type: 'UPDATE', + resource: 'reviews', + payload: { id: record.id, data: { status: 'accepted' } }, + }, + options ); return record && record.status === 'pending' ? (