diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 58cefd9c79..779ea858cb 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -48,6 +48,7 @@ import useRafInterval from './useRafInterval'; import useRafState from './useRafState'; import useRafTimeout from './useRafTimeout'; import useReactive from './useReactive'; +import useRefState from './useRefState'; import useRequest, { clearCache } from './useRequest'; import useResetState from './useResetState'; import { configResponsive, useResponsive } from './useResponsive'; @@ -156,4 +157,5 @@ export { useRafTimeout, useResetState, useMutationObserver, + useRefState, }; diff --git a/packages/hooks/src/useRefState/__tests__/index.test.ts b/packages/hooks/src/useRefState/__tests__/index.test.ts new file mode 100644 index 0000000000..84b71a1946 --- /dev/null +++ b/packages/hooks/src/useRefState/__tests__/index.test.ts @@ -0,0 +1,36 @@ +import { renderHook, act } from '@testing-library/react'; +import useRefState from '../index'; + +describe('useRefState', () => { + const setUp = (initialValue: T) => + renderHook(() => { + const [state, setState, getState] = useRefState(initialValue); + return { + state, + setState, + getState, + } as const; + }); + + it('should support 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.getState()).toBe(1); + }); + + it('should getState frozen', () => { + const hook = setUp(0); + const prevGetState = hook.result.current.getState; + act(() => { + hook.result.current.setState(1); + }); + expect(hook.result.current.getState).toBe(prevGetState); + }); +}); diff --git a/packages/hooks/src/useRefState/demo/demo1.tsx b/packages/hooks/src/useRefState/demo/demo1.tsx new file mode 100644 index 0000000000..399982ff24 --- /dev/null +++ b/packages/hooks/src/useRefState/demo/demo1.tsx @@ -0,0 +1,30 @@ +/** + * title: Basic usage + * desc: useRefState The closure trap is solved by adding a function that gets the current state + * + * title.zh-CN: 基础用法 + * desc.zh-CN: useRefState 多了一个获取当前最新state的函数,解决了闭包陷阱 + */ + +import React, { useEffect } from 'react'; +import { useRefState } from 'ahooks'; + + +export default () => { + const [value, setValue, getValue] = useRefState(false); + + useEffect(() => { + setTimeout(() => { + setValue('data loaded from server'); + }, 2000); + + setTimeout(() => { + console.log(getValue()); + }, 3000); + }, []); + + const text = value || 'Loading...'; + + return
{text}
; +}; + diff --git a/packages/hooks/src/useRefState/index.en-US.md b/packages/hooks/src/useRefState/index.en-US.md new file mode 100644 index 0000000000..5c1f4fa110 --- /dev/null +++ b/packages/hooks/src/useRefState/index.en-US.md @@ -0,0 +1,30 @@ +--- +nav: + path: /hooks +--- + +# useRefState + +To deal with the closure problem, there is an additional function to get the latest state on the basis of useState + +## Examples + +### Default usage + + + +## TypeScript definition + +```typescript +import { Dispatch, SetStateAction } from 'react'; +type GetStateAction = () => S; + +function useGetState(initialState: S | (() => S)): [S, Dispatch>, GetStateAction]; +function useGetState(): [S | undefined, Dispatch>, GetStateAction]; +``` + +## API + +```typescript +const [value, setValue, getValue] = useRefState(false); +``` diff --git a/packages/hooks/src/useRefState/index.ts b/packages/hooks/src/useRefState/index.ts new file mode 100644 index 0000000000..0bc2d80412 --- /dev/null +++ b/packages/hooks/src/useRefState/index.ts @@ -0,0 +1,29 @@ +import type { Dispatch, SetStateAction } from 'react'; +import { useRef, useState, useCallback } from "react"; +import { isFunction } from '../utils'; + +type GetStateAction = () => S; + +function useRefState( + initialState: S | (() => S), +): [S, Dispatch>, GetStateAction]; +function useRefState(): [ + S | undefined, + Dispatch>, + GetStateAction, +]; +function useRefState(value?: S) { + const [state, setState] = useState(value); + const stateRef = useRef(state); + + const setRefState = useCallback((patch: S | ((state?: S) => S)) => { + const newState = isFunction(patch) ? patch(stateRef.current) : patch; + stateRef.current = newState; + setState(newState); + }, []) + + const getState = useCallback(() => stateRef.current, []); + return [state, setRefState, getState] as const; +} + +export default useRefState; diff --git a/packages/hooks/src/useRefState/index.zh-CN.md b/packages/hooks/src/useRefState/index.zh-CN.md new file mode 100644 index 0000000000..264846c815 --- /dev/null +++ b/packages/hooks/src/useRefState/index.zh-CN.md @@ -0,0 +1,30 @@ +--- +nav: + path: /hooks +--- + +# useRefState + +处理闭包问题,在 useState 的基础上多了一个获取最新 state 的函数 + +## 代码演示 + +### 基础用法 + + + +## 类型定义 + +```typescript +import { Dispatch, SetStateAction } from 'react'; +type GetStateAction = () => S; + +function useGetState(initialState: S | (() => S)): [S, Dispatch>, GetStateAction]; +function useGetState(): [S | undefined, Dispatch>, GetStateAction]; +``` + +## API + +```typescript +const [value, setValue, getValue] = useRefState(initialState); +```