feat: 🎸 add useGetSet hook

This commit is contained in:
streamich 2018-10-29 14:57:56 +01:00
parent deccec0070
commit bfc30b99ab
5 changed files with 105 additions and 0 deletions

View File

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

46
docs/useGetSet.md Normal file
View File

@ -0,0 +1,46 @@
# `useGetSet`
React state hook that returns state getter function instead of
raw state itself, this prevents subtle bugs when state is used
in nested functions.
## Usage
Below example uses `useGetSet` to increment a number after 1 second
on each click.
```jsx
import {useGetSet} from 'react-use';
const Demo = () => {
const [get, set] = useGetSet(0);
const onClick = () => {
setTimeout(() => {
set(get() + 1)
}, 1_000);
};
return (
<button onClick={onClick}>Clicked: {get()}</button>
);
};
```
If you would do this example in a naive way using regular `useState`
hook, the counter would not increment correctly if you click fast multiple times.
```jsx
const DemoWrong = () => {
const [cnt, set] = useState(0);
const onClick = () => {
setTimeout(() => {
set(cnt + 1)
}, 1_000);
};
return (
<button onClick={onClick}>Clicked: {cnt}</button>
);
};
```

View File

@ -0,0 +1,40 @@
import * as React from 'react';
import {storiesOf} from '@storybook/react';
import {useGetSet} from '..';
import {useState} from '../react';
import ShowDocs from '../util/ShowDocs';
const Demo = () => {
const [get, set] = useGetSet(0);
const onClick = () => {
setTimeout(() => {
set(get() + 1)
}, 1_000);
};
return (
<button onClick={onClick}>Clicked: {get()}</button>
);
};
const DemoWrong = () => {
const [cnt, set] = useState(0);
const onClick = () => {
setTimeout(() => {
set(cnt + 1)
}, 1_000);
};
return (
<button onClick={onClick}>Clicked: {cnt}</button>
);
};
storiesOf('useGetSet', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useGetSet.md')} />)
.add('Demo', () =>
<Demo/>
)
.add('DemoWrong', () =>
<DemoWrong/>
)

View File

@ -5,6 +5,7 @@ import useCounter from './useCounter';
import useCss from './useCss';
import useFavicon from './useFavicon';
import useGeolocation from './useGeolocation';
import useGetSet from './useGetSet';
import useHover from './useHover';
import useIdle from './useIdle';
import useLifecycles from './useLifecycles';
@ -40,6 +41,7 @@ export {
useCss,
useFavicon,
useGeolocation,
useGetSet,
useHover,
useIdle,
useLifecycles,

16
src/useGetSet.ts Normal file
View File

@ -0,0 +1,16 @@
import {useState, useRef} from './react';
const useGetSet = <T>(initialValue: T): [() => T, (value: T) => void] => {
const [_, update] = useState(undefined);
let state = useRef(initialValue);
const get = () => state.current;
const set = (value: T) => {
state.current = value;
update(undefined);
};
return [get, set];
};
export default useGetSet;