diff --git a/packages/ra-core/src/dataProvider/useGetMany.spec.tsx b/packages/ra-core/src/dataProvider/useGetMany.spec.tsx index c40ed46c502..d3fd93b15bf 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.spec.tsx +++ b/packages/ra-core/src/dataProvider/useGetMany.spec.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import expect from 'expect'; import { render, waitFor } from '@testing-library/react'; +import { QueryClient } from '@tanstack/react-query'; import { CoreAdminContext } from '../core'; import { useGetMany } from './useGetMany'; +import { useGetOne } from './useGetOne'; import { testDataProvider } from '../dataProvider'; -import { useState } from 'react'; -import { QueryClient } from '@tanstack/react-query'; const UseGetMany = ({ resource, @@ -39,7 +39,7 @@ const UseCustomGetMany = ({ options?: any; callback?: Function; }) => { - const [stateIds, setStateIds] = useState(ids); + const [stateIds, setStateIds] = React.useState(ids); const hookValue = useGetMany(resource, { ids: stateIds }, options); if (callback) callback(hookValue); @@ -398,4 +398,45 @@ describe('useGetMany', () => { expect(abort).toHaveBeenCalled(); }); }); + + describe('TypeScript', () => { + it('should return the parametric type', () => { + type Foo = { id: number; name: string }; + const _Dummy = () => { + const { data, error, isPending } = useGetMany('posts', { + ids: [1], + }); + if (isPending || error) return null; + return
{data[0].name}
; + }; + // no render needed, only checking types + }); + it('should accept empty id param', () => { + const _Dummy = () => { + type Post = { + id: number; + tag_ids: number[]; + }; + const { data: comment } = useGetOne('comments', { + id: 1, + }); + type Tag = { + id: number; + name: string; + }; + const { data, error, isPending } = useGetMany('posts', { + ids: comment?.tag_ids, + }); + if (isPending || error) return null; + return ( +
    + {data.map(tag => ( +
  • {tag.name}
  • + ))} +
+ ); + }; + // no render needed, only checking types + }); + }); }); diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index 2acec9d0bf1..103849426c5 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -53,7 +53,7 @@ import { useEvent } from '../util'; */ export const useGetMany = ( resource: string, - params: Partial = {}, + params: Partial>, options: UseGetManyOptions = {} ): UseGetManyHookValue => { const { ids, meta } = params; @@ -64,6 +64,7 @@ export const useGetMany = ( onError = noop, onSuccess = noop, onSettled = noop, + enabled, ...queryOptions } = options; const onSuccessEvent = useEvent(onSuccess); @@ -115,6 +116,7 @@ export const useGetMany = ( } }, retry: false, + enabled: enabled ?? ids != null, ...queryOptions, }); diff --git a/packages/ra-core/src/dataProvider/useGetManyAggregate.ts b/packages/ra-core/src/dataProvider/useGetManyAggregate.ts index 3a8092fb737..f4b0bf4c002 100644 --- a/packages/ra-core/src/dataProvider/useGetManyAggregate.ts +++ b/packages/ra-core/src/dataProvider/useGetManyAggregate.ts @@ -68,7 +68,7 @@ import { useEvent } from '../util'; */ export const useGetManyAggregate = ( resource: string, - params: GetManyParams, + params: Partial>, options: UseGetManyAggregateOptions = {} ): UseGetManyHookValue => { const dataProvider = useDataProvider(); @@ -78,6 +78,7 @@ export const useGetManyAggregate = ( onError = noop, onSuccess = noop, onSettled = noop, + enabled, ...queryOptions } = options; const onSuccessEvent = useEvent(onSuccess); @@ -133,6 +134,7 @@ export const useGetManyAggregate = ( }); }), placeholderData, + enabled: enabled ?? ids != null, retry: false, ...queryOptions, }); diff --git a/packages/ra-core/src/dataProvider/useGetOne.spec.tsx b/packages/ra-core/src/dataProvider/useGetOne.spec.tsx index 058bc28f079..81076df19f2 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.spec.tsx +++ b/packages/ra-core/src/dataProvider/useGetOne.spec.tsx @@ -299,4 +299,38 @@ describe('useGetOne', () => { expect(abort).toHaveBeenCalled(); }); }); + describe('TypeScript', () => { + it('should return the parametric type', () => { + type Foo = { id: number; name: string }; + const _Dummy = () => { + const { data, error, isPending } = useGetOne('posts', { + id: 1, + }); + if (isPending || error) return null; + return
{data.name}
; + }; + // no render needed, only checking types + }); + it('should accept empty id param', () => { + const _Dummy = () => { + type Comment = { + id: number; + post_id: number; + }; + const { data: comment } = useGetOne('comments', { + id: 1, + }); + type Post = { + id: number; + title: string; + }; + const { data, error, isPending } = useGetOne('posts', { + id: comment?.post_id, + }); + if (isPending || error) return null; + return
{data.title}
; + }; + // no render needed, only checking types + }); + }); }); diff --git a/packages/ra-core/src/dataProvider/useGetOne.ts b/packages/ra-core/src/dataProvider/useGetOne.ts index 1bff14f4079..c81d76b44b4 100644 --- a/packages/ra-core/src/dataProvider/useGetOne.ts +++ b/packages/ra-core/src/dataProvider/useGetOne.ts @@ -49,7 +49,7 @@ import { useEvent } from '../util'; */ export const useGetOne = ( resource: string, - { id, meta }: GetOneParams, + { id, meta }: Partial>, options: UseGetOneOptions = {} ): UseGetOneHookValue => { const dataProvider = useDataProvider(); @@ -57,6 +57,7 @@ export const useGetOne = ( onError = noop, onSuccess = noop, onSettled = noop, + enabled, ...queryOptions } = options; const onSuccessEvent = useEvent(onSuccess); @@ -69,16 +70,19 @@ export const useGetOne = ( // As the react-query cache is type-sensitive, we always stringify the identifier to get a match queryKey: [resource, 'getOne', { id: String(id), meta }], queryFn: queryParams => - dataProvider - .getOne(resource, { - id, - meta, - signal: - dataProvider.supportAbortSignal === true - ? queryParams.signal - : undefined, - }) - .then(({ data }) => data), + id == null + ? new Promise(() => {}) + : dataProvider + .getOne(resource, { + id, + meta, + signal: + dataProvider.supportAbortSignal === true + ? queryParams.signal + : undefined, + }) + .then(({ data }) => data), + enabled: enabled ?? id != null, ...queryOptions, }); diff --git a/packages/ra-core/src/dataProvider/withLifecycleCallbacks.ts b/packages/ra-core/src/dataProvider/withLifecycleCallbacks.ts index 63e7524fe5b..4e17dc475f2 100644 --- a/packages/ra-core/src/dataProvider/withLifecycleCallbacks.ts +++ b/packages/ra-core/src/dataProvider/withLifecycleCallbacks.ts @@ -202,7 +202,7 @@ export const withLifecycleCallbacks = ( getMany: async function ( resource: string, - params: GetManyParams + params: GetManyParams ) { let newParams = params; diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index 994b39fa645..1b43118b589 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -99,7 +99,7 @@ export type DataProvider = { getMany: ( resource: ResourceType, - params: GetManyParams & QueryFunctionContext + params: GetManyParams & QueryFunctionContext ) => Promise>; getManyReference: ( @@ -172,8 +172,8 @@ export interface GetOneResult { data: RecordType; } -export interface GetManyParams { - ids: Identifier[]; +export interface GetManyParams { + ids: RecordType['id'][]; meta?: any; signal?: AbortSignal; } diff --git a/packages/ra-data-localforage/src/index.ts b/packages/ra-data-localforage/src/index.ts index 5940210f6a8..bb7e76c9f72 100644 --- a/packages/ra-data-localforage/src/index.ts +++ b/packages/ra-data-localforage/src/index.ts @@ -1,6 +1,5 @@ /* eslint-disable import/no-anonymous-default-export */ import fakeRestProvider from 'ra-data-fakerest'; - import { CreateParams, DataProvider, @@ -111,7 +110,7 @@ export default async ( ) => baseDataProvider.getOne(resource, params), getMany: ( resource: string, - params: GetManyParams + params: GetManyParams ) => baseDataProvider.getMany(resource, params), getManyReference: ( resource: string,