diff --git a/docs/guide/upgrade.en-US.md b/docs/guide/upgrade.en-US.md index 5f95f8ab48..4cf1a8bc73 100644 --- a/docs/guide/upgrade.en-US.md +++ b/docs/guide/upgrade.en-US.md @@ -43,7 +43,7 @@ The package.json will be: Now, ahooks in your project is still v4, and ahooks4 is v5: -```tsx +```tsx | pure import React from 'react'; import { useRequest } from 'ahooks'; // v3 import { useRequest } from 'ahooks4'; // v4 diff --git a/docs/guide/upgrade.zh-CN.md b/docs/guide/upgrade.zh-CN.md index edc73633c4..6b48ae6187 100644 --- a/docs/guide/upgrade.zh-CN.md +++ b/docs/guide/upgrade.zh-CN.md @@ -43,7 +43,7 @@ $ bun add ahooks4@npm:ahooks@4 现在,你项目中的 ahooks 还是 v3 版本,ahooks4 是 v4 版本: -```tsx +```tsx | pure import React from 'react'; import { useRequest } from 'ahooks'; // v3 import { useRequest } from 'ahooks4'; // v4 diff --git a/packages/hooks/src/createUseStorageState/index.ts b/packages/hooks/src/createUseStorageState/index.ts index c4f8e90df3..70a3e2cd9e 100644 --- a/packages/hooks/src/createUseStorageState/index.ts +++ b/packages/hooks/src/createUseStorageState/index.ts @@ -70,7 +70,10 @@ export function createUseStorageState(getStorage: () => Storage | undefined) { const updateState = (value?: SetState) => { const currentState = isFunction(value) ? value(state) : value; - setState(currentState); + + if (!listenStorageChange) { + setState(currentState); + } try { let newValue: string | null; @@ -126,5 +129,6 @@ export function createUseStorageState(getStorage: () => Storage | undefined) { return [state, useMemoizedFn(updateState)] as const; } + return useStorageState; } diff --git a/packages/hooks/src/useDynamicList/__tests__/index.test.ts b/packages/hooks/src/useDynamicList/__tests__/index.test.ts index 2da76fa026..e6509cfb1d 100644 --- a/packages/hooks/src/useDynamicList/__tests__/index.test.ts +++ b/packages/hooks/src/useDynamicList/__tests__/index.test.ts @@ -3,6 +3,15 @@ import useDynamicList from '../index'; describe('useDynamicList', () => { const setUp = (props: any): any => renderHook(() => useDynamicList(props)); + const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + afterEach(() => { + warnSpy.mockReset(); + }); + + afterAll(() => { + warnSpy.mockRestore(); + }); it('getKey should work', () => { const hook = setUp([1, 2, 3]); @@ -97,6 +106,18 @@ describe('useDynamicList', () => { hook.result.current.remove(7); }); expect(hook.result.current.list.length).toBe(7); + + // batch remove + act(() => { + hook.result.current.batchRemove(1); + }); + expect(warnSpy).toHaveBeenCalledWith( + '`indexes` parameter of `batchRemove` function expected to be an array, but got "number".', + ); + act(() => { + hook.result.current.batchRemove([0, 1, 2]); + }); + expect(hook.result.current.list.length).toBe(4); }); it('same items should have different keys', () => { diff --git a/packages/hooks/src/useDynamicList/demo/demo1.tsx b/packages/hooks/src/useDynamicList/demo/demo1.tsx index 0b65e6dd8c..ed9a10781e 100644 --- a/packages/hooks/src/useDynamicList/demo/demo1.tsx +++ b/packages/hooks/src/useDynamicList/demo/demo1.tsx @@ -7,12 +7,13 @@ */ import React from 'react'; -import { Input, Space } from 'antd'; +import { Button, Input, Space } from 'antd'; import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { useDynamicList } from 'ahooks'; export default () => { - const { list, remove, getKey, insert, replace } = useDynamicList(['David', 'Jack']); + const { list, remove, batchRemove, getKey, insert, replace } = useDynamicList(['David', 'Jack']); + const listIndexes = list.map((item, index) => index); const Row = (index: number, item: any) => ( @@ -27,11 +28,25 @@ export default () => { ); return ( - <> - - {list.map((ele, index) => Row(index, ele))} + + {list.map((ele, index) => Row(index, ele))} + + + -

