Skip to content

Commit

Permalink
fix: cannot send custom params with send in usePagination, only c…
Browse files Browse the repository at this point in the history
…lone array and plain object in `deepClone` (#608)

* fix: fix that cannot receive custom params call send `send` in `usePagination`

* fix: fix to only clone array and plain object in `deepClone`
  • Loading branch information
JOU-amjs authored Dec 17, 2024
1 parent bcefee5 commit 9e95069
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-eels-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'alova': patch
---

fix that cannot receive custom params call send `send` in `usePagination`
5 changes: 5 additions & 0 deletions .changeset/ten-foxes-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@alova/shared': patch
---

fix to only clone array and plain object in `deepClone`
44 changes: 27 additions & 17 deletions packages/client/src/hooks/pagination/usePagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ const paginationAssert = createAssert('usePagination');
const indexAssert = (index: number, rawData: any[]) =>
paginationAssert(isNumber(index) && index < len(rawData), 'index must be a number that less than list length');

const parseSendArgs = (args: any[]) => [
args[args.length - 2], // refreshPage
args[args.length - 1], // isRefresh
args.slice(0, args.length - 2) // send args
];

export default <AG extends AlovaGenerics, ListData extends unknown[]>(
handler: (page: number, pageSize: number) => Method<AG>,
handler: (page: number, pageSize: number, ...args: any[]) => Method<AG>,
config: PaginationHookConfig<AG, ListData> = {}
) => {
const {
Expand Down Expand Up @@ -86,14 +92,13 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
const fetchStates = useFetcher<FetcherType<Alova<AG>>>({
__referingObj: referingObject,
updateState: falseValue,
force: ({ args }) => args[0]
force: ({ args }) => args[len(args) - 1]
});
const { loading, fetch, abort: abortFetch, onSuccess: onFetchSuccess } = fetchStates;
const fetchingRef = ref(loading);

const getHandlerMethod = (refreshPage: number | undefined = page.v) => {
const pageSizeVal = pageSize.v;
const handlerMethod = handler(refreshPage, pageSizeVal);
const getHandlerMethod = (refreshPage: number = page.v, customArgs: any[] = []) => {
const handlerMethod = handler(refreshPage, pageSize.v, ...customArgs);

// Define unified additional names to facilitate management
saveSnapshot(handlerMethod);
Expand Down Expand Up @@ -121,9 +126,12 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
(actionName: string) =>
(...args: any[]) =>
delegationActions.current[actionName](...args);
const states = useWatcher<AG, [page?: number, force?: boolean]>(
getHandlerMethod,
[...watchingStates, page.e, pageSize.e] as any,
const states = useWatcher(
(...args: any[]) => {
const [refreshPage, , customArgs] = parseSendArgs(args);
return getHandlerMethod(refreshPage, customArgs);
},
[...watchingStates, page.e, pageSize.e],
{
__referingObj: referingObject,
immediate,
Expand Down Expand Up @@ -207,9 +215,9 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
};

// Preload next page data
const fetchNextPage = async (rawData?: any[], force = falseValue) => {
const fetchNextPage = async (rawData: any[] | undefined, force: boolean, customArgs: any[] = []) => {
const nextPage = page.v + 1;
const fetchMethod = getHandlerMethod(nextPage);
const fetchMethod = getHandlerMethod(nextPage, customArgs);
if (
preloadNextPage &&
(await canPreload({
Expand All @@ -220,13 +228,13 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
forceRequest: force
}))
) {
promiseCatch(fetch(fetchMethod as Method, force), noop);
promiseCatch(fetch(fetchMethod as Method, ...customArgs, force), noop);
}
};
// Preload previous page data
const fetchPreviousPage = async (rawData: any[]) => {
const fetchPreviousPage = async (rawData: any[], customArgs: any[] = []) => {
const prevPage = page.v - 1;
const fetchMethod = getHandlerMethod(prevPage);
const fetchMethod = getHandlerMethod(prevPage, customArgs);
if (
preloadPreviousPage &&
(await canPreload({
Expand All @@ -235,7 +243,7 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
fetchMethod
}))
) {
promiseCatch(fetch(fetchMethod as Method), noop);
promiseCatch(fetch(fetchMethod as Method, ...customArgs, undefinedValue), noop);
}
};
// If the returned data is smaller than the page size, it is considered the last page.
Expand Down Expand Up @@ -299,13 +307,14 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
const awaitResolve = ref(undefinedValue as GeneralFn | undefined);
const awaitReject = ref(undefinedValue as GeneralFn | undefined);
states
.onSuccess(({ data: rawData, args: [refreshPage, isRefresh], method }) => {
.onSuccess(({ data: rawData, args, method }) => {
const [refreshPage, isRefresh, customArgs] = parseSendArgs(args);
const { total: cachedTotal } = getSnapshotMethods(method) || {};
const typedRawData = rawData as any[];
total.v = cachedTotal !== undefinedValue ? cachedTotal : totalGetter(typedRawData);
if (!isRefresh) {
fetchPreviousPage(typedRawData);
fetchNextPage(typedRawData);
fetchPreviousPage(typedRawData, customArgs);
fetchNextPage(typedRawData, falseValue, customArgs);
}

const pageSizeVal = pageSize.v;
Expand Down Expand Up @@ -583,6 +592,7 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
return exposeProvider({
...states,
...objectify([data, page, pageCount, pageSize, total, isLastPage]),
send: (...args: any[]) => send(...args, undefinedValue, undefinedValue),

fetching: fetchStates.loading,
onFetchSuccess: fetchStates.onSuccess,
Expand Down
6 changes: 6 additions & 0 deletions packages/client/test/react/components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function Pagination({ getter, paginationConfig = {}, handleExposure = () => {} }
page,
pageSize,
isLastPage,
send,
update,
insert,
replace,
Expand Down Expand Up @@ -278,6 +279,11 @@ function Pagination({ getter, paginationConfig = {}, handleExposure = () => {} }
onClick={() => update({ data: [] })}>
btn
</button>
<button
role="customSend"
onClick={() => send('a', 1)}>
btn1
</button>
</div>
);
}
Expand Down
38 changes: 38 additions & 0 deletions packages/client/test/react/usePagination.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1686,4 +1686,42 @@ describe('react => usePagination', () => {
expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([0, 1, 2, 3, 100, 200, 300]));
});
});

test('should receive custom params from function send', async () => {
const successFn = vi.fn();
const fetchSuccessMockFn = vi.fn();
render(
<Pagination
getter={(page, pageSize) => getter1(page, pageSize)}
paginationConfig={{
total: (res: any) => res.total,
data: (res: any) => res.list,
immediate: false
}}
handleExposure={exposure => {
exposure.onSuccess(({ args }) => {
successFn(args);
});
exposure.onFetchSuccess(({ args }) => {
fetchSuccessMockFn(args);
});
}}
/>
);

await waitFor(() => {
expect(screen.getByRole('page')).toHaveTextContent('1');
expect(screen.getByRole('pageSize')).toHaveTextContent('10');
expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([]));
expect(screen.getByRole('total')).toHaveTextContent('');
expect(screen.getByRole('pageCount')).toHaveTextContent('');
expect(screen.getByRole('isLastPage')).toHaveTextContent('true');
});

fireEvent.click(screen.getByRole('customSend'));
await waitFor(() => {
expect(successFn).toHaveBeenCalledWith(['a', 1, undefined, undefined]);
expect(fetchSuccessMockFn).toHaveBeenCalledWith(['a', 1, false]);
});
});
});
8 changes: 7 additions & 1 deletion packages/client/test/vue/components/pagination.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@
">
btn1
</button>
<button
role="customSend"
@click="send('a', 1)">
btn1
</button>
</div>
</template>

Expand All @@ -257,7 +262,7 @@ type CollapsedAlovaGenerics = Omit<AlovaGenerics, 'StatesExport'> & {
};
const props = defineProps<{
getter: (page: number, pageSize: number) => Method<CollapsedAlovaGenerics>;
getter: (page: number, pageSize: number, ...args: any[]) => Method<CollapsedAlovaGenerics>;
paginationConfig?: PaginationHookConfig<CollapsedAlovaGenerics, unknown[]>;
handleExposure?: (exposure: ReturnType<typeof usePagination>) => void;
}>();
Expand Down Expand Up @@ -286,6 +291,7 @@ const {
page,
pageSize,
isLastPage,
send,
update,
insert,
replace,
Expand Down
42 changes: 40 additions & 2 deletions packages/client/test/vue/usePagination.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1396,7 +1396,7 @@ describe('vue => usePagination', () => {
});
});

test("load more mode paginated data don't need to preload when go to last page", async () => {
test("load more mode paginated data doesn't need to preload when go to last page", async () => {
const fetchMockFn = vi.fn();
render(Pagination, {
props: {
Expand Down Expand Up @@ -1676,7 +1676,7 @@ describe('vue => usePagination', () => {
});
});

test('should can be interrupted by middleware', async () => {
test('should be interrupted by middleware', async () => {
const successMockFn = vi.fn();
const errorMockFn = vi.fn();
const completedMockFn = vi.fn();
Expand All @@ -1702,4 +1702,42 @@ describe('vue => usePagination', () => {
expect(errorMockFn).not.toHaveBeenCalled();
expect(completedMockFn).not.toHaveBeenCalled();
});

test('should receive custom params from function send', async () => {
const successFn = vi.fn();
const fetchSuccessMockFn = vi.fn();
render(Pagination, {
props: {
getter: (page, pageSize) => getter1(page, pageSize),
paginationConfig: {
total: (res: any) => res.total,
data: (res: any) => res.list,
immediate: false
},
handleExposure(exposure) {
exposure.onSuccess(({ args }) => {
successFn(args);
});
exposure.onFetchSuccess(({ args }) => {
fetchSuccessMockFn(args);
});
}
}
});

await waitFor(() => {
expect(screen.getByRole('page')).toHaveTextContent('1');
expect(screen.getByRole('pageSize')).toHaveTextContent('10');
expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([]));
expect(screen.getByRole('total')).toHaveTextContent('');
expect(screen.getByRole('pageCount')).toHaveTextContent('');
expect(screen.getByRole('isLastPage')).toHaveTextContent('true');
});

fireEvent.click(screen.getByRole('customSend'));
await waitFor(() => {
expect(successFn).toHaveBeenCalledWith(['a', 1, undefined, undefined]);
expect(fetchSuccessMockFn).toHaveBeenCalledWith(['a', 1, false]);
});
});
});
2 changes: 1 addition & 1 deletion packages/shared/src/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ export const deepClone = <T>(obj: T): T => {
return mapItem(obj, deepClone) as T;
}

if (isPlainObject(obj)) {
if (isPlainObject(obj) && obj.constructor === ObjectCls) {
const clone = {} as T;
forEach(objectKeys(obj), key => {
clone[key as keyof T] = deepClone(obj[key as keyof T]);
Expand Down
15 changes: 14 additions & 1 deletion packages/shared/test/function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,18 +663,31 @@ describe('shared functions', () => {
expect(clonedArray[1]).not.toBe(originalArray[1]);
expect(clonedArray[2]).not.toBe(originalArray[2]);

class J {}

// objects
const originalObject = {
a: 1,
b: { c: 2 },
d: [3, 4],
e: null
e: null,
f: undefined,
g: () => {},
h: Symbol('h'),
i: new Date(),
j: new J()
};
const clonedObject = deepClone(originalObject);
expect(clonedObject).toStrictEqual(originalObject);
expect(clonedObject).not.toBe(originalObject);
expect(clonedObject.b).not.toBe(originalObject.b);
expect(clonedObject.d).not.toBe(originalObject.d);
expect(clonedObject.e).toBeNull();
expect(clonedObject.f).toBeUndefined();
expect(clonedObject.g).toBe(originalObject.g);
expect(clonedObject.h).toBe(originalObject.h);
expect(clonedObject.i).toBe(originalObject.i);
expect(clonedObject.j).toBe(originalObject.j);

// nested complex structures
const originalNested = {
Expand Down

0 comments on commit 9e95069

Please sign in to comment.