Skip to content

Commit 2ab03eb

Browse files
authored
feat(useList): develop useList (#297)
* feat(useList): develop useList * feat(useList): support clear and options
1 parent 683667c commit 2ab03eb

File tree

8 files changed

+586
-0
lines changed

8 files changed

+586
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ export { default as SpreadSheet } from './spreadSheet';
2424
export { default as StatusTag } from './statusTag';
2525
export { default as useWindowSwitchListener } from './switchWindow';
2626
export { default as useCookieListener } from './cookies';
27+
export { default as useList } from './useList';
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { act, waitFor } from '@testing-library/react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import useList from '..';
4+
5+
describe('Test useList hook', () => {
6+
it('Should get initial data with default params', () => {
7+
const fetcher = jest.fn().mockResolvedValue({
8+
total: 1,
9+
data: [{ uuid: 1 }],
10+
});
11+
12+
const { result } = renderHook(() => useList(fetcher, { current: 1, pageSize: 20 }));
13+
14+
expect(fetcher).toBeCalledTimes(1);
15+
waitFor(() => {
16+
expect(result.current.data.length).toBe(1);
17+
expect(result.current.params).toBe(
18+
expect.objectContaining({ current: 1, pageSize: 20, total: 1 })
19+
);
20+
});
21+
});
22+
23+
it('Should get data after mutating', () => {
24+
const fetcher = jest.fn().mockResolvedValue({
25+
total: 1,
26+
data: [{ uuid: 1 }],
27+
});
28+
29+
const { result } = renderHook(() =>
30+
useList(fetcher, { current: 1, pageSize: 20, search: '' })
31+
);
32+
expect(fetcher).toBeCalledTimes(1);
33+
34+
act(() => {
35+
result.current.mutate({ search: 'test' });
36+
});
37+
38+
waitFor(() => {
39+
expect(fetcher).toBeCalledTimes(2);
40+
expect(result.current.params).toBe(expect.objectContaining({ search: 'test' }));
41+
});
42+
});
43+
44+
it('Should support revalidate after mutating', () => {
45+
const fetcher = jest.fn().mockResolvedValue({
46+
total: 1,
47+
data: [{ uuid: 1 }],
48+
});
49+
50+
const { result } = renderHook(() =>
51+
useList(fetcher, { current: 1, pageSize: 20, search: '' })
52+
);
53+
expect(fetcher).toBeCalledTimes(1);
54+
55+
act(() => {
56+
result.current.mutate({ search: 'test' }, { revalidate: false });
57+
});
58+
59+
waitFor(() => {
60+
expect(result.current.params).toBe(expect.objectContaining({ search: 'test' }));
61+
});
62+
});
63+
64+
it('Should support get data with current params', () => {
65+
const fetcher = jest.fn().mockResolvedValue({
66+
total: 1,
67+
data: [{ uuid: 1 }],
68+
});
69+
70+
const { result } = renderHook(() =>
71+
useList(fetcher, { current: 1, pageSize: 20, search: '' })
72+
);
73+
expect(fetcher).toBeCalledTimes(1);
74+
75+
act(() => {
76+
result.current.mutate();
77+
});
78+
79+
waitFor(() => {
80+
expect(fetcher).toBeCalledTimes(2);
81+
});
82+
});
83+
});

src/useList/demos/basic.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React from 'react';
2+
import { Result, Table, Input } from 'antd';
3+
import { useList } from 'dt-react-component';
4+
import getMockData, { type MockData } from './data';
5+
import type { Fetcher } from 'dt-react-component/useList';
6+
7+
const fetcher: Fetcher<MockData, { current: number; pageSize: number; search?: string }> = (
8+
params
9+
) => {
10+
return new Promise<{
11+
data: MockData[];
12+
total: number;
13+
}>((resolve) => {
14+
setTimeout(() => {
15+
resolve(getMockData(params));
16+
}, 150);
17+
});
18+
};
19+
20+
export default () => {
21+
const { error, params, loading, data, mutate } = useList(fetcher, { current: 1, pageSize: 20 });
22+
23+
if (error) return <Result status={500} />;
24+
25+
return (
26+
<>
27+
<Input.Search
28+
value={params.search}
29+
onChange={(e) => mutate({ search: e.target.value }, { revalidate: false })}
30+
onSearch={() => mutate()}
31+
style={{ marginBottom: 12 }}
32+
/>
33+
<Table
34+
loading={loading}
35+
columns={[
36+
{
37+
key: 'name',
38+
title: 'name',
39+
dataIndex: 'name',
40+
},
41+
{
42+
key: 'address',
43+
title: 'address',
44+
dataIndex: 'address',
45+
},
46+
{
47+
key: 'company',
48+
title: 'company',
49+
dataIndex: 'company',
50+
},
51+
{
52+
key: 'gender',
53+
title: 'gender',
54+
dataIndex: 'gender',
55+
},
56+
{
57+
key: 'weight',
58+
title: 'weight',
59+
dataIndex: 'weight',
60+
},
61+
]}
62+
onChange={(pagination) =>
63+
mutate({ current: pagination.current, pageSize: pagination.pageSize })
64+
}
65+
size="small"
66+
scroll={{ y: 200 }}
67+
dataSource={data}
68+
pagination={{
69+
current: params.current,
70+
pageSize: params.pageSize,
71+
total: params.total,
72+
}}
73+
rowKey="uuid"
74+
bordered
75+
/>
76+
</>
77+
);
78+
};

src/useList/demos/data.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { faker } from '@faker-js/faker';
2+
3+
export type MockData = {
4+
uuid: string;
5+
name: string;
6+
address: string;
7+
company: string;
8+
gender: string;
9+
weight: number;
10+
};
11+
12+
const data: MockData[] = [];
13+
14+
export default async function getMockData({ current, pageSize, search, sorter, filters }: any) {
15+
if (!data.length) {
16+
data.push(
17+
...Array.from({ length: 200 }).map(() => {
18+
return {
19+
uuid: faker.datatype.uuid(),
20+
name: faker.internet.userName(),
21+
address: faker.address.cityName(),
22+
company: faker.company.bs(),
23+
gender: faker.name.sex(),
24+
weight: faker.datatype.number({
25+
max: 200,
26+
min: 80,
27+
}),
28+
};
29+
})
30+
);
31+
}
32+
33+
const start = (current - 1) * pageSize;
34+
35+
let next = data.filter((i) => (search ? i.name.includes(search) : true));
36+
37+
if (filters) {
38+
next = next.filter((i) => (filters as string[]).includes(i.gender));
39+
}
40+
41+
if (sorter) {
42+
next.sort((a: any, b: any) => {
43+
if (sorter[0].asc) {
44+
return a[sorter[0].field] - b[sorter[0].field];
45+
}
46+
47+
return b[sorter[0].field] - a[sorter[0].field];
48+
});
49+
}
50+
51+
return {
52+
data: next.slice(start, start + pageSize),
53+
total: next.length,
54+
};
55+
}

src/useList/demos/options.tsx

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Result, Table, Drawer, Button } from 'antd';
3+
import { useList } from 'dt-react-component';
4+
import { faker } from '@faker-js/faker';
5+
import getMockData, { type MockData } from './data';
6+
import type { Fetcher } from 'dt-react-component/useList';
7+
8+
const fetcher: Fetcher<MockData, {}> = (params) => {
9+
return new Promise<{
10+
data: MockData[];
11+
total: number;
12+
}>((resolve) => {
13+
setTimeout(() => {
14+
resolve(getMockData(params));
15+
}, 150);
16+
});
17+
};
18+
19+
export default () => {
20+
const { error, params, loading, data, mutate } = useList(fetcher, { current: 1, pageSize: 20 });
21+
22+
const {
23+
loading: detailLoading,
24+
data: detailData,
25+
mutate: detailMutate,
26+
clear,
27+
} = useList(
28+
() =>
29+
new Promise<{ total: number; data: { name: string }[] }>((resolve) => {
30+
setTimeout(() => {
31+
resolve({
32+
data: new Array(5).fill(1).map(() => {
33+
return {
34+
name: faker.internet.userName(),
35+
};
36+
}),
37+
total: 5,
38+
});
39+
}, 100);
40+
}),
41+
{},
42+
{ immediate: false }
43+
);
44+
45+
const [current, setCurrent] = useState<number | undefined>(undefined);
46+
const [visible, setVisible] = useState(false);
47+
48+
useEffect(() => {
49+
if (current) {
50+
detailMutate();
51+
}
52+
}, [current]);
53+
54+
if (error) return <Result status={500} />;
55+
56+
return (
57+
<div style={{ position: 'relative', overflow: 'hidden' }}>
58+
<Table
59+
loading={loading}
60+
columns={[
61+
{
62+
key: 'name',
63+
title: 'name',
64+
dataIndex: 'name',
65+
render(text) {
66+
return (
67+
<Button
68+
type="link"
69+
onClick={() => {
70+
setVisible(true);
71+
setCurrent(text);
72+
}}
73+
>
74+
{text}
75+
</Button>
76+
);
77+
},
78+
},
79+
{
80+
key: 'address',
81+
title: 'address',
82+
dataIndex: 'address',
83+
},
84+
{
85+
key: 'company',
86+
title: 'company',
87+
dataIndex: 'company',
88+
},
89+
{
90+
key: 'gender',
91+
title: 'gender',
92+
dataIndex: 'gender',
93+
},
94+
{
95+
key: 'weight',
96+
title: 'weight',
97+
dataIndex: 'weight',
98+
},
99+
]}
100+
onChange={(pagination) =>
101+
mutate({ current: pagination.current, pageSize: pagination.pageSize })
102+
}
103+
size="small"
104+
scroll={{ y: 200 }}
105+
dataSource={data}
106+
pagination={{
107+
current: params.current,
108+
pageSize: params.pageSize,
109+
total: params.total,
110+
}}
111+
rowKey="uuid"
112+
bordered
113+
/>
114+
<Drawer
115+
visible={visible}
116+
title="detail"
117+
mask={false}
118+
onClose={() => {
119+
setVisible(false);
120+
}}
121+
afterVisibleChange={(visible) => {
122+
if (!visible) {
123+
setCurrent(undefined);
124+
clear();
125+
}
126+
}}
127+
getContainer={false}
128+
>
129+
<Table
130+
bordered
131+
size="small"
132+
loading={detailLoading}
133+
columns={[{ key: 'name', title: 'name', dataIndex: 'name' }]}
134+
dataSource={detailData}
135+
pagination={false}
136+
/>
137+
</Drawer>
138+
</div>
139+
);
140+
};

0 commit comments

Comments
 (0)