diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f2c037c6b..744695f640 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,17 +3,25 @@ version: 2 refs: container: &container docker: - - image: node:12.9.1 + - image: node:12.13.0 working_directory: ~/repo steps: - &Versions run: name: Versions command: node -v && npm -v && yarn -v + - &CacheRestore + restore_cache: + key: dependency-cache-{{ checksum "yarn.lock" }} - &Install run: name: Install Dependencies command: yarn install --pure-lockfile + - &CacheSave + save_cache: + key: dependency-cache-{{ checksum "yarn.lock" }} + paths: + - ./node_modules - &Build run: name: Build @@ -42,8 +50,9 @@ refs: run: name: Post commit status for Storybook command: | - npx cross-ci :run \ - npx commit-status success Storybook "'\${BUILD_VERSION}'" "'https://$CIRCLE_BUILD_NUM-154950925-gh.circle-artifacts.com/0/root/repo/storybook-static/index.html'" + npx cross-ci :run curl -H "'Authorization: token \${GITHUB_TOKEN}' -H 'Accept: application/vnd.github.v3+json'" \ + "'https://api.github.com/repos/\${PROJECT_OWNER}/\${PROJECT_NAME}/statuses/$CIRCLE_SHA1'" -X POST \ + -d "'{\"state\": \"success\", \"context\": \"Storybook\", \"description\": \"\${BUILD_VERSION}\", \"target_url\": \"https://$CIRCLE_BUILD_NUM-154950925-gh.circle-artifacts.com/0/root/repo/storybook-static/index.html\"}'" jobs: all: @@ -51,7 +60,9 @@ jobs: steps: - checkout - *Versions + - *CacheRestore - *Install + - *CacheSave - *Build - *Build_Storybook - *Test @@ -65,7 +76,9 @@ jobs: steps: - checkout - *Versions + - *CacheRestore - *Install + - *CacheSave - *Build - *Build_Storybook - *Test @@ -87,7 +100,9 @@ jobs: steps: - checkout - *Versions + - *CacheRestore - *Install + - *CacheSave - *Build - *Build_Storybook - *Test diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..bfcd240e6f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +# Description + + + + +## Type of change + + +- [ ] Bug fix _(non-breaking change which fixes an issue)_ +- [ ] New feature _(non-breaking change which adds functionality)_ +- [ ] **Breaking change** _(fix or feature that would cause existing functionality to not work as before)_ + +# Checklist +- [ ] Read the [Contributing Guide](https://github.com/streamich/react-use/blob/master/CONTRIBUTING.md) +- [ ] Perform a code self-review +- [ ] Comment the code, particularly in hard-to-understand areas +- [ ] Add documentation +- [ ] Add hook's story at Storybook +- [ ] Cover changes with tests +- [ ] Ensure the test suite passes (`yarn test`) +- [ ] Provide 100% tests coverage +- [ ] Make sure code lints (`yarn lint`). Fix it with `yarn lint:fix` in case of failure. +- [ ] Make sure types are fine (`yarn lint:types`). + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3396f4c6a0..06d6725ce9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,205 @@ +## [13.2.2](https://github.com/streamich/react-use/compare/v13.2.1...v13.2.2) (2019-11-06) + + +### Bug Fixes + +* **#749:** now should work with SSR ([c12976c](https://github.com/streamich/react-use/commit/c12976cad26577a4be3ac65133268f41bbdc82da)), closes [#749](https://github.com/streamich/react-use/issues/749) + +## [13.2.1](https://github.com/streamich/react-use/compare/v13.2.0...v13.2.1) (2019-11-04) + + +### Bug Fixes + +* **yarn.lock:** re-create the yarn.lock file with nailed versions in devDeps ([f094a3a](https://github.com/streamich/react-use/commit/f094a3ae833f406137b9d5355843a6615af20164)) + +# [13.2.0](https://github.com/streamich/react-use/compare/v13.1.0...v13.2.0) (2019-11-04) + + +### Features + +* re-create yarn.lock ([d48e03e](https://github.com/streamich/react-use/commit/d48e03e9ee38555ff29ca46fb6e75c13e9c23aba)) +* re-create yarn.lock ([ccdffe0](https://github.com/streamich/react-use/commit/ccdffe027fba15bdca1f35dc375a0c32739aee6f)) +* **usePreviousDistinct:** add tests for undefined value behaviour; ([cb373f9](https://github.com/streamich/react-use/commit/cb373f951fb3f34b9e54793687de14000a2dc08e)) +* **usePreviousDistinct:** improve types; ([30f53e8](https://github.com/streamich/react-use/commit/30f53e8c5d7e8b27bf3f273ebfcacabf30146ba3)) +* **usePreviousDistinct:** now predicate not called on initial render; ([fbe9b13](https://github.com/streamich/react-use/commit/fbe9b1303c0433d5608ca5b507d9c76711b5cb68)) +* **useStateList:** rework useStateList to make it work properly. ([242c274](https://github.com/streamich/react-use/commit/242c274dd49779fa80f8b9e451c699205279339e)) +* **useTitle:** reworked hook to make it synchronous without useUpdate; ([a133267](https://github.com/streamich/react-use/commit/a13326779ffd6885ac531240b984a77bcad3bee6)) +* **useWindowSize:** A bit changed lyfecycle and added types; ([03bdecf](https://github.com/streamich/react-use/commit/03bdecf7ac6aa0ad863a1efd71c056aa41df62b7)) + +# [13.1.0](https://github.com/streamich/react-use/compare/v13.0.1...v13.1.0) (2019-11-01) + + +### Features + +* pull request template for features ([1dc21f3](https://github.com/streamich/react-use/commit/1dc21f3)) + +## [13.0.1](https://github.com/streamich/react-use/compare/v13.0.0...v13.0.1) (2019-11-01) + + +### Bug Fixes + +* **useLockBodyScroll:** infer overflow type directly from declaration. ([26baf47](https://github.com/streamich/react-use/commit/26baf47)) + +# [13.0.0](https://github.com/streamich/react-use/compare/v12.13.0...v13.0.0) (2019-11-01) + + +* Merge pull request #711 from streamich/remove-react-wait ([8d40f18](https://github.com/streamich/react-use/commit/8d40f18)), closes [#711](https://github.com/streamich/react-use/issues/711) + + +### Features + +* remove useRefMounted hook ([ad74d3d](https://github.com/streamich/react-use/commit/ad74d3d)) +* **useRefMounted:** remove obsolete hook; ([dc364c8](https://github.com/streamich/react-use/commit/dc364c8)) +* **useWait:** removed from package due to it is simple reexport of other package; ([d7c38bd](https://github.com/streamich/react-use/commit/d7c38bd)) + + +### BREAKING CHANGES + +* useWait hook has been removed from react-use +* deprecated useRefMounted hook is now removed, use useMountedState hook instead + +# [12.13.0](https://github.com/streamich/react-use/compare/v12.12.0...v12.13.0) (2019-10-31) + + +### Features + +* **useCounter:** reworked with use of new resolveHookState function plus improved memory usage; ([befcf84](https://github.com/streamich/react-use/commit/befcf84)) +* **useGetSet:** reworked with use of new resolveHookState function plus improved memory usage; ([9b5d0f2](https://github.com/streamich/react-use/commit/9b5d0f2)) +* react-like state resolver to use it in stateful hooks; ([9fd02eb](https://github.com/streamich/react-use/commit/9fd02eb)) + +# [12.12.0](https://github.com/streamich/react-use/compare/v12.11.0...v12.12.0) (2019-10-31) + + +### Features + +* add typings for createReducer ([f1cf036](https://github.com/streamich/react-use/commit/f1cf036)) + +# [12.11.0](https://github.com/streamich/react-use/compare/v12.10.0...v12.11.0) (2019-10-31) + + +### Features + +* **useWait:** add deprecation messages to readme and export; ([d338245](https://github.com/streamich/react-use/commit/d338245)) + +# [12.10.0](https://github.com/streamich/react-use/compare/v12.9.1...v12.10.0) (2019-10-30) + + +### Features + +* **useUpdate:** improve memory usage - now single function instance to increment all counters; ([0f02fd0](https://github.com/streamich/react-use/commit/0f02fd0)) + +## [12.9.1](https://github.com/streamich/react-use/compare/v12.9.0...v12.9.1) (2019-10-28) + + +### Bug Fixes + +* useSize avoid crash in Safari 11 ([da0e66b](https://github.com/streamich/react-use/commit/da0e66b)) + +# [12.9.0](https://github.com/streamich/react-use/compare/v12.8.0...v12.9.0) (2019-10-26) + + +### Features + +* add createBreakpoint ([79ba4ef](https://github.com/streamich/react-use/commit/79ba4ef)) + +# [12.8.0](https://github.com/streamich/react-use/compare/v12.7.2...v12.8.0) (2019-10-25) + + +### Features + +* add ensuredForwardRef and useEnsuredForwardedRef ([1bfe063](https://github.com/streamich/react-use/commit/1bfe063)) + +## [12.7.2](https://github.com/streamich/react-use/compare/v12.7.1...v12.7.2) (2019-10-23) + + +### Bug Fixes + +* 🐛 bump set-harmonic-interval package version ([f7c709a](https://github.com/streamich/react-use/commit/f7c709a)) + +## [12.7.1](https://github.com/streamich/react-use/compare/v12.7.0...v12.7.1) (2019-10-17) + + +### Bug Fixes + +* example in the docs; ([7f54cad](https://github.com/streamich/react-use/commit/7f54cad)) +* rename story's mediator and add `g` flag to it's regex; ([652b318](https://github.com/streamich/react-use/commit/652b318)) + +# [12.7.0](https://github.com/streamich/react-use/compare/v12.6.0...v12.7.0) (2019-10-17) + + +### Bug Fixes + +* error throw tests; ([056875b](https://github.com/streamich/react-use/commit/056875b)) +* useMultiStateValidator readme description; ([8c7f7f5](https://github.com/streamich/react-use/commit/8c7f7f5)) + + +### Features + +* useMultiStateValidator ([ae26988](https://github.com/streamich/react-use/commit/ae26988)) + +# [12.6.0](https://github.com/streamich/react-use/compare/v12.5.0...v12.6.0) (2019-10-16) + + +### Features + +* useRafState ([#684](https://github.com/streamich/react-use/issues/684)) ([00816a4](https://github.com/streamich/react-use/commit/00816a4)) + +# [12.5.0](https://github.com/streamich/react-use/compare/v12.4.0...v12.5.0) (2019-10-13) + + +### Features + +* useList allow pushing multiple items ([#621](https://github.com/streamich/react-use/issues/621)) ([a624364](https://github.com/streamich/react-use/commit/a624364)) + +# [12.4.0](https://github.com/streamich/react-use/compare/v12.3.2...v12.4.0) (2019-10-12) + + +### Features + +* useIntersection ([#652](https://github.com/streamich/react-use/issues/652)) ([d5f359f](https://github.com/streamich/react-use/commit/d5f359f)) + +## [12.3.2](https://github.com/streamich/react-use/compare/v12.3.1...v12.3.2) (2019-10-12) + + +### Bug Fixes + +* improve use of refs in dependency lists ([#655](https://github.com/streamich/react-use/issues/655)) ([ed8e26d](https://github.com/streamich/react-use/commit/ed8e26d)) + +## [12.3.1](https://github.com/streamich/react-use/compare/v12.3.0...v12.3.1) (2019-10-10) + + +### Bug Fixes + +* move [@types](https://github.com/types)/react-wait to dependencies, closes [#661](https://github.com/streamich/react-use/issues/661) ([#662](https://github.com/streamich/react-use/issues/662)) ([6bdd74e](https://github.com/streamich/react-use/commit/6bdd74e)) + +# [12.3.0](https://github.com/streamich/react-use/compare/v12.2.3...v12.3.0) (2019-10-07) + + +### Features + +* reset util callback for useList ([#654](https://github.com/streamich/react-use/issues/654)) ([9ea3548](https://github.com/streamich/react-use/commit/9ea3548)) + +## [12.2.3](https://github.com/streamich/react-use/compare/v12.2.2...v12.2.3) (2019-10-05) + + +### Bug Fixes + +* move react-wait types to dev dependencies, closes [#644](https://github.com/streamich/react-use/issues/644) ([49372ac](https://github.com/streamich/react-use/commit/49372ac)) + +## [12.2.2](https://github.com/streamich/react-use/compare/v12.2.1...v12.2.2) (2019-09-26) + + +### Bug Fixes + +* useDebounce remove deps from function arguments ([#623](https://github.com/streamich/react-use/issues/623)) ([23d6a5a](https://github.com/streamich/react-use/commit/23d6a5a)) + +## [12.2.1](https://github.com/streamich/react-use/compare/v12.2.0...v12.2.1) (2019-09-23) + + +### Bug Fixes + +* remove attempt from deps of retry in useAsyncRetry ([#614](https://github.com/streamich/react-use/issues/614)) ([adce59e](https://github.com/streamich/react-use/commit/adce59e)) + # [12.2.0](https://github.com/streamich/react-use/compare/v12.1.0...v12.2.0) (2019-09-02) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..1664243297 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing + +Thanks for being willing to contribute 🙌 If you contribute to this project, you agree to release your work under the license of this project. + +**Working on your first Pull Request?** You can learn how from this [First Contributions](https://github.com/firstcontributions/first-contributions) guide. + +## Project setup + +1. Fork and clone the repo +1. Run `yarn install` to install dependencies +1. Create a branch for your PR with `git checkout -b pr/your-branch-name` + +> Tip: Keep your `master` branch pointing at the original repository and make +> pull requests from branches on your fork. To do this, run: +> +> ```sh +> git remote add upstream https://github.com/streamich/react-use.git +> git fetch upstream +> git branch --set-upstream-to=upstream/master master +> ``` +> +> This will add the original repository as a "remote" called "upstream," Then +> fetch the git information from that remote, then set your local `master` +> branch to use the upstream master branch whenever you run `git pull`. Then you +> can make all of your pull request branches based on this `master` branch. +> Whenever you want to update your version of `master`, do a regular `git pull`. + +## Development + +This library is a collection of React hooks so a proposal for a new hook will need to utilize the [React Hooks API](https://reactjs.org/docs/hooks-reference.html) internally to be taken into consideration. + +### Creating a new hook + +1. Create `src/useYourHookName.ts` and `src/__stories__/useYourHookName.story.tsx`, run `yarn start` to start the storybook development server and start coding your hook +1. Create `src/__tests__/useYourHookName.test.ts`, run `yarn test:watch` to start the test runner in watch mode and start writing tests for your hook +1. Create `src/docs/useYourHookName.md` and create documentation for your hook +1. Export your hook from `src/index.ts` and add your hook to `README.md` + +You can also write your tests first if you prefer [test-driven development](https://en.wikipedia.org/wiki/Test-driven_development). + +### Updating an existing hook + +1. Run `yarn start` to start the storybook development server and start applying changes +2. Update tests according to your changes using `yarn test:watch` +3. Update documentation according to your changes + +## Committing and Pushing changes + +### Commit messages + +This repo uses [semantic-release](https://github.com/semantic-release/semantic-release) and [conventional commit messages](https://conventionalcommits.org) so prefix your commits with `fix:` or `feat:` if you want your changes to appear in [release notes](https://github.com/streamich/react-use/blob/master/CHANGELOG.md). + +### Git hooks + +There are git hooks set up with this project that are automatically enabled +when you install dependencies. These hooks automatically test and validate your code when creating commits. They're really handy but can be temporarily disabled by adding a `--no-verify` flag to your commit command. This is useful when you want to commit and push to get feedback on uncompleted code. + +## Help needed + +Please have a look at the [open issues](https://github.com/streamich/react-use/issues) and respond to questions, bug reports and feature requests. Thanks! + +We're also looking to improve the code coverage on this project. To easily know what hooks need tests run `yarn test:coverage` to generate a code coverage report. You can see the report in your terminal or open `coverage/lcov-report/index.html` to see the HTML report. diff --git a/README.md b/README.md index 358a7f329c..ffde67da09 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ - [`useGeolocation`](./docs/useGeolocation.md) — tracks geo location state of user's device. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usegeolocation--demo) - [`useHover` and `useHoverDirty`](./docs/useHover.md) — tracks mouse hover state of some element. [![][img-demo]](https://codesandbox.io/s/zpn583rvx) - [`useIdle`](./docs/useIdle.md) — tracks whether user is being inactive. + - [`useIntersection`](./docs/useIntersection.md) — tracks an HTML element's intersection. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useintersection--demo) - [`useKey`](./docs/useKey.md), [`useKeyPress`](./docs/useKeyPress.md), [`useKeyboardJs`](./docs/useKeyboardJs.md), and [`useKeyPressEvent`](./docs/useKeyPressEvent.md) — track keys. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usekeypressevent--demo) - [`useLocation`](./docs/useLocation.md) and [`useSearchParam`](./docs/useSearchParam.md) — tracks page navigation bar location state. - [`useMedia`](./docs/useMedia.md) — tracks state of a CSS media query. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemedia--demo) @@ -60,11 +61,12 @@ - [`usePageLeave`](./docs/usePageLeave.md) — triggers when mouse leaves page boundaries. - [`useScroll`](./docs/useScroll.md) — tracks an HTML element's scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescroll--docs) - [`useScrolling`](./docs/useScrolling.md) — tracks whether HTML element is scrolling. - - [`useSize`](./docs/useSize.md) — tracks an HTML element's dimensions. + - [`useSize`](./docs/useSize.md) — tracks an HTML element's size. - [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing. - [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs) - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) - - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver).[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo) + - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions using the Resize Observer API.[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo) + - [`createBreakpoint`](./doc/createBreakpoint.md) — tracks `innerWidth`

