Skip to content

Commit

Permalink
fix(core): add checks for ids and resource to useMany (#6618) (R…
Browse files Browse the repository at this point in the history
…esolves #6617)
  • Loading branch information
aliemir authored Jan 2, 2025
1 parent 5b01e8f commit 5377e7d
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 3 deletions.
11 changes: 11 additions & 0 deletions .changeset/unlucky-rules-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@refinedev/core": patch
---

fix(core): add missing checks and warnings for `ids` and `resource` props in `useMany` hook

Added checks for `ids` and `resource` props to check in runtime if they are valid or not.

`useMany` will warn if `ids` or `resource` props are missing unless the query is manually enabled through `queryOptions.enabled` prop.

[Resolves #6617](https://github.com/refinedev/refine/issues/6617)
111 changes: 111 additions & 0 deletions packages/core/src/hooks/data/useMany.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {

import type { IRefineContextProvider } from "../../contexts/refine/types";
import { useMany } from "./useMany";
import type { BaseKey } from "@contexts/data/types";
import * as warnOnce from "warn-once";

const mockRefineProvider: IRefineContextProvider = {
hasDashboard: false,
Expand Down Expand Up @@ -963,4 +965,113 @@ describe("useMany Hook", () => {
);
});
});

describe("should require `ids` and `resource` props", () => {
it("should require `ids` prop", () => {
const warnMock = jest.spyOn(console, "warn").mockImplementation(() => {});

const getManyMock = jest.fn().mockResolvedValue({
data: [],
});

const result = renderHook(
() =>
useMany({
resource: "posts",
ids: undefined as unknown as BaseKey[],
}),
{
wrapper: TestWrapper({
dataProvider: {
default: {
...MockJSONServer.default,
getMany: getManyMock,
},
},
resources: [{ name: "posts" }],
}),
},
);

expect(result.result.current.isLoading).toBeTruthy();
expect(result.result.current.fetchStatus).toBe("idle");
expect(getManyMock).not.toHaveBeenCalled();
expect(warnMock).toHaveBeenCalledWith(
expect.stringContaining('[useMany]: Missing "ids" prop.'),
);

warnMock.mockClear();
});

it("should require `resource` prop", () => {
const warnMock = jest.spyOn(console, "warn").mockImplementation(() => {});

const getManyMock = jest.fn().mockResolvedValue({
data: [],
});

const result = renderHook(
() =>
useMany({
resource: undefined as unknown as string,
ids: ["1", "2"],
}),
{
wrapper: TestWrapper({
dataProvider: {
default: {
...MockJSONServer.default,
getMany: getManyMock,
},
},
resources: [{ name: "posts" }],
}),
},
);

expect(result.result.current.isLoading).toBeTruthy();
expect(result.result.current.fetchStatus).toBe("idle");
expect(getManyMock).not.toHaveBeenCalled();
expect(warnMock).toHaveBeenCalledWith(
expect.stringContaining('[useMany]: Missing "resource" prop.'),
);

warnMock.mockClear();
});

it("should not warn if manually enabled", () => {
const warnMock = jest.spyOn(console, "warn").mockImplementation(() => {});

const getManyMock = jest.fn().mockResolvedValue({
data: [],
});

const result = renderHook(
() =>
useMany({
resource: undefined as unknown as string,
ids: ["1", "2"],
queryOptions: {
enabled: true,
},
}),
{
wrapper: TestWrapper({
dataProvider: {
default: {
...MockJSONServer.default,
getMany: getManyMock,
},
},
resources: [{ name: "posts" }],
}),
},
);

expect(result.result.current.isLoading).toBeTruthy();
expect(result.result.current.fetchStatus).toBe("fetching");
expect(getManyMock).toHaveBeenCalled();
expect(warnMock).not.toHaveBeenCalled();
});
});
});
26 changes: 23 additions & 3 deletions packages/core/src/hooks/data/useMany.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
type UseLoadingOvertimeReturnType,
useLoadingOvertime,
} from "../useLoadingOvertime";
import warnOnce from "warn-once";

export type UseManyProps<TQueryFnData, TError, TData> = {
/**
Expand Down Expand Up @@ -133,17 +134,24 @@ export const useMany = <

const combinedMeta = getMeta({ resource, meta: preferredMeta });

const hasIds = Array.isArray(ids);
const hasResource = Boolean(resource?.name);
const manuallyEnabled = queryOptions?.enabled === true;

warnOnce(!hasIds && !manuallyEnabled, idsWarningMessage(ids, resource?.name));
warnOnce(!hasResource && !manuallyEnabled, resourceWarningMessage());

useResourceSubscription({
resource: identifier,
types: ["*"],
params: {
ids: ids,
ids: ids ?? [],
meta: combinedMeta,
metaData: combinedMeta,
subscriptionType: "useMany",
...liveParams,
},
channel: `resources/${resource.name}`,
channel: `resources/${resource?.name ?? ""}`,
enabled: isEnabled,
liveMode,
onLiveEvent,
Expand All @@ -163,7 +171,7 @@ export const useMany = <
.data(pickedDataProvider)
.resource(identifier)
.action("many")
.ids(...ids)
.ids(...(ids ?? []))
.params({
...(preferredMeta || {}),
})
Expand Down Expand Up @@ -193,6 +201,7 @@ export const useMany = <
),
);
},
enabled: hasIds && hasResource,
...queryOptions,
onSuccess: (data) => {
queryOptions?.onSuccess?.(data);
Expand Down Expand Up @@ -238,3 +247,14 @@ export const useMany = <

return { ...queryResponse, overtime: { elapsedTime } };
};

const idsWarningMessage = (
ids: BaseKey[],
resource: string,
) => `[useMany]: Missing "ids" prop. Expected an array of ids, but got "${typeof ids}". Resource: "${resource}"
See https://refine.dev/docs/data/hooks/use-many/#ids-`;

const resourceWarningMessage = () => `[useMany]: Missing "resource" prop. Expected a string, but got undefined.
See https://refine.dev/docs/data/hooks/use-many/#resource-`;

0 comments on commit 5377e7d

Please sign in to comment.