Skip to content

Commit

Permalink
fix: correct key listData to data in usePagination (#514)
Browse files Browse the repository at this point in the history
  • Loading branch information
JOU-amjs authored Aug 23, 2024
1 parent 743bccd commit b6816d5
Show file tree
Hide file tree
Showing 17 changed files with 137 additions and 86 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-radios-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'alova': patch
---

modify key `listData` to `data` in `usePagination`
6 changes: 5 additions & 1 deletion packages/alova/typings/clienthook/general.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AlovaGenerics,
FetchRequestState,
FrontRequestState,
MergedStatesMap,
Method,
Progress,
ReferingObject,
Expand Down Expand Up @@ -226,7 +227,7 @@ export interface Hook {
m?: Method;

/** saveStatesFns */
sf: ((frontStates: FrontRequestState) => void)[];
sf: ((frontStates: MergedStatesMap) => void)[];

/** removeStatesFns */
rf: (() => void)[];
Expand Down Expand Up @@ -255,4 +256,7 @@ export interface Hook {

/** refering object */
ro: ReferingObject;

/** managed states */
ms: MergedStatesMap;
}
2 changes: 1 addition & 1 deletion packages/alova/typings/clienthook/hooks/updateState.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export type UpdateStateCollection<Responded> = {
* @returns is updated
*/
export declare function updateState<Responded>(
matcher: Method<AlovaGenerics<Responded>>,
matcher: Method<AlovaGenerics<Responded extends unknown ? any : Responded>>,
handleUpdate: UpdateStateCollection<Responded>['data'] | UpdateStateCollection<Responded>
): Promise<boolean>;
6 changes: 4 additions & 2 deletions packages/alova/typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EventManager } from '@alova/shared/createEventManager';
import { FrameworkState } from '@alova/shared/FrameworkState';

export interface AlovaGenerics<
R = any,
Expand Down Expand Up @@ -207,11 +208,12 @@ export interface FrontRequestState<L = any, R = any, E = any, D = any, U = any>
data: R;
}

export type MergedStatesMap = Record<string, FrameworkState<any, string>>;
export interface EffectRequestParams<E> {
handler: (...args: any[]) => void;
removeStates: () => void;
saveStates: (frontStates: FrontRequestState) => void;
frontStates: FrontRequestState;
saveStates: (frontStates: MergedStatesMap) => void;
frontStates: MergedStatesMap;
watchingStates?: E[];
immediate: boolean;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/client/src/hooks/core/implements/createHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ export default <AG extends AlovaGenerics>(
c,

/** referingObject */
ro
ro,

/** managedStates */
ms: {}
}) as Hook;
15 changes: 7 additions & 8 deletions packages/client/src/hooks/core/implements/createRequestState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
statesHookHelper
} from '@alova/shared/function';
import { PromiseCls, falseValue, forEach, isArray, promiseCatch, trueValue, undefinedValue } from '@alova/shared/vars';
import type { AlovaGlobalCacheAdapter, FrontRequestState, Method, Progress } from 'alova';
import type { AlovaGlobalCacheAdapter, Method, Progress } from 'alova';
import { AlovaGenerics, globalConfigMap, promiseStatesHook } from 'alova';
import {
AlovaCompleteEvent,
Expand Down Expand Up @@ -97,16 +97,14 @@ export default function createRequestState<AG extends AlovaGenerics, Config exte
};
// 将外部传入的受监管的状态一同放到frontStates集合中
const { managedStates = {} } = useHookConfig as FrontRequestHookConfig<AG>;
const managedStatesProxy = mapObject(managedStates, (state, key) => transformState2Proxy(state, key));
const data = create((isFn(initialData) ? initialData() : initialData) as AG['Responded'], 'data');
const loading = create(initialLoading, 'loading');
const error = create(undefinedValue as Error | undefined, 'error');
const downloading = create({ ...progress }, 'downloading');
const uploading = create({ ...progress }, 'uploading');

