diff --git a/packages/hooks/src/useProState/__tests__/index.test.ts b/packages/hooks/src/useProState/__tests__/index.test.ts new file mode 100644 index 0000000000..58175fa01e --- /dev/null +++ b/packages/hooks/src/useProState/__tests__/index.test.ts @@ -0,0 +1,90 @@ +import { renderHook, act } from '@testing-library/react'; +import useProState from '../index'; + +describe('useProState', () => { + const setUp = (initialValue: T) => + renderHook(() => { + const [state, { setState, getState, resetState, setMergeState }] = + useProState(initialValue); + return { + state, + setState, + getState, + resetState, + setMergeState, + } as const; + }); + + it('should support initialValue', () => { + const hook = setUp(0); + expect(hook.result.current.state).toBe(0); + }); + + it('should support function initialValue', () => { + const hook = setUp(() => 0); + expect(hook.result.current.state).toBe(0); + }); + + it('should support update', () => { + const hook = setUp(0); + act(() => { + hook.result.current.setState(1); + }); + expect(hook.result.current.state).toBe(1); + }); + + it('should support function update', () => { + const hook = setUp(0); + act(() => { + hook.result.current.setState((prev) => prev + 1); + }); + expect(hook.result.current.state).toBe(1); + }); + + it('should support get latest value', () => { + const hook = setUp(0); + act(() => { + hook.result.current.setState(1); + }); + expect(hook.result.current.getState()).toBe(1); + }); + + it('should support frozen', () => { + const hook = setUp(0); + const prevGetState = hook.result.current.getState; + act(() => { + hook.result.current.setState(1); + }); + expect(hook.result.current.getState).toBe(prevGetState); + }); + + it('should keep random initialValue', () => { + const random = Math.random(); + const hook = setUp(random); + act(() => { + hook.result.current.setState(Math.random()); + hook.result.current.resetState(); + }); + expect(hook.result.current.state).toBe(random); + }); + + it('should keep random function initialValue', () => { + const random = Math.random(); + const hook = setUp(() => random); + act(() => { + hook.result.current.setState(() => Math.random()); + hook.result.current.resetState(); + }); + expect(hook.result.current.state).toBe(random); + }); + + it('should support setMergeState', () => { + const hook = setUp<{ hello?: string; foo?: string }>({ + hello: 'world', + }); + act(() => { + hook.result.current.setMergeState({ foo: 'bar' }); + }); + expect(hook.result.current.state).toEqual({ hello: 'world', foo: 'bar' }); + }); +}); diff --git a/packages/hooks/src/useProState/demo/demo1.tsx b/packages/hooks/src/useProState/demo/demo1.tsx new file mode 100644 index 0000000000..208cd77425 --- /dev/null +++ b/packages/hooks/src/useProState/demo/demo1.tsx @@ -0,0 +1,26 @@ +/** + * title: Open console to view logs + * desc: The counter prints the value every 3 seconds + * + * title.zh-CN: 打开控制台查看输出 + * desc.zh-CN: 计数器每 3 秒打印一次值 + */ + +import React, { useEffect } from 'react'; +import { useProState } from 'ahooks'; + +export default () => { + const [state, { setState, getState }] = useProState(0); + + useEffect(() => { + const interval = setInterval(() => { + console.log('interval count', getState()); + }, 3000); + + return () => { + clearInterval(interval); + }; + }, []); + + return ; +}; diff --git a/packages/hooks/src/useProState/demo/demo2.tsx b/packages/hooks/src/useProState/demo/demo2.tsx new file mode 100644 index 0000000000..a602ec34e1 --- /dev/null +++ b/packages/hooks/src/useProState/demo/demo2.tsx @@ -0,0 +1,44 @@ +/** + * title: Friendly reminder + * desc: SetMergeState will automatically merge objects. The usage of setState is consistent with useState in hook. + * + * title.zh-CN: 温馨提示 + * desc.zh-CN: setMergeState会自动合并对象,setState和hook中useState的用法一致。 + */ + +import React from 'react'; +import { useProState } from 'ahooks'; + +interface State { + hello: string; + value: number; + [key: string]: any; +} + +export default () => { + const [state, { setState, resetState, setMergeState }] = useProState({ + hello: '', + value: Math.random(), + }); + + return ( +
+
{JSON.stringify(state, null, 2)}
+

+ + + +

+
+ ); +}; diff --git a/packages/hooks/src/useProState/index.en-US.md b/packages/hooks/src/useProState/index.en-US.md new file mode 100644 index 0000000000..032bf2aedd --- /dev/null +++ b/packages/hooks/src/useProState/index.en-US.md @@ -0,0 +1,65 @@ +--- +nav: + path: /hooks +--- + +# useSetState + +The hook that manage state, providing the ability to set, get the latest value, reset, and merge. + +## Examples + +### Get the latest value + + + +### Merge and reset + + + +## API + +```typescript +export type SetMergeState> = ( + state: Pick | null | ((prevState: Readonly) => Pick | S | null), +) => void; +export type DispatchType = Dispatch>; + +function useProState>( + initialState?: S | (() => S), +): [ + S, + { + setState: DispatchType; + getState: () => S; + resetState: () => void; + setMergeState: SetMergeState; + }, +]; + +function useProState(initialState?: S | (() => S)): [ + S, + { + setState: DispatchType; + getState: () => S; + resetState: () => void; + setMergeState: (s: Record) => void; + }, +]; +``` + +### Result + +| Property | Description | Type | Default | +| ------------- | -------------------- | ------------------------------------------------------ | ------- | +| state | Current state | `S` | - | +| setState | Set state | `DispatchType` | - | +| getState | Get the latest value | `() => S` | - | +| resetState | reset state | `() => void` | - | +| setMergeState | merge state | `SetMergeState | (s: Record) => void` | - | + +### Params + +| Property | Description | Type | Default | +| ------------ | ------------- | -------------- | ------- | +| initialState | Initial state | `T \| () => T` | - | diff --git a/packages/hooks/src/useProState/index.ts b/packages/hooks/src/useProState/index.ts new file mode 100644 index 0000000000..96536925b9 --- /dev/null +++ b/packages/hooks/src/useProState/index.ts @@ -0,0 +1,52 @@ +import { useState, type Dispatch, type SetStateAction, useCallback } from 'react'; +import useLatest from '../useLatest'; +import { isFunction } from '../utils'; + +export type SetMergeState> = ( + state: Pick | null | ((prevState: Readonly) => Pick | S | null), +) => void; +export type DispatchType = Dispatch>; + +function useProState>( + initialState?: S | (() => S), +): [ + S, + { + setState: DispatchType; + getState: () => S; + resetState: () => void; + setMergeState: SetMergeState; + }, +]; + +function useProState(initialState?: S | (() => S)): [ + S, + { + setState: DispatchType; + getState: () => S; + resetState: () => void; + setMergeState: (s: Record) => void; + }, +]; + +function useProState(initialState: S) { + const [state, setState] = useState(initialState); + const latestValue = useLatest(state); + + const getState = useCallback(() => latestValue.current, []); + + const resetState = useCallback(() => { + setState(initialState); + }, []); + + const setMergeState = useCallback((patch) => { + setState((prevState) => { + const newState = isFunction(patch) ? patch(prevState) : patch; + return newState ? { ...prevState, ...newState } : prevState; + }); + }, []); + + return [state, { setState, getState, resetState, setMergeState }]; +} + +export default useProState; diff --git a/packages/hooks/src/useProState/index.zh-CN.md b/packages/hooks/src/useProState/index.zh-CN.md new file mode 100644 index 0000000000..c21a40aaf7 --- /dev/null +++ b/packages/hooks/src/useProState/index.zh-CN.md @@ -0,0 +1,65 @@ +--- +nav: + path: /hooks +--- + +# useProState + +管理 state 的 Hook,提供设置、获取最新值、重置、合并的能力。 + +## 代码演示 + +### 获取最新值 + + + +### 合并与重置 + + + +## API + +```typescript +export type SetMergeState> = ( + state: Pick | null | ((prevState: Readonly) => Pick | S | null), +) => void; +export type DispatchType = Dispatch>; + +function useProState>( + initialState?: S | (() => S), +): [ + S, + { + setState: DispatchType; + getState: () => S; + resetState: () => void; + setMergeState: SetMergeState; + }, +]; + +function useProState(initialState?: S | (() => S)): [ + S, + { + setState: DispatchType; + getState: () => S; + resetState: () => void; + setMergeState: (s: Record) => void; + }, +]; +``` + +### Result + +| 参数 | 说明 | 类型 | 默认值 | +| ------------- | ---------- | ------------------------------------------------------ | ------ | +| state | 当前状态 | `S` | - | +| setState | 设置状态 | `DispatchType` | - | +| getState | 获取最新值 | `() => S` | - | +| resetState | 重置状态 | `() => void` | - | +| setMergeState | 合并状态 | `SetMergeState | (s: Record) => void` | - | + +### Params + +| 参数 | 说明 | 类型 | 默认值 | +| ------------ | -------- | -------------- | ------ | +| initialState | 初始状态 | `S \| () => S` | - |