From bfc30b99abec09b304335bd08f4e4f39e9c972d2 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 29 Oct 2018 14:57:56 +0100 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20useGetSet=20ho?= =?UTF-8?q?ok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/useGetSet.md | 46 +++++++++++++++++++++++++++++ src/__stories__/useGetSet.story.tsx | 40 +++++++++++++++++++++++++ src/index.ts | 2 ++ src/useGetSet.ts | 16 ++++++++++ 5 files changed, 105 insertions(+) create mode 100644 docs/useGetSet.md create mode 100644 src/__stories__/useGetSet.story.tsx create mode 100644 src/useGetSet.ts diff --git a/README.md b/README.md index d7938ce6..4647b9cd 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@

- [**State**](./docs/State.md) + - [`useGetSet`](./docs/useGetSet.md) — returns state getter `get()` instead of raw state. - [`useObservable`](./docs/useObservable.md) — tracks latest value of an `Observable`. - [`useSetState`](./docs/useSetState.md) — creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0) - [`useToggle`](./docs/useToggle.md) — tracks state of a boolean. diff --git a/docs/useGetSet.md b/docs/useGetSet.md new file mode 100644 index 00000000..bda5cd40 --- /dev/null +++ b/docs/useGetSet.md @@ -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 ( + + ); +}; +``` + +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 ( + + ); +}; +``` diff --git a/src/__stories__/useGetSet.story.tsx b/src/__stories__/useGetSet.story.tsx new file mode 100644 index 00000000..540a4c9c --- /dev/null +++ b/src/__stories__/useGetSet.story.tsx @@ -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 ( + + ); +}; + +const DemoWrong = () => { + const [cnt, set] = useState(0); + const onClick = () => { + setTimeout(() => { + set(cnt + 1) + }, 1_000); + }; + + return ( + + ); +}; + +storiesOf('useGetSet', module) + .add('Docs', () => ) + .add('Demo', () => + + ) + .add('DemoWrong', () => + + ) diff --git a/src/index.ts b/src/index.ts index 3b63f020..8c2e1dbb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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, diff --git a/src/useGetSet.ts b/src/useGetSet.ts new file mode 100644 index 00000000..2b1849cb --- /dev/null +++ b/src/useGetSet.ts @@ -0,0 +1,16 @@ +import {useState, useRef} from './react'; + +const useGetSet = (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; From c00f308e957b2db49535514cf90d238d1fb177be Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 29 Oct 2018 15:05:16 +0100 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20useUpdate=20ho?= =?UTF-8?q?ok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/useUpdate.md | 21 +++++++++++++++++++++ src/__stories__/useUpdate.story.tsx | 21 +++++++++++++++++++++ src/index.ts | 2 ++ src/useUpdate.ts | 5 +++++ 5 files changed, 50 insertions(+) create mode 100644 docs/useUpdate.md create mode 100644 src/__stories__/useUpdate.story.tsx create mode 100644 src/useUpdate.ts diff --git a/README.md b/README.md index 4647b9cd..7019a6a1 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ - [`useSpring`](./docs/useSpring.md) — interpolates number over time according to spring dynamics. - [`useTimeout`](./docs/useTimeout.md) — returns true after a timeout. - [`useTween`](./docs/useTween.md) — re-renders component, while tweening a number from 0 to 1. [![][img-demo]](https://codesandbox.io/s/52990wwzyl) + - [`useUpdate`](./docs/useUpdate.md) — returns a callback, which re-renders component when called.

- [**Side-effects**](./docs/Side-effects.md) diff --git a/docs/useUpdate.md b/docs/useUpdate.md new file mode 100644 index 00000000..9f9549ef --- /dev/null +++ b/docs/useUpdate.md @@ -0,0 +1,21 @@ +# `useUpdate` + +React utility hook that returns a function that forces component +to re-render when called. + + +## Usage + +```jsx +import {useUpdate} from 'react-use'; + +const Demo = () => { + const update = useUpdate(); + return ( + <> +
Time: {Date.now()}
+ + + ); +}; +``` diff --git a/src/__stories__/useUpdate.story.tsx b/src/__stories__/useUpdate.story.tsx new file mode 100644 index 00000000..b445f3c2 --- /dev/null +++ b/src/__stories__/useUpdate.story.tsx @@ -0,0 +1,21 @@ +import {storiesOf} from '@storybook/react'; +import * as React from 'react'; +import {useUpdate} from '..'; +import ShowDocs from '../util/ShowDocs'; + +const Demo = () => { + const update = useUpdate(); + return ( + <> +
Time: {Date.now()}
+ + + ); +}; + +storiesOf('useUpdate', module) + .add('Docs', () => ) + .add('Demo', () => + + ) + diff --git a/src/index.ts b/src/index.ts index 8c2e1dbb..974acbcb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import useTitle from './useTitle'; import useToggle from './useToggle'; import useTween from './useTween'; import useUnmount from './useUnmount'; +import useUpdate from './useUpdate'; import useWindowSize from './useWindowSize'; export { @@ -67,5 +68,6 @@ export { useToggle, useTween, useUnmount, + useUpdate, useWindowSize, }; diff --git a/src/useUpdate.ts b/src/useUpdate.ts new file mode 100644 index 00000000..cb1948cb --- /dev/null +++ b/src/useUpdate.ts @@ -0,0 +1,5 @@ +import {useState} from './react'; + +const useUpdate = () => useState(0)[1] as (() => void); + +export default useUpdate; From 52abf315cad389103db9933a2f0d3989844085dc Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 29 Oct 2018 15:06:38 +0100 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20use=20useUpdate?= =?UTF-8?q?=20in=20useGetSet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useGetSet.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/useGetSet.ts b/src/useGetSet.ts index 2b1849cb..6e76bbb3 100644 --- a/src/useGetSet.ts +++ b/src/useGetSet.ts @@ -1,13 +1,14 @@ -import {useState, useRef} from './react'; +import {useRef} from './react'; +import useUpdate from './useUpdate'; const useGetSet = (initialValue: T): [() => T, (value: T) => void] => { - const [_, update] = useState(undefined); + const update = useUpdate(); let state = useRef(initialValue); const get = () => state.current; const set = (value: T) => { state.current = value; - update(undefined); + update(); }; return [get, set];