- [**UI**](./docs/UI.md) @@ -75,7 +77,6 @@ - [`useFullscreen`](./docs/useFullscreen.md) — display an element or video full-screen. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/ui-usefullscreen--demo) - [`useSpeech`](./docs/useSpeech.md) — synthesizes speech from a text string. [![][img-demo]](https://codesandbox.io/s/n090mqz69m) - [`useVideo`](./docs/useVideo.md) — plays video, tracks its state, and exposes playback controls. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/ui-usevideo--demo) - - [`useWait`](./docs/useWait.md) — complex waiting management for UIs.

- [**Animations**](./docs/Animations.md) @@ -104,12 +105,12 @@ - [`useTitle`](./docs/useTitle.md) — sets title of the page. - [`usePermission`](./docs/usePermission.md) — query permission status for browser APIs.
-
+
- [**Lifecycles**](./docs/Lifecycles.md) - [`useEffectOnce`](./docs/useEffectOnce.md) — a modified [`useEffect`](https://reactjs.org/docs/hooks-reference.html#useeffect) hook that only runs once. - [`useEvent`](./docs/useEvent.md) — subscribe to events. - [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks. - - [`useMountedState`](./docs/useMountedState.md) and [`useRefMounted`](./docs/useRefMounted.md) — track if component is mounted. + - [`useMountedState`](./docs/useMountedState.md) — track if component is mounted. - [`usePromise`](./docs/usePromise.md) — resolves promise only while component is mounted. - [`useLogger`](./docs/useLogger.md) — logs in console as component goes through life-cycles. - [`useMount`](./docs/useMount.md) — calls `mount` callbacks. @@ -125,15 +126,26 @@ - [`useDefault`](./docs/useDefault.md) — returns the default value when state is `null` or `undefined`. - [`useGetSet`](./docs/useGetSet.md) — returns state getter `get()` instead of raw state. - [`useGetSetState`](./docs/useGetSetState.md) — as if [`useGetSet`](./docs/useGetSet.md) and [`useSetState`](./docs/useSetState.md) had a baby. - - [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props. + - [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props. [![][img-demo]](https://codesandbox.io/s/fervent-galileo-krgx6) + - [`usePreviousDistinct`](./docs/usePreviousDistinct.md) — like `usePrevious` but with a predicate to determine if `previous` should update. - [`useObservable`](./docs/useObservable.md) — tracks latest value of an `Observable`. + - [`useRafState`](./docs/useRafState.md) — creates `setState` method which only updates after `requestAnimationFrame`. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-userafstate--demo) - [`useSetState`](./docs/useSetState.md) — creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0) - - [`useStateList`](./docs/useStateList.md) — circularly iterates over an array. - - [`useToggle` and `useBoolean`](./docs/useToggle.md) — tracks state of a boolean. + - [`useStateList`](./docs/useStateList.md) — circularly iterates over an array. [![][img-demo]](https://codesandbox.io/s/bold-dewdney-pjzkd) + - [`useToggle` and `useBoolean`](./docs/useToggle.md) — tracks state of a boolean. [![][img-demo]](https://codesandbox.io/s/focused-sammet-brw2d) - [`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) — tracks state of an array. - [`useMap`](./docs/useMap.md) — tracks state of an object. + - [`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) +
+
+- [**Miscellaneous**]() + - [`useEnsuredForwardedRef`](./docs/useEnsuredForwardedRef.md) and [`ensuredForwardRef`](./docs/useEnsuredForwardedRef.md) — use a React.forwardedRef safely. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-useensuredforwardedref--demo)
@@ -160,7 +172,6 @@ [img-demo]: https://img.shields.io/badge/demo-%20%20%20%F0%9F%9A%80-green.svg -

Contributors

diff --git a/docs/createBreakpoint.md b/docs/createBreakpoint.md new file mode 100644 index 0000000000..a07cb68b15 --- /dev/null +++ b/docs/createBreakpoint.md @@ -0,0 +1,43 @@ +# `createBreakpoint` + +## Usage + +### use default breakpoint + +laptopL: 1440, laptop: 1024, tablet: 768 + +```jsx +import React from "react"; +import { createBreakpoint } from "react-use"; + +const useBreakpoint = createBreakpoint(); + +const Demo = () => { + const breakpoint = useBreakpoint(); + + if (breakpoint === "laptopL") return
This is very big Laptop
; + else if (breakpoint == "laptop") return
This is Laptop
; + else if (breakpoint == "tablet") return
This is Tablet
; + else return
Too small!
; +}; +``` + +### use custom breakpoint + +XL: 1280, L: 768, S: 350 + +```jsx +import React from "react"; +import { createBreakpoint } from "react-use"; + +const useBreakpoint = createBreakpoint({ XL: 1280, L: 768, S: 350 }); + +const Demo = () => { + const breakpoint = useBreakpoint(); + + if (breakpoint === "XL") return
XL
; + else if (breakpoint == "L") return
LoL
; + else if (breakpoint == "S") return
Sexyy
; + else return
Wth
; +}; +``` diff --git a/docs/useEnsuredForwardedRef.md b/docs/useEnsuredForwardedRef.md new file mode 100644 index 0000000000..b094a284bc --- /dev/null +++ b/docs/useEnsuredForwardedRef.md @@ -0,0 +1,63 @@ +# `useEnsuredForwardedRef` + +React hook to use a ForwardedRef safely. + +In some scenarios, you may need to use a _ref_ from inside and outside a component. If that's the case, you should use `React.forwardRef` to pass it through the child component. This is useful when you only want to forward that _ref_ and expose an internal `HTMLelement` to a parent component, for example. However, if you need to manipulate that reference inside a child's lifecycle hook... things get complicated, since you can't always ensure that the _ref_ is being sent by the parent component and if it is not, you will get `undefined` instead of a valid _ref_. + +This hook is useful in this specific case, it will __ensure__ that you get a valid reference on the other side. + +## Usage + +```jsx +import {ensuredForwardRef} from 'react-use'; + +const Demo = () => { + return ( + + ); +}; + +const Child = ensuredForwardRef((props, ref) => { + useEffect(() => { + console.log(ref.current.getBoundingClientRect()) + }, []) + + return ( +
+ ); +}); +``` + +## Alternative usage + +```jsx +import {useEnsuredForwardedRef} from 'react-use'; + +const Demo = () => { + return ( + + ); +}; + +const Child = React.forwardRef((props, ref) => { + // Here `ref` is undefined + const ensuredForwardRef = useEnsuredForwardedRef(ref); + // ensuredForwardRef will always be a valid reference. + + useEffect(() => { + console.log(ensuredForwardRef.current.getBoundingClientRect()) + }, []) + + return ( +
+ ); +}); +``` + +## Reference + +```ts +ensuredForwardRef(Component: RefForwardingComponent): ForwardRefExoticComponent & RefAttributes>; + +useEnsuredForwardedRef(ref: React.MutableRefObject): React.MutableRefObject; +``` diff --git a/docs/useIntersection.md b/docs/useIntersection.md new file mode 100644 index 0000000000..802ee1da6a --- /dev/null +++ b/docs/useIntersection.md @@ -0,0 +1,36 @@ +# `useIntersection` + +React sensor hook that tracks the changes in the intersection of a target element with an ancestor element or with a top-level document's viewport. Uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) and returns a [IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry). + +## Usage + +```jsx +import * as React from 'react'; +import { useIntersection } from 'react-use'; + +const Demo = () => { + const intersectionRef = React.useRef(null); + const intersection = useIntersection(intersectionRef, { + root: null, + rootMargin: '0px', + threshold: 1 + }); + + return ( +
+ {intersection && intersection.intersectionRatio < 1 + ? 'Obscured' + : 'Fully in view'} +
+ ); +}; +``` + +## Reference + +```ts +useIntersection( + ref: RefObject, + options: IntersectionObserverInit, +): IntersectionObserverEntry | null; +``` diff --git a/docs/useInterval.md b/docs/useInterval.md index 03679acfa8..5c3ae9a7ab 100644 --- a/docs/useInterval.md +++ b/docs/useInterval.md @@ -1,6 +1,6 @@ # `useInterval` -React hook that allow you using declarative `setInterval`. +A declarative interval hook based on [Dan Abramov's article on overreacted.io](https://overreacted.io/making-setinterval-declarative-with-react-hooks). The interval can be paused by setting the delay to `null`. ## Usage @@ -11,32 +11,31 @@ import {useInterval} from 'react-use'; const Demo = () => { const [count, setCount] = React.useState(0); const [delay, setDelay] = React.useState(1000); + const [isRunning, toggleIsRunning] = useBoolean(true); - useInterval(() => { - setCount(count + 1); - }, delay); - - function handleDelayChange(e) { - setDelay(Number(e.target.value)); - } + useInterval( + () => { + setCount(count + 1); + }, + isRunning ? delay : null + ); return (
- delay: + delay: setDelay(Number(event.target.value))} />

count: {count}

- +
); }; ``` - ## Reference ```js -useInterval(fn, delay?: number) +useInterval(callback, delay?: number) ``` diff --git a/docs/useList.md b/docs/useList.md index 48b2c9a020..2e33e188a5 100644 --- a/docs/useList.md +++ b/docs/useList.md @@ -2,21 +2,31 @@ React state hook that tracks a value of an array. - ## Usage ```jsx import {useList} from 'react-use'; const Demo = () => { - const [list, {set, push}] = useList(); + const [list, { clear, filter, push, remove, set, sort, updateAt, reset }] = useList(); return (
-
{list.join(',')}
- - + + + + + + + + + +
{JSON.stringify(list, null, 2)}
); }; ``` + +## Related hooks + +- [useUpsert](./useUpsert.md) diff --git a/docs/useMap.md b/docs/useMap.md index 99e5e131f4..cf3273afe3 100644 --- a/docs/useMap.md +++ b/docs/useMap.md @@ -2,22 +2,28 @@ React state hook that tracks a value of an object. - ## Usage ```jsx import {useMap} from 'react-use'; const Demo = () => { - const [map, {set, reset}] = useMap({ + const [map, {set, remove, reset}] = useMap({ hello: 'there', }); return (
+ + +
{JSON.stringify(map, null, 2)}
- -
); }; diff --git a/docs/useMeasure.md b/docs/useMeasure.md index 92b5db7b11..0f574fbcbe 100644 --- a/docs/useMeasure.md +++ b/docs/useMeasure.md @@ -1,6 +1,6 @@ # `useMeasure` -React sensor hook that reacts to changes in size of any of the observed elements. +React sensor hook that tracks dimensions of an HTML element using the [Resize Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). ## Usage @@ -8,12 +8,18 @@ React sensor hook that reacts to changes in size of any of the observed elements import { useMeasure } from "react-use"; const Demo = () => { - const [ref, { width, height }] = useMeasure(); + const [ref, { x, y, width, height, top, right, bottom, left }] = useMeasure(); return (
+
x: {x}
+
y: {y}
width: {width}
height: {height}
+
top: {top}
+
right: {right}
+
bottom: {bottom}
+
left: {left}
); }; @@ -21,4 +27,4 @@ const Demo = () => { ## Related hooks -- [useSize](./useSize.md) \ No newline at end of file +- [useSize](./useSize.md) diff --git a/docs/useMediatedState.md b/docs/useMediatedState.md new file mode 100644 index 0000000000..0779a2e97d --- /dev/null +++ b/docs/useMediatedState.md @@ -0,0 +1,39 @@ +# `useMediatedState` + +A lot like the standard `useState`, but with mediation process. + +## Usage +```ts +import * as React from 'react'; +import { useMediatedState } from '../useMediatedState'; + +const inputMediator = s => s.replace(/[\s]+/g, ' '); +const Demo = () => { + const [state, setState] = useMediatedState(inputMediator, ''); + + return ( +
+
You will not be able to enter more than one space
+ ) => { + setState(ev.target.value); + }} + /> +
+ ); +}; +``` + +## Reference +```ts +const [state, setState] = useMediatedState( + mediator: StateMediator, + initialState?: S +); +``` + +> Initial state will be set as-is. + +In case mediator expects 2 arguments it will receive the `setState` function as second argument, it is useful for async mediators. +>This hook will not cancel previous mediation when new one been invoked, you have to handle it yourself._ diff --git a/docs/useMount.md b/docs/useMount.md index 95b53ae8b9..7b357c55ff 100644 --- a/docs/useMount.md +++ b/docs/useMount.md @@ -8,7 +8,7 @@ React lifecycle hook that calls a function after the component is mounted. Use ` import {useMount} from 'react-use'; const Demo = () => { - useMount(() => console.log('MOUNTED')); + useMount(() => alert('MOUNTED')); return null; }; ``` diff --git a/docs/useMultiStateValidator.md b/docs/useMultiStateValidator.md new file mode 100644 index 0000000000..42de5b1e88 --- /dev/null +++ b/docs/useMultiStateValidator.md @@ -0,0 +1,55 @@ +# `useMultiStateValidator` + +Each time any of given states changes - validator function is invoked. + +## Usage +```ts +import * as React from 'react'; +import { useMultiStateValidator } from 'react-use'; + +const DemoStateValidator = (s: number[]) => [s.every((num: number) => !(num % 2))] as [boolean]; +const Demo = () => { + const [state1, setState1] = React.useState(1); + const [state2, setState2] = React.useState(1); + const [state3, setState3] = React.useState(1); + const [[isValid]] = useMultiStateValidator([state1, state2, state3], DemoStateValidator); + + return ( +
+
Below fields will be valid if all of them is even
+ ) => { + setState1((ev.target.value as unknown) as number); + }} + /> + ) => { + setState2((ev.target.value as unknown) as number); + }} + /> + ) => { + setState3((ev.target.value as unknown) as number); + }} + /> + {isValid !== null && {isValid ? 'Valid!' : 'Invalid'}} +
+ ); +}; +``` + +## Reference +```ts +const [validity, revalidate] = useStateValidator( + state: any[] | { [p: string]: any } | { [p: number]: any }, + validator: (state, setValidity?)=>[boolean|null, ...any[]], + initialValidity: any = [undefined] +); +``` +- **`state`**_`: any[] | { [p: string]: any } | { [p: number]: any }`_ can be both an array or object. It's _values_ will be used as a deps for inner `useEffect`. +- **`validity`**_`: [boolean|null, ...any[]]`_ result of validity check. First element is strictly nullable boolean, but others can contain arbitrary data; +- **`revalidate`**_`: ()=>void`_ runs validator once again +- **`validator`**_`: (state, setValidity?)=>[boolean|null, ...any[]]`_ should return an array suitable for validity state described above; + - `states` - current states values as the've been passed to the hook; + - `setValidity` - if defined hook will not trigger validity change automatically. Useful for async validators; +- `initialValidity` - validity value which set when validity is nt calculated yet; diff --git a/docs/usePrevious.md b/docs/usePrevious.md index 953dee14e1..76c49d4abd 100644 --- a/docs/usePrevious.md +++ b/docs/usePrevious.md @@ -13,7 +13,11 @@ const Demo = () => { return (

- Now: {count}, before: {prevCount} + + +

+ Now: {count}, before: {prevCount} +

); }; diff --git a/docs/useRafState.md b/docs/useRafState.md new file mode 100644 index 0000000000..7740724bb3 --- /dev/null +++ b/docs/useRafState.md @@ -0,0 +1,33 @@ +# `useRafState` + +React state hook that only updates state in the callback of [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). + +## Usage + +```jsx +import {useRafState, useMount} from 'react-use'; + +const Demo = () => { + const [state, setState] = useRafState({ + width: 0, + height: 0, + }); + + useMount(() => { + const onResize = () => { + setState({ + width: window.clientWidth, + height: window.height, + }); + }; + + window.addEventListener('resize', onResize); + + return () => { + window.removeEventListener('resize', onResize); + }; + }); + + return
{JSON.stringify(state, null, 2)}
; +}; +``` diff --git a/docs/useRefMounted.md b/docs/useRefMounted.md deleted file mode 100644 index 0e7d708eb5..0000000000 --- a/docs/useRefMounted.md +++ /dev/null @@ -1,28 +0,0 @@ -# `useRefMounted` - ->**DEPRECATED** ->This method is obsolete, use `useMountedState` instead. - -Lifecycle hook that tracks if component is mounted. Returns a ref, which has a -boolean `.current` property. - - -## Usage - -```jsx -import {useRefMounted} from 'react-use'; - -const Demo = () => { - const refMounted = useRefMounted(); - - useEffect(() => { - setTimeout(() => { - if (refMounted.current) { - // ... - } else { - // ... - } - }, 1000); - }); -}; -``` diff --git a/docs/useSearchParam.md b/docs/useSearchParam.md index f118467656..6d9fe29c56 100644 --- a/docs/useSearchParam.md +++ b/docs/useSearchParam.md @@ -2,7 +2,6 @@ React sensor hook that tracks browser's location search param. - ## Usage ```jsx @@ -27,3 +26,7 @@ const Demo = () => { ); }; ``` + +## Caveats/Gotchas + +When using a hash router, like `react-router`'s [``](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/HashRouter.md), this hook won't be able to read the search parameters as they are considered part of the hash of the URL by browsers. diff --git a/docs/useSize.md b/docs/useSize.md index ec0d90b812..3784d01796 100644 --- a/docs/useSize.md +++ b/docs/useSize.md @@ -9,7 +9,8 @@ import {useSize} from 'react-use'; const Demo = () => { const [sized, {width, height}] = useSize( - ({width}) =>
Size me up! ({width}px)
+ ({width}) =>
Size me up! ({width}px)
, + { width: 100, height: 100 } ); return ( @@ -21,3 +22,16 @@ const Demo = () => { ); }; ``` + +## Reference + +```js +useSize(element, initialSize); +``` + +- `element` — sized element. +- `initialSize` — initial size containing a `width` and `height` key. + +## Related hooks + +- [useMeasure](./useMeasure.md) diff --git a/docs/useStateValidator.md b/docs/useStateValidator.md new file mode 100644 index 0000000000..0b434fcaf9 --- /dev/null +++ b/docs/useStateValidator.md @@ -0,0 +1,47 @@ +# `useStateValidator` + +Each time given state changes - validator function is invoked. + +## Usage +```ts +import * as React from 'react'; +import { useCallback } from 'react'; +import { useStateValidator } from 'react-use'; + +const DemoStateValidator = s => [s === '' ? null : (s * 1) % 2 === 0]; +const Demo = () => { + const [state, setState] = React.useState(0); + const [[isValid]] = useStateValidator(state, DemoStateValidator); + + return ( +
+
Below field is valid only if number is even
+ ) => { + setState(ev.target.value); + }} + /> + {isValid !== null && {isValid ? 'Valid!' : 'Invalid'}} +
+ ); +}; +``` + +## Reference +```ts +const [validity, revalidate] = useStateValidator( + state: any, + validator: (state, setValidity?)=>[boolean|null, ...any[]], + initialValidity: any +); +``` +- **`validity`**_`: [boolean|null, ...any[]]`_ result of validity check. First element is strictly nullable boolean, but others can contain arbitrary data; +- **`revalidate`**_`: ()=>void`_ runs validator once again +- **`validator`**_`: (state, setValidity?)=>[boolean|null, ...any[]]`_ should return an array suitable for validity state described above; + - `state` - current state; + - `setValidity` - if defined hook will not trigger validity change automatically. Useful for async validators; +- `initialValidity` - validity value which set when validity is nt calculated yet; diff --git a/docs/useToggle.md b/docs/useToggle.md index e1a342e76a..0405c6ce18 100644 --- a/docs/useToggle.md +++ b/docs/useToggle.md @@ -7,7 +7,7 @@ React state hook that tracks value of a boolean. ## Usage ```jsx -import {useToggle, useBoolean} from 'react-use'; +import {useToggle} from 'react-use'; const Demo = () => { const [on, toggle] = useToggle(true); diff --git a/docs/useUnmount.md b/docs/useUnmount.md index 83dc772219..c0a3e6c85b 100644 --- a/docs/useUnmount.md +++ b/docs/useUnmount.md @@ -8,7 +8,7 @@ React lifecycle hook that calls a function when the component will unmount. Use import {useUnmount} from 'react-use'; const Demo = () => { - useUnmount(() => console.log('UNMOUNTED')); + useUnmount(() => alert('UNMOUNTED')); return null; }; ``` diff --git a/docs/useUpsert.md b/docs/useUpsert.md index 2384660db7..48c3de554c 100644 --- a/docs/useUpsert.md +++ b/docs/useUpsert.md @@ -1,6 +1,7 @@ # `useUpsert` -Superset of `useList`. Provides an additional method to upsert (update or insert) an element into the list. +Superset of [`useList`](./useList.md). Provides an additional method to upsert (update or insert) an element into the list. + ## Usage ```jsx @@ -26,3 +27,7 @@ const Demo = () => { ); }; ``` + +## Related hooks + +- [useList](./useList.md) diff --git a/docs/useWait.md b/docs/useWait.md deleted file mode 100644 index 99f62ae2d2..0000000000 --- a/docs/useWait.md +++ /dev/null @@ -1,37 +0,0 @@ -# `useWait` - -`useWait` is a React Hook helps to manage multiple loading states on the page without any conflict. It's based on a very simple idea that manages an `Array` of multiple loading states. The built-in `Wait` component listens its registered loader and immediately become loading state. - - -## Usage - -```jsx -import { useWait } from 'react-use' - -function UserCreateButton() { - const { startWaiting, endWaiting, isWaiting, Wait } = useWait(); - - return ( -
}> - Create User - - - ); -} -``` - -And you should wrap your `App` with `Waiter` component. It's actually a `Context.Provider` that provides a loading context to the component tree. - -```jsx -const rootElement = document.getElementById("root"); -ReactDOM.render( - - - , - rootElement -); -``` diff --git a/package.json b/package.json index bea0b92ddd..d5659606ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "12.2.0", + "version": "13.2.2", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", @@ -15,6 +15,7 @@ "start": "yarn storybook", "test": "jest", "test:watch": "jest --watch", + "test:coverage": "jest --coverage", "lint": "tslint 'src/**/*.{ts,tsx}' -t verbose", "lint:fix": "yarn lint --fix", "lint:types": "tsc --noEmit", @@ -45,69 +46,69 @@ }, "homepage": "https://github.com/streamich/react-use#readme", "dependencies": { - "@types/react-wait": "^0.3.0", - "copy-to-clipboard": "^3.1.0", - "nano-css": "^5.1.0", + "copy-to-clipboard": "^3.2.0", + "nano-css": "^5.2.1", "react-fast-compare": "^2.0.4", - "react-wait": "^0.3.0", "resize-observer-polyfill": "^1.5.1", - "screenfull": "^4.1.0", - "set-harmonic-interval": "^1.0.0", - "throttle-debounce": "^2.0.1", - "ts-easing": "^0.2.0" + "screenfull": "^5.0.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^2.1.0", + "ts-easing": "^0.2.0", + "tslib": "^1.10.0" }, "peerDependencies": { "react": "^16.8.0", "react-dom": "^16.8.0" }, "devDependencies": { - "@babel/core": "7.6.0", + "@babel/core": "7.7.0", "@babel/plugin-syntax-dynamic-import": "7.2.0", - "@babel/preset-env": "7.6.0", - "@babel/preset-react": "7.0.0", - "@babel/preset-typescript": "7.6.0", - "@semantic-release/changelog": "3.0.4", - "@semantic-release/git": "7.0.16", - "@semantic-release/npm": "5.1.13", - "@storybook/addon-actions": "5.1.11", - "@storybook/addon-knobs": "5.1.11", - "@storybook/addon-notes": "5.1.11", - "@storybook/addon-options": "5.1.11", - "@storybook/react": "5.1.11", - "@testing-library/react-hooks": "2.0.1", - "@types/jest": "24.0.18", - "@types/react": "16.9.2", + "@babel/preset-env": "7.7.1", + "@babel/preset-react": "7.7.0", + "@babel/preset-typescript": "7.7.0", + "@semantic-release/changelog": "3.0.5", + "@semantic-release/git": "7.0.18", + "@semantic-release/npm": "5.3.4", + "@shopify/jest-dom-mocks": "2.8.5", + "@storybook/addon-actions": "5.2.5", + "@storybook/addon-knobs": "5.2.5", + "@storybook/addon-notes": "5.2.5", + "@storybook/addon-options": "5.2.5", + "@storybook/react": "5.2.5", + "@testing-library/react-hooks": "3.2.1", + "@types/jest": "24.0.22", + "@types/react": "16.9.11", "babel-core": "6.26.3", "babel-loader": "8.0.6", "babel-plugin-dynamic-import-node": "2.3.0", - "fork-ts-checker-webpack-plugin": "1.5.0", + "fork-ts-checker-webpack-plugin": "3.0.1", "gh-pages": "2.1.1", - "husky": "3.0.5", + "husky": "3.0.9", "jest": "24.9.0", "keyboardjs": "2.5.1", - "lint-staged": "9.2.5", + "lint-staged": "9.4.2", "markdown-loader": "5.1.0", "prettier": "1.18.2", "raf-stub": "3.0.0", - "react": "16.9.0", - "react-dom": "16.9.0", + "react": "16.11.0", + "react-dom": "16.11.0", "react-frame-component": "4.1.1", "react-spring": "8.0.27", - "react-test-renderer": "16.9.0", + "react-test-renderer": "16.11.0", "rebound": "0.1.0", "redux-logger": "3.0.6", "redux-thunk": "2.3.0", "rimraf": "3.0.0", "rxjs": "6.5.3", - "semantic-release": "15.13.24", - "ts-loader": "6.1.0", + "semantic-release": "15.13.30", + "ts-loader": "6.2.1", "ts-node": "8.4.1", - "tslint": "5.20.0", + "tslint": "6.0.0-beta1", "tslint-config-prettier": "1.18.0", "tslint-eslint-rules": "5.4.0", "tslint-plugin-prettier": "2.0.1", "tslint-react": "4.1.0", - "typescript": "3.5.3" + "typescript": "3.7.2" }, "config": { "commitizen": { @@ -140,8 +141,8 @@ ] }, "volta": { - "node": "10.16.0", - "yarn": "1.16.0" + "node": "10.17.0", + "yarn": "1.19.1" }, "collective": { "type": "opencollective", diff --git a/renovate.json b/renovate.json index 3d1bae418d..37ab773ab4 100644 --- a/renovate.json +++ b/renovate.json @@ -4,6 +4,7 @@ ], "automerge": true, "pinVersions": false, + "ignoreUnstable": true, "major": { "automerge": false }, diff --git a/src/__stories__/createBreakpoint.story.tsx b/src/__stories__/createBreakpoint.story.tsx new file mode 100644 index 0000000000..9ded8c1488 --- /dev/null +++ b/src/__stories__/createBreakpoint.story.tsx @@ -0,0 +1,29 @@ +import { number, withKnobs } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { createBreakpoint } from '..'; +import ShowDocs from './util/ShowDocs'; + +const useBreakpointA = createBreakpoint(); +const useBreakpointB = createBreakpoint({ mobileM: 350, laptop: 1024, tablet: 768 }); + +const Demo = () => { + const breakpointA = useBreakpointA(); + const breakpointB = useBreakpointB(); + return ( +
+

{'try resize your window'}

+

{'createBreakpoint() #default : { laptopL: 1440, laptop: 1024, tablet: 768 }'}

+

{breakpointA}

+

{'createBreakpoint({ mobileM: 350, laptop: 1024, tablet: 768 })'}

+

{breakpointB}

+
+ ); +}; + +storiesOf('sensors|createBreakpoint', module) + .addDecorator(withKnobs) + .add('Docs', () => ) + .add('Demo', () => { + return ; + }); diff --git a/src/__stories__/useEnsuredForwardedRef.story.tsx b/src/__stories__/useEnsuredForwardedRef.story.tsx new file mode 100644 index 0000000000..b2623f2fc4 --- /dev/null +++ b/src/__stories__/useEnsuredForwardedRef.story.tsx @@ -0,0 +1,79 @@ +import { storiesOf } from '@storybook/react'; +import React, { forwardRef, useRef, useState, useEffect, MutableRefObject } from 'react'; +import { useEnsuredForwardedRef } from '..'; +import ShowDocs from './util/ShowDocs'; + +import { boolean, withKnobs } from '@storybook/addon-knobs'; + +const INITIAL_SIZE = { + width: null, + height: null, +}; + +const Demo = ({ activeForwardRef }) => { + const ref = useRef(null); + + const [size, setSize] = useState(INITIAL_SIZE); + + useEffect(() => { + handleClick(); + }, [activeForwardRef]); + + const handleClick = () => { + if (activeForwardRef) { + const { width, height } = ref.current.getBoundingClientRect(); + setSize({ + width, + height, + }); + } else { + setSize(INITIAL_SIZE); + } + }; + + return ( + <> + +
Parent component using external ref: (textarea size)
+
{JSON.stringify(size, null, 2)}
+ + + ); +}; + +const Child = forwardRef(({}, ref: MutableRefObject) => { + const ensuredForwardRef = useEnsuredForwardedRef(ref); + + const [size, setSize] = useState(INITIAL_SIZE); + + useEffect(() => { + handleMouseUp(); + }, []); + + const handleMouseUp = () => { + const { width, height } = ensuredForwardRef.current.getBoundingClientRect(); + setSize({ + width, + height, + }); + }; + + return ( + <> +
Child forwardRef component using forwardRef: (textarea size)
+
{JSON.stringify(size, null, 2)}
+
You can resize this textarea:
+