const frontStates = {
...mapObject(managedStates, (state, key) => transformState2Proxy(state, key)),
...objectify([data, loading, error, downloading, uploading])
};
const frontStates = objectify([data, loading, error, downloading, uploading]);
const eventManager = createEventManager<{
success: AlovaSuccessEvent<AG>;
error: AlovaErrorEvent<AG>;
Expand All @@ -117,10 +115,11 @@ export default function createRequestState<AG extends AlovaGenerics, Config exte

/**
* ## react ##每次执行函数都需要重置以下项
* */
*/
hookInstance.fs = frontStates;
hookInstance.em = eventManager;
hookInstance.c = useHookConfig;
hookInstance.ms = managedStatesProxy;

const hasWatchingStates = watchingStates !== undefinedValue;
// 初始化请求事件
Expand Down Expand Up @@ -159,8 +158,8 @@ export default function createRequestState<AG extends AlovaGenerics, Config exte
? (delay: number) => debouncingSendHandler.current(delay, referingObject, methodHandler)
: () => wrapEffectRequest(referingObject),
removeStates: () => forEach(hookInstance.rf, fn => fn()),
saveStates: (states: FrontRequestState) => forEach(hookInstance.sf, fn => fn(states)),
frontStates,
saveStates: states => forEach(hookInstance.sf, fn => fn(states)),
frontStates: { ...frontStates, ...managedStatesProxy },
watchingStates,
immediate: immediate ?? trueValue
});
Expand Down
13 changes: 3 additions & 10 deletions packages/client/src/hooks/core/implements/stateCache.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { FrameworkState } from '@alova/shared/FrameworkState';
import { deleteAttr } from '@alova/shared/vars';
import type { FrontRequestState, Progress } from 'alova';
import type { MergedStatesMap } from 'alova';
import { Hook } from '~/typings/clienthook';

