Skip to content

Commit bfc30b9

Browse files
committed
feat: 🎸 add useGetSet hook
1 parent deccec0 commit bfc30b9

File tree

5 files changed

+105
-0
lines changed

5 files changed

+105
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<br/>
7272
<br/>
7373
- [**State**](./docs/State.md)
74+
- [`useGetSet`](./docs/useGetSet.md) &mdash; returns state getter `get()` instead of raw state.
7475
- [`useObservable`](./docs/useObservable.md) &mdash; tracks latest value of an `Observable`.
7576
- [`useSetState`](./docs/useSetState.md) &mdash; creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0)
7677
- [`useToggle`](./docs/useToggle.md) &mdash; tracks state of a boolean.

Diff for: docs/useGetSet.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# `useGetSet`
2+
3+
React state hook that returns state getter function instead of
4+
raw state itself, this prevents subtle bugs when state is used
5+
in nested functions.
6+
7+
8+
## Usage
9+
10+
Below example uses `useGetSet` to increment a number after 1 second
11+
on each click.
12+
13+
```jsx
14+
import {useGetSet} from 'react-use';
15+
16+
const Demo = () => {
17+
const [get, set] = useGetSet(0);
18+
const onClick = () => {
19+
setTimeout(() => {
20+
set(get() + 1)
21+
}, 1_000);
22+
};
23+
24+
return (
25+
<button onClick={onClick}>Clicked: {get()}</button>
26+
);
27+
};
28+
```
29+
30+
If you would do this example in a naive way using regular `useState`
31+
hook, the counter would not increment correctly if you click fast multiple times.
32+
33+
```jsx
34+
const DemoWrong = () => {
35+
const [cnt, set] = useState(0);
36+
const onClick = () => {
37+
setTimeout(() => {
38+
set(cnt + 1)
39+
}, 1_000);
40+
};
41+
42+
return (
43+
<button onClick={onClick}>Clicked: {cnt}</button>
44+
);
45+
};
46+
```

Diff for: src/__stories__/useGetSet.story.tsx

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import * as React from 'react';
2+
import {storiesOf} from '@storybook/react';
3+
import {useGetSet} from '..';
4+
import {useState} from '../react';
5+
import ShowDocs from '../util/ShowDocs';
6+
7+
const Demo = () => {
8+
const [get, set] = useGetSet(0);
9+
const onClick = () => {
10+
setTimeout(() => {
11+
set(get() + 1)
12+
}, 1_000);
13+
};
14+
15+
return (
16+
<button onClick={onClick}>Clicked: {get()}</button>
17+
);
18+
};
19+
20+
const DemoWrong = () => {
21+
const [cnt, set] = useState(0);
22+
const onClick = () => {
23+
setTimeout(() => {
24+
set(cnt + 1)
25+
}, 1_000);
26+
};
27+
28+
return (
29+
<button onClick={onClick}>Clicked: {cnt}</button>
30+
);
31+
};
32+
33+
storiesOf('useGetSet', module)
34+
.add('Docs', () => <ShowDocs md={require('../../docs/useGetSet.md')} />)
35+
.add('Demo', () =>
36+
<Demo/>
37+
)
38+
.add('DemoWrong', () =>
39+
<DemoWrong/>
40+
)

Diff for: src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import useCounter from './useCounter';
55
import useCss from './useCss';
66
import useFavicon from './useFavicon';
77
import useGeolocation from './useGeolocation';
8+
import useGetSet from './useGetSet';
89
import useHover from './useHover';
910
import useIdle from './useIdle';
1011
import useLifecycles from './useLifecycles';
@@ -40,6 +41,7 @@ export {
4041
useCss,
4142
useFavicon,
4243
useGeolocation,
44+
useGetSet,
4345
useHover,
4446
useIdle,
4547
useLifecycles,

Diff for: src/useGetSet.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {useState, useRef} from './react';
2+
3+
const useGetSet = <T>(initialValue: T): [() => T, (value: T) => void] => {
4+
const [_, update] = useState(undefined);
5+
let state = useRef(initialValue);
6+
7+
const get = () => state.current;
8+
const set = (value: T) => {
9+
state.current = value;
10+
update(undefined);
11+
};
12+
13+
return [get, set];
14+
};
15+
16+
export default useGetSet;

0 commit comments

Comments
 (0)