Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add useRefState hooks #2348

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -156,4 +157,5 @@ export {
useRafTimeout,
useResetState,
useMutationObserver,
useRefState,
};
36 changes: 36 additions & 0 deletions packages/hooks/src/useRefState/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { renderHook, act } from '@testing-library/react';
import useRefState from '../index';

describe('useRefState', () => {
const setUp = <T>(initialValue: T) =>
renderHook(() => {
const [state, setState, getState] = useRefState<T>(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);
});
});
30 changes: 30 additions & 0 deletions packages/hooks/src/useRefState/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>{text}</div>;
};

30 changes: 30 additions & 0 deletions packages/hooks/src/useRefState/index.en-US.md
Original file line number Diff line number Diff line change
@@ -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

<code src="./demo/demo1.tsx" />

## TypeScript definition

```typescript
import { Dispatch, SetStateAction } from 'react';
type GetStateAction<S> = () => S;

function useGetState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>, GetStateAction<S>];
function useGetState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>, GetStateAction<S | undefined>];
```

## API

```typescript
const [value, setValue, getValue] = useRefState(false);
```
29 changes: 29 additions & 0 deletions packages/hooks/src/useRefState/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Dispatch, SetStateAction } from 'react';
import { useRef, useState, useCallback } from "react";
import { isFunction } from '../utils';

type GetStateAction<S> = () => S;

function useRefState<S>(
initialState: S | (() => S),
): [S, Dispatch<SetStateAction<S>>, GetStateAction<S>];
function useRefState<S = undefined>(): [
S | undefined,
Dispatch<SetStateAction<S | undefined>>,
GetStateAction<S | undefined>,
];
function useRefState<S>(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;
30 changes: 30 additions & 0 deletions packages/hooks/src/useRefState/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
nav:
path: /hooks
---

# useRefState

处理闭包问题,在 useState 的基础上多了一个获取最新 state 的函数

## 代码演示

### 基础用法

<code src="./demo/demo1.tsx" />

## 类型定义

```typescript
import { Dispatch, SetStateAction } from 'react';
type GetStateAction<S> = () => S;

function useGetState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>, GetStateAction<S>];
function useGetState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>, GetStateAction<S | undefined>];
```

## API

```typescript
const [value, setValue, getValue] = useRefState<S>(initialState);
```