// 状态数据缓存
interface CacheItem {
s: FrontRequestState<
FrameworkState<boolean, 'loading'>,
FrameworkState<any, 'data'>,
FrameworkState<Error | undefined, 'error'>,
FrameworkState<Progress, 'downloading'>,
FrameworkState<Progress, 'uploading'>
>;
s: MergedStatesMap;
h: Hook;
}
const stateCache: Record<string, Record<string, CacheItem>> = {};
Expand All @@ -33,7 +26,7 @@ export const getStateCache = (namespace: string, key: string) => {
* @param key 请求key值
* @param data 缓存数据
*/
export const setStateCache = (namespace: string, key: string, data: FrontRequestState, hookInstance: Hook) => {
export const setStateCache = (namespace: string, key: string, data: MergedStatesMap, hookInstance: Hook) => {
const cachedState = (stateCache[namespace] = stateCache[namespace] || {});
cachedState[key] = {
s: data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
sloughConfig
} from '@alova/shared/function';
import { falseValue, promiseResolve, promiseThen, pushItem, trueValue, undefinedValue } from '@alova/shared/vars';
import { AlovaGenerics, FrontRequestState, Method, Progress, queryCache } from 'alova';
import { AlovaGenerics, Method, Progress, queryCache } from 'alova';
import {
AlovaFetcherMiddleware,
AlovaFrontMiddleware,
Expand Down Expand Up @@ -40,7 +40,7 @@ export default function useHookToSendRequest<AG extends AlovaGenerics>(
) {
const currentHookAssert = coreHookAssert(hookInstance.ht);
let methodInstance = getHandlerMethod(methodHandler, currentHookAssert, sendCallingArgs);
const { fs: frontStates, ht: hookType, c: useHookConfig } = hookInstance;
const { fs: frontStates, ht: hookType, c: useHookConfig, ms: managedStates } = hookInstance;
const { loading: loadingState, data: dataState, error: errorState } = frontStates;
const isFetcher = hookType === EnumHookType.USE_FETCHER;
const { force: forceRequest = falseValue, middleware = defaultMiddleware } = useHookConfig as
Expand All @@ -67,8 +67,8 @@ export default function useHookToSendRequest<AG extends AlovaGenerics>(
let controlledLoading = falseValue;
if (!isFetcher) {
// 将初始状态存入缓存以便后续更新
saveStates = (frontStates: FrontRequestState) => setStateCache(id, methodKey, frontStates, hookInstance);
saveStates(frontStates);
saveStates = frontStates => setStateCache(id, methodKey, frontStates, hookInstance);
saveStates({ ...frontStates, ...managedStates });

// 设置状态移除函数,将会传递给hook内的effectRequest,它将被设置在组件卸载时调用
removeStates = () => removeStateCache(id, methodKey);
Expand Down
6 changes: 2 additions & 4 deletions packages/client/src/hooks/pagination/usePagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
// 初始化fetcher
const fetchStates = useFetcher<FetcherType<Alova<AG>>>({
__referingObj: referingObject,
updateState: falseValue,
force: ({ args }) => args[0]
});
const { loading, fetch, abort: abortFetch, onSuccess: onFetchSuccess } = fetchStates;
Expand Down Expand Up @@ -126,10 +127,7 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
__referingObj: referingObject,
immediate,
initialData,
managedStates: {
...objectify([page, pageSize, total], 's'),
listData: data.s
},
managedStates: objectify([data, page, pageSize, total], 's'),
middleware(ctx, next) {
(middleware as any)(
{
Expand Down
9 changes: 4 additions & 5 deletions packages/client/src/updateState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { getStateCache } from './hooks/core/implements/stateCache';
* @param handleUpdate 更新回调
* @returns 是否更新成功,未找到对应的状态时不会更新成功
*/
export default async function updateState<AG extends AlovaGenerics>(
matcher: Method<AG>,
handleUpdate: ((data: AG['Responded']) => any) | UpdateStateCollection<AG['Responded']>
export default async function updateState<Responded = unknown>(
matcher: Method<AlovaGenerics<Responded extends unknown ? any : Responded>>,
handleUpdate: ((data: Responded) => any) | UpdateStateCollection<Responded>
) {
let updated = falseValue;

Expand All @@ -24,15 +24,14 @@ export default async function updateState<AG extends AlovaGenerics>(
const { id } = getContext(matcher);
const { s: frontStates, h: hookInstance } = getStateCache(id, methodKey);
const updateStateCollection = isFn(handleUpdate)
? ({ data: handleUpdate } as UpdateStateCollection<AG['Responded']>)
? ({ data: handleUpdate } as UpdateStateCollection<Responded>)
: handleUpdate;

let updatedDataColumnData = undefinedValue as any;
if (frontStates) {
// 循环遍历更新数据,并赋值给受监管的状态
forEach(objectKeys(updateStateCollection), stateName => {
coreAssert(stateName in frontStates, `state named \`${stateName}\` is not found`);
coreAssert(!objectKeys(frontStates).slice(-4).includes(stateName), 'can not update preset states');
const targetStateProxy = frontStates[stateName as keyof typeof frontStates];
let updatedData = updateStateCollection[stateName as keyof typeof updateStateCollection](targetStateProxy.v);

Expand Down
42 changes: 21 additions & 21 deletions packages/client/test/react/core/update-state.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ describe('update cached response data by user in react', () => {
await screen.findByText(/unit-test/);

// 延迟检查页面是否有更新
await delay(100);
expect(screen.getByRole('path')).toHaveTextContent('/unit-test-updated');
await waitFor(() => {
expect(screen.getByRole('path')).toHaveTextContent('/unit-test-updated');
});
const cacheData = alova.l2Cache.get('placeholder-data') as any;
expect(cacheData.path).toBe('/unit-test-updated');
});
Expand Down Expand Up @@ -118,34 +119,29 @@ describe('update cached response data by user in react', () => {
render((<Page />) as ReactElement<any, any>);
await screen.findByText(/unit-test/);

// 预设状态不能更新
expect(() =>
updateState(Get, {
loading: () => true
})
).rejects.toThrow();

// 非状态数据不能更新
expect(() =>
await expect(() =>
updateState(Get, {
extraData2: () => 1
})
).rejects.toThrow();

// 未找到状态抛出错误
expect(() =>
await expect(() =>
updateState(Get, {
extraData3: () => 1
})
).rejects.toThrow();

act(() => {
// 更新成功
// 更新成功
delay().then(() => {
updateState(Get, {
extraData: () => 1
});
});
expect(screen.getByRole('extraData')).toHaveTextContent('1');
await waitFor(() => {
expect(screen.getByRole('extraData')).toHaveTextContent('1');
});
});

test('the request sent by the same use hook should have the same saved states', async () => {
Expand Down Expand Up @@ -189,28 +185,32 @@ describe('update cached response data by user in react', () => {
});

// 执行了两次不同参数的请求后,验证两次请求是否缓存了相同的states
act(() => {
delay().then(() => {
updateState(getter('a'), {
data: d => ({
...d,
path: '/path-str-a'
})
});
});
expect(screen.getByRole('path')).toHaveTextContent('/path-str-a');
act(() => {
await waitFor(() => {
expect(screen.getByRole('path')).toHaveTextContent('/path-str-a');
});
delay().then(() => {
updateState(getter('b'), {
data: d => ({
...d,
path: '/path-str-b'
})
});
});
expect(screen.getByRole('path')).toHaveTextContent('/path-str-b');
await waitFor(() => {
expect(screen.getByRole('path')).toHaveTextContent('/path-str-b');

// 两处缓存的状态应该都是最新值
expect(getStateCache(alova.id, key(getter('a'))).s.data.v.path).toBe('/path-str-b');
expect(getStateCache(alova.id, key(getter('b'))).s.data.v.path).toBe('/path-str-b');
// 两处缓存的状态应该都是最新值
expect(getStateCache(alova.id, key(getter('a'))).s.data.v.path).toBe('/path-str-b');
expect(getStateCache(alova.id, key(getter('b'))).s.data.v.path).toBe('/path-str-b');
});
});

test('all saved states in unmounted component will be removed', async () => {
Expand Down
42 changes: 41 additions & 1 deletion packages/client/test/react/usePagination.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mockRequestAdapter, setMockListData, setMockListWithSearchData, setMockShortListData } from '#/mockData';
import { accessAction, actionDelegationMiddleware } from '@/index';
import { accessAction, actionDelegationMiddleware, updateState } from '@/index';
import { GeneralFn } from '@alova/shared/types';
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
Expand Down Expand Up @@ -1639,4 +1639,44 @@ describe('react => usePagination', () => {
expect(successMockFn).toHaveBeenCalledTimes(2);
});
});

test('should update state data when call `updateState` function', async () => {
const initialPageSize = 4;
render(
<Pagination
getter={getter1}
paginationConfig={{
data: (res: any) => res.list,
append: true,
initialPageSize
}}
/>
);

await waitFor(() => {
expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([0, 1, 2, 3]));
});

let updated: boolean;
delay()
.then(() =>
updateState<number[]>(getter1(1, initialPageSize), {
data: list => [...list, 100, 200],
total: old => old + 10
})
)
.then(res => {
updated = res;
});
await waitFor(() => {
expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([0, 1, 2, 3, 100, 200]));
expect(screen.getByRole('total')).toHaveTextContent('310');
expect(updated).toBeTruthy();
});

delay().then(() => updateState<number[]>(getter1(1, initialPageSize), list => [...list, 300]));
await waitFor(() => {
expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([0, 1, 2, 3, 100, 200, 300]));
});
});
});
Loading

0 comments on commit b6816d5

Please sign in to comment.