Skip to content

Commit

Permalink
feat: 🎸 add useGetSet hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Oct 29, 2018
1 parent deccec0 commit bfc30b9
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
46 changes: 46 additions & 0 deletions docs/useGetSet.md
Original file line number Diff line number Diff line change
@@ -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>
);
};
```
40 changes: 40 additions & 0 deletions src/__stories__/useGetSet.story.tsx
Original file line number Diff line number Diff line change
@@ -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/>
)
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -40,6 +41,7 @@ export {
useCss,
useFavicon,
useGeolocation,
useGetSet,
useHover,
useIdle,
useLifecycles,
Expand Down
16 changes: 16 additions & 0 deletions src/useGetSet.ts
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit bfc30b9

Please sign in to comment.