{JSON.stringify(list)}

- +
{JSON.stringify([list])}
+
); }; diff --git a/packages/hooks/src/useDynamicList/index.ts b/packages/hooks/src/useDynamicList/index.ts index 1bcdd162cf..32d4987266 100644 --- a/packages/hooks/src/useDynamicList/index.ts +++ b/packages/hooks/src/useDynamicList/index.ts @@ -1,4 +1,5 @@ import { useCallback, useRef, useState } from 'react'; +import isDev from '../utils/isDev'; const useDynamicList = (initialList: T[] = []) => { const counterRef = useRef(-1); @@ -77,6 +78,37 @@ const useDynamicList = (initialList: T[] = []) => { }); }, []); + const batchRemove = useCallback((indexes: number[]) => { + if (!Array.isArray(indexes)) { + if (isDev) { + console.error( + `\`indexes\` parameter of \`batchRemove\` function expected to be an array, but got "${typeof indexes}".`, + ); + } + return; + } + if (!indexes.length) { + return; + } + + setList((prevList) => { + const newKeyList: number[] = []; + const newList = prevList.filter((item, index) => { + const shouldKeep = !indexes.includes(index); + + if (shouldKeep) { + newKeyList.push(getKey(index)); + } + + return shouldKeep; + }); + + keyList.current = newKeyList; + + return newList; + }); + }, []); + const move = useCallback((oldIndex: number, newIndex: number) => { if (oldIndex === newIndex) { return; @@ -150,6 +182,7 @@ const useDynamicList = (initialList: T[] = []) => { merge, replace, remove, + batchRemove, getKey, getIndex, move, diff --git a/packages/hooks/src/useFullscreen/demo/demo4.tsx b/packages/hooks/src/useFullscreen/demo/demo4.tsx index 0d4391c649..b8e5f07428 100644 --- a/packages/hooks/src/useFullscreen/demo/demo4.tsx +++ b/packages/hooks/src/useFullscreen/demo/demo4.tsx @@ -1,9 +1,9 @@ /** * title: Coexist with other full screen operations - * desc: The element's full screen may be modified by other scripts, don't worry, ahooks can work with them. + * description: The element's full screen may be modified by other scripts, don't worry, ahooks can work with them. * * title.zh-CN: 与其它全屏操作共存 - * desc.zh-CN: 元素的全屏情况可能被其它脚本修改,不用担心,ahooks 可以与它们共存。 + * description.zh-CN: 元素的全屏情况可能被其它脚本修改,不用担心,ahooks 可以与它们共存。 */ import React, { useRef } from 'react'; diff --git a/packages/hooks/src/useKeyPress/demo/demo8.tsx b/packages/hooks/src/useKeyPress/demo/demo8.tsx index 904e78b775..af613559ef 100644 --- a/packages/hooks/src/useKeyPress/demo/demo8.tsx +++ b/packages/hooks/src/useKeyPress/demo/demo8.tsx @@ -1,9 +1,9 @@ /** * title: Get the trigger key - * desc: Multiple shortcuts are registered by a hook, each corresponding to a different logic. + * description: Multiple shortcuts are registered by a hook, each corresponding to a different logic. * * title.zh-CN: 获取触发的按键 - * desc.zh-CN: 单个 hook 注册多个快捷键,每个快捷键对应不同逻辑。 + * description.zh-CN: 单个 hook 注册多个快捷键,每个快捷键对应不同逻辑。 */ import React, { useState } from 'react'; diff --git a/packages/hooks/src/useLocalStorageState/demo/demo4.tsx b/packages/hooks/src/useLocalStorageState/demo/demo4.tsx index ca1c914229..822d86852b 100644 --- a/packages/hooks/src/useLocalStorageState/demo/demo4.tsx +++ b/packages/hooks/src/useLocalStorageState/demo/demo4.tsx @@ -1,20 +1,21 @@ /** * title: Sync state with localStorage - * desc: When the stored value changes, all `useLocalStorageState` with the same `key` will synchronize their states, including different tabs of the same browser (try to open two tabs of this page, clicking a button on one page will automatically update the "count" on the other page). + * description: When the stored value changes, all `useLocalStorageState` with the same `key` will synchronize their states, including different tabs of the same browser (try to open two tabs of this page, clicking a button on one page will automatically update the "count" on the other page). * * title.zh-CN: 将 state 与 localStorage 保持同步 - * desc.zh-CN: 存储值变化时,所有 `key` 相同的 `useLocalStorageState` 会同步状态,包括同一浏览器不同 tab 之间(尝试打开两个此页面,点击其中一个页面的按钮,另一个页面的 count 会自动更新) + * description.zh-CN: 存储值变化时,所有 `key` 相同的 `useLocalStorageState` 会同步状态,包括同一浏览器不同 tab 之间(尝试打开两个此页面,点击其中一个页面的按钮,另一个页面的 count 会自动更新) */ import React from 'react'; +import { Button, Space } from 'antd'; import { useLocalStorageState } from 'ahooks'; export default function () { return ( - <> + - + ); } @@ -24,15 +25,10 @@ function Counter() { listenStorageChange: true, }); - const add = () => setCount(count! + 1); - const clear = () => setCount(); - return ( -
- - -
+ + + + ); } diff --git a/packages/hooks/src/useLocalStorageState/index.en-US.md b/packages/hooks/src/useLocalStorageState/index.en-US.md index 797ff77247..d57d9754dc 100644 --- a/packages/hooks/src/useLocalStorageState/index.en-US.md +++ b/packages/hooks/src/useLocalStorageState/index.en-US.md @@ -17,10 +17,7 @@ A Hook that store state into localStorage. - -### Sync state with localStorage - - + ## API diff --git a/packages/hooks/src/useLocalStorageState/index.zh-CN.md b/packages/hooks/src/useLocalStorageState/index.zh-CN.md index ed0ceb0e9a..76f4319bec 100644 --- a/packages/hooks/src/useLocalStorageState/index.zh-CN.md +++ b/packages/hooks/src/useLocalStorageState/index.zh-CN.md @@ -17,10 +17,7 @@ demo: - -### 将 state 与 localStorage 保持同步 - - + ## API diff --git a/packages/hooks/src/useRequest/docs/refreshDeps/demo/refreshDeps.tsx b/packages/hooks/src/useRequest/docs/refreshDeps/demo/refreshDeps.tsx index a9cb3020fd..222b19fe30 100644 --- a/packages/hooks/src/useRequest/docs/refreshDeps/demo/refreshDeps.tsx +++ b/packages/hooks/src/useRequest/docs/refreshDeps/demo/refreshDeps.tsx @@ -1,9 +1,9 @@ /** * title: Repeat last request - * desc: When the dependency array changes, use the previous parameters to make the request again. + * description: When the dependency array changes, use the previous parameters to make the request again. * * title.zh-CN: 重复上一次请求 - * desc.zh-CN: 依赖数组变化时,使用上一次的参数重新发起请求。 + * description.zh-CN: 依赖数组变化时,使用上一次的参数重新发起请求。 */ import React, { useState } from 'react'; diff --git a/packages/hooks/src/useSelections/__tests__/index.test.ts b/packages/hooks/src/useSelections/__tests__/index.test.ts index 156984aec8..15cfa76191 100644 --- a/packages/hooks/src/useSelections/__tests__/index.test.ts +++ b/packages/hooks/src/useSelections/__tests__/index.test.ts @@ -1,4 +1,5 @@ import { act, renderHook } from '@testing-library/react'; +import { useState } from 'react'; import useSelections from '../index'; import type { Options } from '../index'; @@ -193,4 +194,45 @@ describe('useSelections', () => { expect(result.current.selected).toEqual(_selected); expect(result.current.isSelected(_selectedItem)).toBe(true); }); + + it('clearAll should work correct', async () => { + const runCase = (data, newData, remainData) => { + const { result } = renderHook(() => { + const [list, setList] = useState(data); + const hook = useSelections(list, { + itemKey: 'id', + }); + + return { setList, hook }; + }); + const { setSelected, unSelectAll, clearAll } = result.current.hook; + + act(() => { + setSelected(data); + }); + expect(result.current.hook.selected).toEqual(data); + expect(result.current.hook.allSelected).toBe(true); + + act(() => { + result.current.setList(newData); + }); + expect(result.current.hook.allSelected).toBe(false); + + act(() => { + unSelectAll(); + }); + expect(result.current.hook.selected).toEqual(remainData); + + act(() => { + clearAll(); + }); + expect(result.current.hook.selected).toEqual([]); + expect(result.current.hook.allSelected).toEqual(false); + expect(result.current.hook.noneSelected).toBe(true); + expect(result.current.hook.partiallySelected).toBe(false); + }; + + runCase(_data, [3, 4, 5], [1, 2]); + runCase(_dataObj, [{ id: 3 }, { id: 4 }, { id: 5 }], [{ id: 1 }, { id: 2 }]); + }); }); diff --git a/packages/hooks/src/useSelections/demo/demo2.tsx b/packages/hooks/src/useSelections/demo/demo2.tsx index 521e0f3b74..1d6988994d 100644 --- a/packages/hooks/src/useSelections/demo/demo2.tsx +++ b/packages/hooks/src/useSelections/demo/demo2.tsx @@ -1,9 +1,9 @@ /** * title: Object array - * desc: When array items are object, you need to specify the field name for the unique key. + * description: When array items are object, you need to specify the field name for the unique key. * * title.zh-CN: 对象数组 - * desc.zh-CN: 数组项是对象时,需要指定唯一 key 的字段名称。 + * description.zh-CN: 数组项是对象时,需要指定唯一 key 的字段名称。 */ import { Checkbox, Col, Row } from 'antd'; diff --git a/packages/hooks/src/useSelections/demo/demo3.tsx b/packages/hooks/src/useSelections/demo/demo3.tsx index 48dd7cb253..b1941717cc 100644 --- a/packages/hooks/src/useSelections/demo/demo3.tsx +++ b/packages/hooks/src/useSelections/demo/demo3.tsx @@ -1,9 +1,9 @@ /** * title: Pagination - * desc: Load data with pagination and enable cross-page selection. + * description: Load data with pagination and enable cross-page selection. * * title.zh-CN: 分页多选 - * desc.zh-CN: 分页加载数据,并跨页选择。 + * description.zh-CN: 分页加载数据,并跨页选择。 */ import { Checkbox, Divider, Pagination, Spin } from 'antd'; diff --git a/packages/hooks/src/useSelections/index.en-US.md b/packages/hooks/src/useSelections/index.en-US.md index 7cc50f2292..3acf2a0ff7 100644 --- a/packages/hooks/src/useSelections/index.en-US.md +++ b/packages/hooks/src/useSelections/index.en-US.md @@ -14,17 +14,9 @@ This hook is used for Checkbox group, supports multiple selection, single select ## Examples -### Default usage - - -### Object array - - - -### Pagination - - + + ## API @@ -41,6 +33,14 @@ const result: Result = useSelections(items: T[], options?: Options); const result: Result = useSelections(items: T[], defaultSelected?: T[]); ``` +### Params + + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| items | Data items | `T[]` | - | +| options | Optional configuration | `Options` | - | + ### Options @@ -53,7 +53,7 @@ const result: Result = useSelections(items: T[], defaultSelected?: T[]); | Property | Description | Type | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | -| selected | Selected Items | `T[]` | +| selected | Selected items | `T[]` | | allSelected | Is all items selected | `boolean` | | noneSelected | Is no item selected | `boolean` | | partiallySelected | Is partially items selected | `boolean` | @@ -65,3 +65,4 @@ const result: Result = useSelections(items: T[], defaultSelected?: T[]); | selectAll | Select all items | `() => void` | | unSelectAll | UnSelect all items | `() => void` | | toggleAll | Toggle select all items | `() => void` | +| clearAll | Clear all selected (In general, `clearAll` is equivalent to `unSelectAll`. If the items is dynamic, `clearAll` will clear "all selected data", while `unSelectAll` will only clear "the currently selected data in the items") | `() => void` | diff --git a/packages/hooks/src/useSelections/index.ts b/packages/hooks/src/useSelections/index.ts index 1f9c1f547c..e98ce0fa3b 100644 --- a/packages/hooks/src/useSelections/index.ts +++ b/packages/hooks/src/useSelections/index.ts @@ -94,6 +94,11 @@ export default function useSelections(items: T[], options?: T[] | Options) const toggleAll = () => (allSelected ? unSelectAll() : selectAll()); + const clearAll = () => { + selectedMap.clear(); + setSelected([]); + }; + return { selected, noneSelected, @@ -106,6 +111,7 @@ export default function useSelections(items: T[], options?: T[] | Options) toggle: useMemoizedFn(toggle), selectAll: useMemoizedFn(selectAll), unSelectAll: useMemoizedFn(unSelectAll), + clearAll: useMemoizedFn(clearAll), toggleAll: useMemoizedFn(toggleAll), } as const; } diff --git a/packages/hooks/src/useSelections/index.zh-CN.md b/packages/hooks/src/useSelections/index.zh-CN.md index 006f481f39..b1cfdd9a1d 100644 --- a/packages/hooks/src/useSelections/index.zh-CN.md +++ b/packages/hooks/src/useSelections/index.zh-CN.md @@ -14,17 +14,9 @@ demo: ## 代码演示 -### 基础用法 - - -### 对象数组 - - - -### 分页多选 - - + + ## API @@ -41,6 +33,14 @@ const result: Result = useSelections(items: T[], options?: Options); const result: Result = useSelections(items: T[], defaultSelected?: T[]); ``` +### Params + + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| items | 元素列表 | `T[]` | - | +| options | 可选配置项 | `Options` | - | + ### Options @@ -51,17 +51,18 @@ const result: Result = useSelections(items: T[], defaultSelected?: T[]); ### Result -| 参数 | 说明 | 类型 | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | -| selected | 已经选择的元素 | `T[]` | -| allSelected | 是否全选 | `boolean` | -| noneSelected | 是否一个都没有选择 | `boolean` | -| partiallySelected | 是否半选 | `boolean` | -| isSelected | 是否被选择 | `(value: T) => boolean` | -| setSelected | 选择多个元素。多次执行时,后面的返回值会覆盖前面的,因此如果希望合并多次操作的结果,需要手动处理:`setSelected((oldArray) => oldArray.concat(newArray))` | `(value: T[]) => void \| (value: (prevState: T[]) => T[]) => void` | -| select | 选择单个元素 | `(value: T) => void` | -| unSelect | 取消选择单个元素 | `(value: T) => void` | -| toggle | 反选单个元素 | `(value: T) => void` | -| selectAll | 选择全部元素 | `() => void` | -| unSelectAll | 取消选择全部元素 | `() => void` | -| toggleAll | 反选全部元素 | `() => void` | +| 参数 | 说明 | 类型 | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| selected | 已经选择的元素 | `T[]` | +| allSelected | 是否全选 | `boolean` | +| noneSelected | 是否一个都没有选择 | `boolean` | +| partiallySelected | 是否半选 | `boolean` | +| isSelected | 是否被选择 | `(value: T) => boolean` | +| setSelected | 选择多个元素。多次执行时,后面的返回值会覆盖前面的,因此如果希望合并多次操作的结果,需要手动处理:`setSelected((oldArray) => oldArray.concat(newArray))` | `(value: T[]) => void \| (value: (prevState: T[]) => T[]) => void` | +| select | 选择单个元素 | `(value: T) => void` | +| unSelect | 取消选择单个元素 | `(value: T) => void` | +| toggle | 反选单个元素 | `(value: T) => void` | +| selectAll | 选择全部元素 | `() => void` | +| unSelectAll | 取消选择全部元素 | `() => void` | +| toggleAll | 反选全部元素 | `() => void` | +| clearAll | 清除所有选中元素(一般情况下,`clearAll` 等价于 `unSelectAll`。如果元素列表是动态的,则 `clearAll` 会清除掉“所有选中过的元素”,而 `unSelectAll` 只会清除掉“当前元素列表里选中的元素”) | `() => void` | diff --git a/packages/hooks/src/useSetState/demo/demo1.tsx b/packages/hooks/src/useSetState/demo/demo1.tsx index 3746f8dc92..4ed9ff99f4 100644 --- a/packages/hooks/src/useSetState/demo/demo1.tsx +++ b/packages/hooks/src/useSetState/demo/demo1.tsx @@ -1,9 +1,9 @@ /** * title: Default usage - * desc: Automatically merge object. + * description: Automatically merge object. * * title.zh-CN: 基础用法 - * desc.zh-CN: 自动合并对象。 + * description.zh-CN: 自动合并对象。 */ import React from 'react'; diff --git a/packages/hooks/src/useSetState/demo/demo2.tsx b/packages/hooks/src/useSetState/demo/demo2.tsx index fc9f941a38..c2d458ae6a 100644 --- a/packages/hooks/src/useSetState/demo/demo2.tsx +++ b/packages/hooks/src/useSetState/demo/demo2.tsx @@ -1,9 +1,9 @@ /** * title: Updating with callback - * desc: When using the callback to update, the previous state can be received, and the return value will be automatically merged. + * description: When using the callback to update, the previous state can be received, and the return value will be automatically merged. * * title.zh-CN: 使用回调更新 - * desc.zh-CN: 通过回调进行更新,可以获取上一次的状态,并且也会自动合并返回的对象。 + * description.zh-CN: 通过回调进行更新,可以获取上一次的状态,并且也会自动合并返回的对象。 */ import React from 'react'; diff --git a/packages/hooks/src/utils/getDocumentOrShadow.ts b/packages/hooks/src/utils/getDocumentOrShadow.ts index f4b6f21f18..6b5b844377 100644 --- a/packages/hooks/src/utils/getDocumentOrShadow.ts +++ b/packages/hooks/src/utils/getDocumentOrShadow.ts @@ -8,6 +8,7 @@ const checkIfAllInShadow = (targets: BasicTarget[]): boolean => { const targetElement = getTargetElement(item); if (!targetElement) return false; if (targetElement.getRootNode() instanceof ShadowRoot) return true; + return false; }); }; diff --git a/packages/use-url-state/src/demo/demo4.tsx b/packages/use-url-state/src/demo/demo4.tsx index 0a214c339b..02a730c3d9 100644 --- a/packages/use-url-state/src/demo/demo4.tsx +++ b/packages/use-url-state/src/demo/demo4.tsx @@ -1,9 +1,9 @@ /** * title: Multi-state management (split) - * desc: useUrlState can handle multiple useUrlState updates simultaneously + * description: useUrlState can handle multiple useUrlState updates simultaneously * * title.zh-CN: 多状态管理 (拆分) - * desc.zh-CN: useUrlState 可以同时处理多个 useUrlState 更新 + * description.zh-CN: useUrlState 可以同时处理多个 useUrlState 更新 */ import React from 'react';