diff --git a/README.md b/README.md index 120c86f4a2..7e0e14c758 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ - [`useCounter` and `useNumber`](./docs/useCounter.md) — tracks state of a number. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usecounter--demo) - [`useList`](./docs/useList.md) and [`useUpsert`](./docs/useUpsert.md) — tracks state of an array. [![][img-demo]](https://codesandbox.io/s/wonderful-mahavira-1sm0w) - [`useMap`](./docs/useMap.md) — tracks state of an object. [![][img-demo]](https://codesandbox.io/s/quirky-dewdney-gi161) + - [`useQueue`](./docs/useQueue.md) — implements simple queue. - [`useStateValidator`](./docs/useStateValidator.md) — tracks state of an object. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usestatevalidator--demo) - [`useMultiStateValidator`](./docs/useMultiStateValidator.md) — alike the `useStateValidator`, but tracks multiple states at a time. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemultistatevalidator--demo) - [`useMediatedState`](./docs/useMediatedState.md) — like the regular `useState` but with mediation by custom function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemediatedstate--demo) diff --git a/docs/useQueue.md b/docs/useQueue.md new file mode 100644 index 0000000000..ab05d2f78d --- /dev/null +++ b/docs/useQueue.md @@ -0,0 +1,26 @@ +# `useQueue` + +React state hook implements simple FIFO queue. + + +## Usage + +```jsx +import { useQueue } from 'react-use'; + +const Demo = () => { + const { add, remove, first, last, size } = useQueue(); + + return ( +
+ + + +
+ ); +}; +``` diff --git a/src/__stories__/useQueue.story.tsx b/src/__stories__/useQueue.story.tsx new file mode 100644 index 0000000000..ecb7ab43c8 --- /dev/null +++ b/src/__stories__/useQueue.story.tsx @@ -0,0 +1,23 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useQueue } from '..'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const { add, remove, first, last, size } = useQueue(); + return ( +
+ + + +
+ ); +}; + +storiesOf('State|useQueue', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/__tests__/useQueue.test.ts b/src/__tests__/useQueue.test.ts new file mode 100644 index 0000000000..4a155ff3b2 --- /dev/null +++ b/src/__tests__/useQueue.test.ts @@ -0,0 +1,33 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useQueue from '../useQueue'; + +const setUp = (initialQueue?: any[]) => renderHook(() => useQueue(initialQueue)); + +it('takes initial state', () => { + const { result } = setUp([1, 2, 3]); + const { first, last, size } = result.current; + expect(first).toEqual(1); + expect(last).toEqual(3); + expect(size).toEqual(3); +}); + +it('appends new member', () => { + const { result } = setUp([1, 2]); + act(() => { + result.current.add(3); + }); + const { first, last, size } = result.current; + expect(first).toEqual(1); + expect(last).toEqual(3); + expect(size).toEqual(3); +}); + +it('pops oldest member', () => { + const { result } = setUp([1, 2]); + act(() => { + result.current.remove(); + }); + const { first, size } = result.current; + expect(first).toEqual(2); + expect(size).toEqual(1); +}); diff --git a/src/index.ts b/src/index.ts index 40a4fb2e2f..6ffced21ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,6 +61,7 @@ export { default as usePermission } from './usePermission'; export { default as usePrevious } from './usePrevious'; export { default as usePreviousDistinct } from './usePreviousDistinct'; export { default as usePromise } from './usePromise'; +export { default as useQueue } from './useQueue'; export { default as useRaf } from './useRaf'; export { default as useRafLoop } from './useRafLoop'; export { default as useRafState } from './useRafState'; diff --git a/src/useQueue.ts b/src/useQueue.ts new file mode 100644 index 0000000000..97a8372dce --- /dev/null +++ b/src/useQueue.ts @@ -0,0 +1,37 @@ +import { useState } from 'react'; + +export interface QueueMethods { + add: (item: T) => void; + remove: () => T; + first: T; + last: T; + size: number; +} + +const useQueue = (initialValue: T[] = []): QueueMethods => { + const [state, set] = useState(initialValue); + return { + add: value => { + set(queue => [...queue, value]); + }, + remove: () => { + let result; + set(([first, ...rest]) => { + result = first; + return rest; + }); + return result; + }, + get first() { + return state[0]; + }, + get last() { + return state[state.length - 1]; + }, + get size() { + return state.length; + }, + }; +}; + +export default useQueue; diff --git a/yarn.lock b/yarn.lock index ea96acb66b..949a434e5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4464,7 +4464,7 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -debuglog@^1.0.1: +debuglog@*, debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -6501,7 +6501,7 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" -imurmurhash@^0.1.4: +imurmurhash@*, imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -7982,6 +7982,11 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" +lodash._baseindexof@*: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" + integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= + lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -7990,11 +7995,33 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" +lodash._bindcallback@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= + +lodash._cacheindexof@*: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" + integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= + +lodash._createcache@*: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" + integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= + dependencies: + lodash._getnative "^3.0.0" + lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= +lodash._getnative@*, lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= + lodash._root@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" @@ -8045,6 +8072,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.restparam@*: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"