diff --git a/.changeset/four-ads-sin.md b/.changeset/four-ads-sin.md new file mode 100644 index 0000000000000..7646f89b75bdf --- /dev/null +++ b/.changeset/four-ads-sin.md @@ -0,0 +1,9 @@ +--- +"@refinedev/core": patch +--- + +fix(core): correct `useCustom` return type to reflect undefined data during loading #7088 + +Fixed a type safety issue where `useCustom` returned an empty object (`{}`) while loading but TypeScript type was `CustomResponse["data"]`. This caused runtime errors like `result.data.map is not a function` when accessing data without proper checks. Now `result.data` returns `undefined` while loading with type `CustomResponse["data"] | undefined`, requiring proper null checks like `result.data?.map()`. + +Resolves #7088 diff --git a/packages/core/src/hooks/data/useCustom.spec.tsx b/packages/core/src/hooks/data/useCustom.spec.tsx index 8d01ce0d7172d..40e6f6c92c305 100644 --- a/packages/core/src/hooks/data/useCustom.spec.tsx +++ b/packages/core/src/hooks/data/useCustom.spec.tsx @@ -27,9 +27,47 @@ describe("useCustom Hook", () => { const { data } = result.current.result; + expect(data).toBeDefined(); expect(data).toHaveLength(2); }); + it("should return undefined data while loading", async () => { + const { result } = renderHook( + () => + useCustom({ + url: "remoteUrl", + method: "get", + }), + { + wrapper: TestWrapper({ + dataProvider: { + default: { + ...MockJSONServer.default, + custom: () => { + return new Promise((res) => { + setTimeout(() => res({ data: [1, 2, 3] } as any), 100); + }); + }, + }, + }, + resources: [{ name: "posts" }], + }), + }, + ); + + // While loading, data should be undefined + expect(result.current.query.isPending).toBeTruthy(); + expect(result.current.result.data).toBeUndefined(); + + await waitFor(() => { + expect(result.current.query.isSuccess).toBeTruthy(); + }); + + // After loading, data should be defined + expect(result.current.result.data).toBeDefined(); + expect(result.current.result.data).toEqual([1, 2, 3]); + }); + describe("without custom query key", () => { const config = { sorters: [{ field: "id", order: "desc" }] } as any; const meta = { meta: "meta" }; diff --git a/packages/core/src/hooks/data/useCustom.ts b/packages/core/src/hooks/data/useCustom.ts index 1c0299bbe38fc..4ddd4fab2e1ed 100644 --- a/packages/core/src/hooks/data/useCustom.ts +++ b/packages/core/src/hooks/data/useCustom.ts @@ -110,12 +110,10 @@ export type UseCustomProps = { export type UseCustomReturnType = { query: QueryObserverResult, TError>; result: { - data: CustomResponse["data"]; + data: CustomResponse["data"] | undefined; }; } & UseLoadingOvertimeReturnType; -const EMPTY_OBJECT = Object.freeze({}) as any; - export const useCustom = < TQueryFnData extends BaseRecord = BaseRecord, TError extends HttpError = HttpError, @@ -233,7 +231,7 @@ export const useCustom = < return { query: queryResponse, result: { - data: queryResponse.data?.data || EMPTY_OBJECT, + data: queryResponse.data?.data, }, overtime: { elapsedTime }, };