From b117a306e690376c61b67cc116dc1abac185b336 Mon Sep 17 00:00:00 2001 From: Ward Oosterlijnck Date: Mon, 1 Apr 2019 18:45:25 +1100 Subject: [PATCH 1/3] useDeepCompareEffect --- docs/useDeepCompareEffect.md | 30 +++++++++++++++++ package.json | 3 +- .../useDeepCompareEffect.story.tsx | 33 +++++++++++++++++++ src/index.ts | 2 ++ src/useDeepCompareEffect.ts | 30 +++++++++++++++++ yarn.lock | 2 +- 6 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 docs/useDeepCompareEffect.md create mode 100644 src/__stories__/useDeepCompareEffect.story.tsx create mode 100644 src/useDeepCompareEffect.ts diff --git a/docs/useDeepCompareEffect.md b/docs/useDeepCompareEffect.md new file mode 100644 index 0000000000..9ed37c4d8b --- /dev/null +++ b/docs/useDeepCompareEffect.md @@ -0,0 +1,30 @@ +# `useDeepCompareEffect` + +A modified useEffect hook that is using deep comparison on its dependencies instead of reference equality. + +## Usage + +```jsx +import {useCounter, useDeepCompareEffect} from 'react-use'; + +const Demo = () => { + const [count, {inc: inc}] = useCounter(0); + const options = { step: 2 }; + + useDeepCompareEffect(() => { + inc(options.step) + }, [options]); + + return ( +
+

useDeepCompareEffect: {count}

+
+ ); +}; +``` + +## Reference + +```ts +useDeepCompareEffect(effect: () => void | (() => void | undefined), deps: any[]); +``` diff --git a/package.json b/package.json index 3fe734967f..d492fca280 100644 --- a/package.json +++ b/package.json @@ -35,15 +35,16 @@ "homepage": "https://github.com/streamich/react-use#readme", "dependencies": { "nano-css": "^5.1.0", + "react-fast-compare": "^2.0.4", "react-wait": "^0.3.0", "screenfull": "^4.1.0", "throttle-debounce": "^2.0.1", "ts-easing": "^0.2.0" }, "peerDependencies": { + "keyboardjs": "*", "react": "^16.8.0", "react-dom": "^16.8.0", - "keyboardjs": "*", "rebound": "*" }, "devDependencies": { diff --git a/src/__stories__/useDeepCompareEffect.story.tsx b/src/__stories__/useDeepCompareEffect.story.tsx new file mode 100644 index 0000000000..1035a040af --- /dev/null +++ b/src/__stories__/useDeepCompareEffect.story.tsx @@ -0,0 +1,33 @@ +import {storiesOf} from '@storybook/react'; +import * as React from 'react'; +import {useCounter, useDeepCompareEffect} from '..'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const [countNormal, {inc: incNormal}] = useCounter(0); + const [countDeep, {inc: incDeep}] = useCounter(0); + const options = {max: 500} + + React.useEffect(() => { + if (countNormal < options.max) { + incNormal() + } + }, [options]); + + useDeepCompareEffect(() => { + if (countNormal < options.max) { + incDeep() + } + }, [options]); + + return ( +
+

useEffect: {countNormal}

+

useDeepCompareEffect: {countDeep}

+
+ ); +}; + +storiesOf('useDeepCompareEffect', module) + .add('Docs', () => ) + .add('Demo', () => ) diff --git a/src/index.ts b/src/index.ts index 9b8f364024..272a20b7c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import useDropArea from './useDropArea'; import useCounter from './useCounter'; import useCss from './useCss'; import useDebounce from './useDebounce'; +import useDeepCompareEffect from './useDeepCompareEffect'; import useEffectOnce from './useEffectOnce'; import useEvent from './useEvent'; import useFavicon from './useFavicon'; @@ -78,6 +79,7 @@ export { useCounter, useCss, useDebounce, + useDeepCompareEffect, useEffectOnce, useEvent, useFavicon, diff --git a/src/useDeepCompareEffect.ts b/src/useDeepCompareEffect.ts new file mode 100644 index 0000000000..479d2611cf --- /dev/null +++ b/src/useDeepCompareEffect.ts @@ -0,0 +1,30 @@ +import { useRef, useEffect, EffectCallback, DependencyList } from 'react'; +import * as isEqual from 'react-fast-compare'; + +const isPrimitive = (val: any) => val !== Object(val) + +const useDeepCompareEffect = (effect: EffectCallback, deps: any[]) => { + if (process.env.NODE_ENV !== 'production') { + if (!deps || !deps.length) { + throw new Error( + 'useDeepCompareEffect should not be used with no dependencies. Use React.useEffect instead.', + ) + } + + if (deps.every(isPrimitive)) { + throw new Error( + 'useDeepCompareEffect should not be used with dependencies that are all primitive values. Use React.useEffect instead.', + ); + } + } + + const ref = useRef(undefined); + + if (!isEqual(deps, ref.current)) { + ref.current = deps + } + + useEffect(effect, ref.current) +} + +export default useDeepCompareEffect diff --git a/yarn.lock b/yarn.lock index a00dd1a841..fba1e3856a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8938,7 +8938,7 @@ react-error-overlay@^5.1.4: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.4.tgz#88dfb88857c18ceb3b9f95076f850d7121776991" integrity sha512-fp+U98OMZcnduQ+NSEiQa4s/XMsbp+5KlydmkbESOw4P69iWZ68ZMFM5a2BuE0FgqPBKApJyRuYHR95jM8lAmg== -react-fast-compare@^2.0.2: +react-fast-compare@^2.0.2, react-fast-compare@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== From 33b22a718c4e10b68eb9eed20e773d069ee91e98 Mon Sep 17 00:00:00 2001 From: Ward Oosterlijnck Date: Mon, 1 Apr 2019 20:38:13 +1100 Subject: [PATCH 2/3] dev warnings as console.warn --- src/useDeepCompareEffect.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/useDeepCompareEffect.ts b/src/useDeepCompareEffect.ts index 479d2611cf..48923a40f0 100644 --- a/src/useDeepCompareEffect.ts +++ b/src/useDeepCompareEffect.ts @@ -6,14 +6,14 @@ const isPrimitive = (val: any) => val !== Object(val) const useDeepCompareEffect = (effect: EffectCallback, deps: any[]) => { if (process.env.NODE_ENV !== 'production') { if (!deps || !deps.length) { - throw new Error( - 'useDeepCompareEffect should not be used with no dependencies. Use React.useEffect instead.', - ) + console.warn( + '`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.' + ); } if (deps.every(isPrimitive)) { - throw new Error( - 'useDeepCompareEffect should not be used with dependencies that are all primitive values. Use React.useEffect instead.', + console.warn( + '`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.' ); } } From ba9917cca82f738d31311679519e71ba821d1264 Mon Sep 17 00:00:00 2001 From: Ward Oosterlijnck Date: Mon, 1 Apr 2019 20:38:28 +1100 Subject: [PATCH 3/3] Add useDeepCompareEffect to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 74449a3cc6..4bef7df805 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ - [`useMount`](./docs/useMount.md) — calls `mount` callbacks. - [`useUnmount`](./docs/useUnmount.md) — calls `unmount` callbacks. - [`useUpdateEffect`](./docs/useUpdateEffect.md) — run an `effect` only on updates. + - [`useDeepCompareEffect`](./docs/useUpdateEffect.md) — run an `effect` depending on deep comparison of its dependencies

- [**State**](./docs/State.md)