From db1ced91687eb33a3352f8736bc22932d62a20eb Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Tue, 2 Feb 2021 14:52:35 +0100 Subject: [PATCH 1/7] Add enabled options to query hooks --- packages/ra-core/src/dataProvider/useGetList.ts | 5 ++++- packages/ra-core/src/dataProvider/useGetMany.ts | 16 ++++++++++++++-- .../src/dataProvider/useGetManyReference.ts | 14 ++++++++++++-- .../ra-core/src/dataProvider/useGetMatching.ts | 14 ++++++++++++-- packages/ra-core/src/dataProvider/useGetOne.ts | 14 ++++++++++++-- packages/ra-core/src/dataProvider/useQuery.ts | 5 +++++ .../src/dataProvider/useQueryWithStore.ts | 11 +++++++++++ 7 files changed, 70 insertions(+), 9 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts index 6b467a245aa..4f1557a2cdc 100644 --- a/packages/ra-core/src/dataProvider/useGetList.ts +++ b/packages/ra-core/src/dataProvider/useGetList.ts @@ -31,7 +31,10 @@ const defaultData = {}; * @param {Object} pagination The request pagination { page, perPage }, e.g. { page: 1, perPage: 10 } * @param {Object} sort The request sort { field, order }, e.g. { field: 'id', order: 'DESC' } * @param {Object} filter The request filters, e.g. { title: 'hello, world' } - * @param {Object} options Options object to pass to the dataProvider. May include side effects to be executed upon success or failure, e.g. { onSuccess: { refresh: true } } + * @param {Object} options Options object to pass to the dataProvider. + * @param {boolean} options.enabled Flag to conditionally run the query. If it's false, the query will not run + * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. { onSuccess: { refresh: true } } + * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. { onFailure: error => notify(error.message) } * * @returns The current request state. Destructure as { data, total, ids, error, loading, loaded }. * diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index 9c55b0c296a..987c0359cd9 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -24,6 +24,11 @@ interface Query { interface QueriesToCall { [resource: string]: Query[]; } +interface UseGetManyOptions { + onSuccess?: Callback; + onFailure?: Callback; + enabled?: boolean; +} interface UseGetManyResult { data: Record[]; error?: any; @@ -59,7 +64,10 @@ const DataProviderOptions = { action: CRUD_GET_MANY }; * * @param resource The resource name, e.g. 'posts' * @param ids The resource identifiers, e.g. [123, 456, 789] - * @param options Options object to pass to the dataProvider. May include side effects to be executed upon success or failure, e.g. { onSuccess: { refresh: true } } + * @param {Object} options Options object to pass to the dataProvider. + * @param {boolean} options.enabled Flag to conditionally run the query. If it's false, the query will not run + * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. { onSuccess: { refresh: true } } + * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. { onFailure: error => notify(error.message) } * * @returns The current request state. Destructure as { data, error, loading, loaded }. * @@ -83,7 +91,7 @@ const DataProviderOptions = { action: CRUD_GET_MANY }; const useGetMany = ( resource: string, ids: Identifier[], - options: any = {} + options: UseGetManyOptions = {} ): UseGetManyResult => { // we can't use useQueryWithStore here because we're aggregating queries first // therefore part of the useQueryWithStore logic will have to be repeated below @@ -108,6 +116,10 @@ const useGetMany = ( } dataProvider = useDataProvider(); // not the best way to pass the dataProvider to a function outside the hook, but I couldn't find a better one useEffect(() => { + if (options.enabled === false) { + return; + } + if (!queriesToCall[resource]) { queriesToCall[resource] = []; } diff --git a/packages/ra-core/src/dataProvider/useGetManyReference.ts b/packages/ra-core/src/dataProvider/useGetManyReference.ts index 2eb8c6bfa03..320f0bbdb5b 100644 --- a/packages/ra-core/src/dataProvider/useGetManyReference.ts +++ b/packages/ra-core/src/dataProvider/useGetManyReference.ts @@ -18,6 +18,13 @@ import { const defaultIds = []; const defaultData = {}; +interface UseGetManyReferenceOptions { + onSuccess?: (args?: any) => void; + onFailure?: (error: any) => void; + enabled?: boolean; + [key: string]: any; +} + /** * Call the dataProvider.getManyReference() method and return the resolved result * as well as the loading state. @@ -38,7 +45,10 @@ const defaultData = {}; * @param {Object} sort The request sort { field, order }, e.g. { field: 'id', order: 'DESC' } * @param {Object} filter The request filters, e.g. { body: 'hello, world' } * @param {string} referencingResource The resource name, e.g. 'posts'. Used to generate a cache key - * @param {Object} options Options object to pass to the dataProvider. May include side effects to be executed upon success or failure, e.g. { onSuccess: { refresh: true } } + * @param {Object} options Options object to pass to the dataProvider. + * @param {boolean} options.enabled Flag to conditionally run the query. If it's false, the query will not run + * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. { onSuccess: { refresh: true } } + * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. { onFailure: error => notify(error.message) } * * @returns The current request state. Destructure as { data, total, ids, error, loading, loaded }. * @@ -71,7 +81,7 @@ const useGetManyReference = ( sort: SortPayload, filter: object, referencingResource: string, - options?: any + options?: UseGetManyReferenceOptions ) => { const relatedTo = useMemo( () => nameRelatedTo(resource, id, referencingResource, target, filter), diff --git a/packages/ra-core/src/dataProvider/useGetMatching.ts b/packages/ra-core/src/dataProvider/useGetMatching.ts index a1a3b016627..a44ddc14143 100644 --- a/packages/ra-core/src/dataProvider/useGetMatching.ts +++ b/packages/ra-core/src/dataProvider/useGetMatching.ts @@ -16,6 +16,13 @@ import { getPossibleReferences, } from '../reducer'; +interface UseGetMatchingOptions { + onSuccess?: (args?: any) => void; + onFailure?: (error: any) => void; + enabled?: boolean; + [key: string]: any; +} + const referenceSource = (resource, source) => `${resource}@${source}`; /** @@ -41,7 +48,10 @@ const referenceSource = (resource, source) => `${resource}@${source}`; * @param {Object} filter The request filters, e.g. { title: 'hello, world' } * @param {string} source The field in resource containing the ids of the referenced records, e.g. 'tag_ids' * @param {string} referencingResource The resource name, e.g. 'posts'. Used to build a cache key - * @param {Object} options Options object to pass to the dataProvider. May include side effects to be executed upon success or failure, e.g. { onSuccess: { refresh: true } } + * @param {Object} options Options object to pass to the dataProvider. + * @param {boolean} options.enabled Flag to conditionally run the query. If it's false, the query will not run + * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. { onSuccess: { refresh: true } } + * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. { onFailure: error => notify(error.message) } * * @returns The current request state. Destructure as { data, total, ids, error, loading, loaded }. * @@ -73,7 +83,7 @@ const useGetMatching = ( filter: object, source: string, referencingResource: string, - options?: any + options?: UseGetMatchingOptions ): UseGetMatchingResult => { const relatedTo = referenceSource(referencingResource, source); const payload = { pagination, sort, filter }; diff --git a/packages/ra-core/src/dataProvider/useGetOne.ts b/packages/ra-core/src/dataProvider/useGetOne.ts index 6bf60ea6d0c..c86dd030805 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.ts +++ b/packages/ra-core/src/dataProvider/useGetOne.ts @@ -3,6 +3,13 @@ import get from 'lodash/get'; import { Identifier, Record, ReduxState } from '../types'; import useQueryWithStore from './useQueryWithStore'; +interface UseGetOneOptions { + onSuccess?: (args?: any) => void; + onFailure?: (error: any) => void; + enabled?: boolean; + [key: string]: any; +} + /** * Call the dataProvider.getOne() method and return the resolved value * as well as the loading state. @@ -18,7 +25,10 @@ import useQueryWithStore from './useQueryWithStore'; * * @param resource The resource name, e.g. 'posts' * @param id The resource identifier, e.g. 123 - * @param options Options object to pass to the dataProvider. May include side effects to be executed upon success or failure, e.g. { onSuccess: { refresh: true } } + * @param {Object} options Options object to pass to the dataProvider. + * @param {boolean} options.enabled Flag to conditionally run the query. If it's false, the query will not run + * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. { onSuccess: { refresh: true } } + * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. { onFailure: error => notify(error.message) } * * @returns The current request state. Destructure as { data, error, loading, loaded }. * @@ -36,7 +46,7 @@ import useQueryWithStore from './useQueryWithStore'; const useGetOne = ( resource: string, id: Identifier, - options?: any + options?: UseGetOneOptions ): UseGetOneHookValue => useQueryWithStore( { type: 'getOne', resource, payload: { id } }, diff --git a/packages/ra-core/src/dataProvider/useQuery.ts b/packages/ra-core/src/dataProvider/useQuery.ts index 7be2654e0e6..a4092a494ae 100644 --- a/packages/ra-core/src/dataProvider/useQuery.ts +++ b/packages/ra-core/src/dataProvider/useQuery.ts @@ -22,6 +22,7 @@ import useVersion from '../controller/useVersion'; * @param {Object} query.payload The payload object, e.g; { post_id: 12 } * @param {Object} options * @param {string} options.action Redux action type + * @param {boolean} options.enabled Flag to conditionally run the query. True by default. If it's false, the query will not run * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. () => refresh() * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. (error) => notify(error.message) * @param {boolean} options.withDeclarativeSideEffectsSupport Set to true to support legacy side effects e.g. { onSuccess: { refresh: true } } @@ -93,6 +94,9 @@ const useQuery = ( /* eslint-disable react-hooks/exhaustive-deps */ useEffect(() => { + if (options.enabled === false) { + return; + } /** * Support legacy side effects, e.g. { onSuccess: { refresh: true, unSelectAll: true }} * @@ -145,6 +149,7 @@ export interface Query { export interface QueryOptions { action?: string; + enabled?: boolean; onSuccess?: OnSuccess | DeclarativeSideEffect; onFailure?: OnFailure | DeclarativeSideEffect; withDeclarativeSideEffectsSupport?: boolean; diff --git a/packages/ra-core/src/dataProvider/useQueryWithStore.ts b/packages/ra-core/src/dataProvider/useQueryWithStore.ts index 33777c75f3d..ddea024d1cb 100644 --- a/packages/ra-core/src/dataProvider/useQueryWithStore.ts +++ b/packages/ra-core/src/dataProvider/useQueryWithStore.ts @@ -26,6 +26,7 @@ export interface QueryOptions { onSuccess?: OnSuccess; onFailure?: OnFailure; action?: string; + enabled?: boolean; [key: string]: any; } @@ -79,6 +80,7 @@ const defaultIsDataLoaded = (data: any): boolean => data !== undefined; * @param {Object} query.payload The payload object, e.g; { post_id: 12 } * @param {Object} options * @param {string} options.action Redux action type + * @param {boolean} options.enabled Flag to conditionally run the query. If it's false, the query will not run * @param {Function} options.onSuccess Side effect function to be executed upon success, e.g. () => refresh() * @param {Function} options.onFailure Side effect function to be executed upon failure, e.g. (error) => notify(error.message) * @param {Function} dataSelector Redux selector to get the result. Required. @@ -137,6 +139,10 @@ const useQueryWithStore = ( }); useEffect(() => { + if (options.enabled === false) { + return; + } + if (requestSignatureRef.current !== requestSignature) { // request has changed, reset the loading state requestSignatureRef.current = requestSignature; @@ -171,10 +177,15 @@ const useQueryWithStore = ( state.total, total, isDataLoaded, + options.enabled, ]); const dataProvider = useDataProvider(); useEffect(() => { + if (options.enabled === false) { + return; + } + // When several identical queries are issued during the same tick, // we only pass one query to the dataProvider. // To achieve that, the closure keeps a list of dataProvider promises From 2cf4f74c1cee03ff2b247a2bca9da4c195287323 Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Wed, 3 Feb 2021 09:45:51 +0100 Subject: [PATCH 2/7] Implement the logic inside useDataProvider --- packages/ra-core/src/dataProvider/useDataProvider.ts | 8 ++++++++ packages/ra-core/src/dataProvider/useGetList.ts | 9 ++++++++- packages/ra-core/src/dataProvider/useGetMany.ts | 4 ---- packages/ra-core/src/dataProvider/useQuery.ts | 3 --- packages/ra-core/src/dataProvider/useQueryWithStore.ts | 8 -------- packages/ra-core/src/types.ts | 1 + 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useDataProvider.ts b/packages/ra-core/src/dataProvider/useDataProvider.ts index e8f17b3ef9c..7869f03420b 100644 --- a/packages/ra-core/src/dataProvider/useDataProvider.ts +++ b/packages/ra-core/src/dataProvider/useDataProvider.ts @@ -134,6 +134,7 @@ const useDataProvider = (): DataProviderProxy => { onSuccess = undefined, onFailure = undefined, mutationMode = undoable ? 'undoable' : 'pessimistic', + enabled = true, ...rest } = options || {}; @@ -157,6 +158,13 @@ const useDataProvider = (): DataProviderProxy => { 'You must pass an onSuccess callback calling notify() to use the undoable mode' ); } + if (typeof enabled !== 'boolean') { + throw new Error('The enabled option must be a boolean'); + } + + if (enabled === false) { + return Promise.resolve(); + } const params = { resource, diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts index 4f1557a2cdc..03d312ac6bf 100644 --- a/packages/ra-core/src/dataProvider/useGetList.ts +++ b/packages/ra-core/src/dataProvider/useGetList.ts @@ -11,6 +11,13 @@ import { } from '../types'; import useQueryWithStore from './useQueryWithStore'; +interface UseGetListOptions { + onSuccess?: (args?: any) => void; + onFailure?: (error: any) => void; + enabled?: boolean; + [key: string]: any; +} + const defaultIds = []; const defaultData = {}; @@ -60,7 +67,7 @@ const useGetList = ( pagination: PaginationPayload, sort: SortPayload, filter: object, - options?: any + options?: UseGetListOptions ): { data?: RecordMap; ids?: Identifier[]; diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index 987c0359cd9..4a2b2e0bc70 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -116,10 +116,6 @@ const useGetMany = ( } dataProvider = useDataProvider(); // not the best way to pass the dataProvider to a function outside the hook, but I couldn't find a better one useEffect(() => { - if (options.enabled === false) { - return; - } - if (!queriesToCall[resource]) { queriesToCall[resource] = []; } diff --git a/packages/ra-core/src/dataProvider/useQuery.ts b/packages/ra-core/src/dataProvider/useQuery.ts index a4092a494ae..dca734da960 100644 --- a/packages/ra-core/src/dataProvider/useQuery.ts +++ b/packages/ra-core/src/dataProvider/useQuery.ts @@ -94,9 +94,6 @@ const useQuery = ( /* eslint-disable react-hooks/exhaustive-deps */ useEffect(() => { - if (options.enabled === false) { - return; - } /** * Support legacy side effects, e.g. { onSuccess: { refresh: true, unSelectAll: true }} * diff --git a/packages/ra-core/src/dataProvider/useQueryWithStore.ts b/packages/ra-core/src/dataProvider/useQueryWithStore.ts index ddea024d1cb..d04f182d428 100644 --- a/packages/ra-core/src/dataProvider/useQueryWithStore.ts +++ b/packages/ra-core/src/dataProvider/useQueryWithStore.ts @@ -139,10 +139,6 @@ const useQueryWithStore = ( }); useEffect(() => { - if (options.enabled === false) { - return; - } - if (requestSignatureRef.current !== requestSignature) { // request has changed, reset the loading state requestSignatureRef.current = requestSignature; @@ -182,10 +178,6 @@ const useQueryWithStore = ( const dataProvider = useDataProvider(); useEffect(() => { - if (options.enabled === false) { - return; - } - // When several identical queries are issued during the same tick, // we only pass one query to the dataProvider. // To achieve that, the closure keeps a list of dataProvider promises diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index 3486a27a6ca..b6b5edb9e5e 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -297,6 +297,7 @@ export interface UseDataProviderOptions { mutationMode?: MutationMode; onSuccess?: OnSuccess; onFailure?: OnFailure; + enabled?: boolean; } export type LegacyDataProvider = ( From 98b30d3cef2b9fd5d556a1c995ff685a98c39403 Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Wed, 3 Feb 2021 10:32:05 +0100 Subject: [PATCH 3/7] remove useless dependency in useEffect --- packages/ra-core/src/dataProvider/useQueryWithStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ra-core/src/dataProvider/useQueryWithStore.ts b/packages/ra-core/src/dataProvider/useQueryWithStore.ts index d04f182d428..f525c97973a 100644 --- a/packages/ra-core/src/dataProvider/useQueryWithStore.ts +++ b/packages/ra-core/src/dataProvider/useQueryWithStore.ts @@ -173,7 +173,6 @@ const useQueryWithStore = ( state.total, total, isDataLoaded, - options.enabled, ]); const dataProvider = useDataProvider(); From 51ea64dde25de831010007f509f3bad403f5dc4e Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Wed, 3 Feb 2021 10:36:39 +0100 Subject: [PATCH 4/7] use UseDataProviderOptions when possible --- packages/ra-core/src/dataProvider/useGetList.ts | 10 ++-------- packages/ra-core/src/dataProvider/useGetOne.ts | 16 +++++++--------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts index 03d312ac6bf..59489f1fa6f 100644 --- a/packages/ra-core/src/dataProvider/useGetList.ts +++ b/packages/ra-core/src/dataProvider/useGetList.ts @@ -8,16 +8,10 @@ import { Identifier, Record, RecordMap, + UseDataProviderOptions, } from '../types'; import useQueryWithStore from './useQueryWithStore'; -interface UseGetListOptions { - onSuccess?: (args?: any) => void; - onFailure?: (error: any) => void; - enabled?: boolean; - [key: string]: any; -} - const defaultIds = []; const defaultData = {}; @@ -67,7 +61,7 @@ const useGetList = ( pagination: PaginationPayload, sort: SortPayload, filter: object, - options?: UseGetListOptions + options?: UseDataProviderOptions ): { data?: RecordMap; ids?: Identifier[]; diff --git a/packages/ra-core/src/dataProvider/useGetOne.ts b/packages/ra-core/src/dataProvider/useGetOne.ts index c86dd030805..952449ce90e 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.ts +++ b/packages/ra-core/src/dataProvider/useGetOne.ts @@ -1,15 +1,13 @@ import get from 'lodash/get'; -import { Identifier, Record, ReduxState } from '../types'; +import { + Identifier, + Record, + ReduxState, + UseDataProviderOptions, +} from '../types'; import useQueryWithStore from './useQueryWithStore'; -interface UseGetOneOptions { - onSuccess?: (args?: any) => void; - onFailure?: (error: any) => void; - enabled?: boolean; - [key: string]: any; -} - /** * Call the dataProvider.getOne() method and return the resolved value * as well as the loading state. @@ -46,7 +44,7 @@ interface UseGetOneOptions { const useGetOne = ( resource: string, id: Identifier, - options?: UseGetOneOptions + options?: UseDataProviderOptions ): UseGetOneHookValue => useQueryWithStore( { type: 'getOne', resource, payload: { id } }, From de37d1218f6de7267ba793b0585f9f8056271e50 Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Wed, 3 Feb 2021 16:19:55 +0100 Subject: [PATCH 5/7] Add first test --- .../getDataProviderCallArguments.ts | 1 + .../src/dataProvider/useDataProvider.spec.js | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/packages/ra-core/src/dataProvider/getDataProviderCallArguments.ts b/packages/ra-core/src/dataProvider/getDataProviderCallArguments.ts index 68f35a1b94e..ff7c01a92ae 100644 --- a/packages/ra-core/src/dataProvider/getDataProviderCallArguments.ts +++ b/packages/ra-core/src/dataProvider/getDataProviderCallArguments.ts @@ -9,6 +9,7 @@ const OptionsProperties = [ 'onSuccess', 'undoable', 'mutationMode', + 'enabled', ]; const isDataProviderOptions = (value: any) => { diff --git a/packages/ra-core/src/dataProvider/useDataProvider.spec.js b/packages/ra-core/src/dataProvider/useDataProvider.spec.js index 906dc6b4e60..6136bd1cfe5 100644 --- a/packages/ra-core/src/dataProvider/useDataProvider.spec.js +++ b/packages/ra-core/src/dataProvider/useDataProvider.spec.js @@ -319,6 +319,71 @@ describe('useDataProvider', () => { expect(onFailure.mock.calls[0][0]).toEqual(new Error('foo')); }); + it('should accept an enabled option to block the query until a condition is met', async () => { + const UseGetOneWithEnabled = () => { + const [data, setData] = useState(); + const [error, setError] = useState(); + const [isEnabled, setIsEnabled] = useState(false); + const dataProvider = useDataProvider(); + useEffect(() => { + dataProvider + .getOne('dummy', {}, { enabled: isEnabled }) + .then(res => { + if (res) { + // @fzaninotto I don't think this is correct + // because we return a empty resolved Promise, + // res is undefined here + setData(res.data); + } + }) + .catch(e => setError(e)); + }, [dataProvider, isEnabled]); + + let content =
loading
; + if (error) + content =
{error.message}
; + if (data) + content = ( +
{JSON.stringify(data)}
+ ); + return ( +
+ {content} + +
+ ); + }; + const getOne = jest + .fn() + .mockResolvedValue({ data: { id: 1, title: 'foo' } }); + const dataProvider = { getOne }; + const { queryByTestId, getByRole } = renderWithRedux( + + + + ); + expect(queryByTestId('loading')).not.toBeNull(); + await act(async () => { + await new Promise(resolve => setTimeout(resolve)); + }); + expect(getOne).not.toBeCalled(); + expect(queryByTestId('loading')).not.toBeNull(); + + // enable the query + fireEvent.click(getByRole('button', { name: 'toggle' })); + + await act(async () => { + await new Promise(resolve => setTimeout(resolve)); + }); + expect(getOne).toBeCalledTimes(1); + expect(queryByTestId('loading')).toBeNull(); + expect(queryByTestId('data').textContent).toBe( + '{"id":1,"title":"foo"}' + ); + }); + describe('mutationMode', () => { it('should wait for response to dispatch side effects in pessimistic mode', async () => { let resolveUpdate; From e5a17d7341cd51dcca74537beef2a3e9523049d4 Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Tue, 16 Feb 2021 19:41:21 +0100 Subject: [PATCH 6/7] Resolve with empty object --- .../ra-core/src/dataProvider/useDataProvider.spec.js | 9 +-------- packages/ra-core/src/dataProvider/useDataProvider.ts | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/ra-core/src/dataProvider/useDataProvider.spec.js b/packages/ra-core/src/dataProvider/useDataProvider.spec.js index 6136bd1cfe5..a25010dca66 100644 --- a/packages/ra-core/src/dataProvider/useDataProvider.spec.js +++ b/packages/ra-core/src/dataProvider/useDataProvider.spec.js @@ -328,14 +328,7 @@ describe('useDataProvider', () => { useEffect(() => { dataProvider .getOne('dummy', {}, { enabled: isEnabled }) - .then(res => { - if (res) { - // @fzaninotto I don't think this is correct - // because we return a empty resolved Promise, - // res is undefined here - setData(res.data); - } - }) + .then(res => setData(res.data)) .catch(e => setError(e)); }, [dataProvider, isEnabled]); diff --git a/packages/ra-core/src/dataProvider/useDataProvider.ts b/packages/ra-core/src/dataProvider/useDataProvider.ts index 7869f03420b..e24722a40f5 100644 --- a/packages/ra-core/src/dataProvider/useDataProvider.ts +++ b/packages/ra-core/src/dataProvider/useDataProvider.ts @@ -163,7 +163,7 @@ const useDataProvider = (): DataProviderProxy => { } if (enabled === false) { - return Promise.resolve(); + return Promise.resolve({}); } const params = { From 620736369d25bfc644f61416ab0ccbb289e33676 Mon Sep 17 00:00:00 2001 From: Valentin Hervieu Date: Wed, 17 Feb 2021 09:49:06 +0100 Subject: [PATCH 7/7] Add documentation --- docs/Actions.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/Actions.md b/docs/Actions.md index 28845b4aff3..85fd69d71f6 100644 --- a/docs/Actions.md +++ b/docs/Actions.md @@ -408,6 +408,26 @@ const BulkDeletePostsButton = ({ selectedIds }) => { }; ``` +## Synchronizing Dependant Queries +`useQuery` and all its corresponding specialized hooks support an `enabled` option. This is useful if you need to have a query executed only when a condition is met. For example, in the following example, we only fetch the categories if we have at least one post: +```jsx +// fetch posts +const { ids, data: posts, loading: isLoading } = useGetList( + 'posts', + { page: 1, perPage: 20 }, + { field: 'name', order: 'ASC' }, + {} +); + +// then fetch categories for these posts +const { data: categories, loading: isLoadingCategories } = useGetMany( + 'categories', + ids.map(id=> posts[id].category_id), + // run only if the first query returns non-empty result + { enabled: ids.length > 0 } +); +``` + ## Handling Side Effects In `useDataProvider` `useDataProvider` returns a `dataProvider` object. Each call to its method return a Promise, allowing adding business logic on success in `then()`, and on failure in `catch()`.