├── .editorconfig ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── check-codebase.yml │ ├── mirror.yml │ └── release.yml ├── .gitignore ├── .storybook ├── addons.js ├── config.js └── webpack.config.js ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── babel.config.js ├── docs ├── Animations.md ├── Lifecycles.md ├── Sensors.md ├── Side-effects.md ├── State.md ├── UI.md ├── Usage.md ├── createBreakpoint.md ├── createGlobalState.md ├── createMemo.md ├── createReducer.md ├── createReducerContext.md ├── createStateContext.md ├── useAsync.md ├── useAsyncFn.md ├── useAsyncRetry.md ├── useAudio.md ├── useBattery.md ├── useBeforeUnload.md ├── useClickAway.md ├── useCookie.md ├── useCopyToClipboard.md ├── useCounter.md ├── useCss.md ├── useCustomCompareEffect.md ├── useDebounce.md ├── useDeepCompareEffect.md ├── useDefault.md ├── useDrop.md ├── useEffectOnce.md ├── useEnsuredForwardedRef.md ├── useError.md ├── useEvent.md ├── useFavicon.md ├── useFirstMountState.md ├── useFullscreen.md ├── useGeolocation.md ├── useGetSet.md ├── useGetSetState.md ├── useHarmonicIntervalFn.md ├── useHash.md ├── useHover.md ├── useIdle.md ├── useIntersection.md ├── useInterval.md ├── useIsomorphicLayoutEffect.md ├── useKey.md ├── useKeyPress.md ├── useKeyPressEvent.md ├── useKeyboardJs.md ├── useLatest.md ├── useLifecycles.md ├── useList.md ├── useLocalStorage.md ├── useLocation.md ├── useLockBodyScroll.md ├── useLogger.md ├── useLongPress.md ├── useMap.md ├── useMeasure.md ├── useMedia.md ├── useMediaDevices.md ├── useMediatedState.md ├── useMethods.md ├── useMotion.md ├── useMount.md ├── useMountedState.md ├── useMouse.md ├── useMouseWheel.md ├── useMultiStateValidator.md ├── useNetworkState.md ├── useObservable.md ├── useOrientation.md ├── usePageLeave.md ├── usePermission.md ├── usePinchZoom.md ├── usePrevious.md ├── usePreviousDistinct.md ├── usePromise.md ├── useQueue.md ├── useRaf.md ├── useRafLoop.md ├── useRafState.md ├── useRendersCount.md ├── useScratch.md ├── useScroll.md ├── useScrollbarWidth.md ├── useScrolling.md ├── useSearchParam.md ├── useSessionStorage.md ├── useSet.md ├── useSetState.md ├── useShallowCompareEffect.md ├── useSize.md ├── useSlider.md ├── useSpeech.md ├── useSpring.md ├── useStartTyping.md ├── useStateList.md ├── useStateValidator.md ├── useStateWithHistory.md ├── useThrottle.md ├── useThrottleFn.md ├── useTimeout.md ├── useTimeoutFn.md ├── useTitle.md ├── useToggle.md ├── useTween.md ├── useUnmount.md ├── useUnmountPromise.md ├── useUpdate.md ├── useUpdateEffect.md ├── useUpsert.md ├── useVibrate.md ├── useVideo.md ├── useWindowScroll.md └── useWindowSize.md ├── jest.config.base.ts ├── jest.config.node.ts ├── jest.config.ts ├── package.json ├── renovate.json ├── src ├── component │ └── UseKey.tsx ├── factory │ ├── createBreakpoint.ts │ ├── createGlobalState.ts │ ├── createHTMLMediaHook.ts │ ├── createMemo.ts │ ├── createReducer.ts │ ├── createReducerContext.ts │ ├── createRenderProp.ts │ ├── createRouter.ts │ └── createStateContext.ts ├── index.ts ├── misc │ ├── hookState.ts │ ├── isDeepEqual.ts │ ├── parseTimeRanges.ts │ ├── types.ts │ └── util.ts ├── useAsync.ts ├── useAsyncFn.ts ├── useAsyncRetry.ts ├── useAudio.ts ├── useBattery.ts ├── useBeforeUnload.ts ├── useBoolean.ts ├── useClickAway.ts ├── useCookie.ts ├── useCopyToClipboard.ts ├── useCounter.ts ├── useCss.ts ├── useCustomCompareEffect.ts ├── useDebounce.ts ├── useDeepCompareEffect.ts ├── useDefault.ts ├── useDrop.ts ├── useDropArea.ts ├── useEffectOnce.ts ├── useEnsuredForwardedRef.ts ├── useError.ts ├── useEvent.ts ├── useFavicon.ts ├── useFirstMountState.ts ├── useFullscreen.ts ├── useGeolocation.ts ├── useGetSet.ts ├── useGetSetState.ts ├── useHarmonicIntervalFn.ts ├── useHash.ts ├── useHover.ts ├── useHoverDirty.ts ├── useIdle.ts ├── useIntersection.ts ├── useInterval.ts ├── useIsomorphicLayoutEffect.ts ├── useKey.ts ├── useKeyPress.ts ├── useKeyPressEvent.ts ├── useKeyboardJs.ts ├── useLatest.ts ├── useLifecycles.ts ├── useList.ts ├── useLocalStorage.ts ├── useLocation.ts ├── useLockBodyScroll.ts ├── useLogger.ts ├── useLongPress.ts ├── useMap.ts ├── useMeasure.ts ├── useMeasureDirty.ts ├── useMedia.ts ├── useMediaDevices.ts ├── useMediatedState.ts ├── useMethods.ts ├── useMotion.ts ├── useMount.ts ├── useMountedState.ts ├── useMouse.ts ├── useMouseHovered.ts ├── useMouseWheel.ts ├── useMultiStateValidator.ts ├── useNetworkState.ts ├── useNumber.ts ├── useObservable.ts ├── useOrientation.ts ├── usePageLeave.ts ├── usePermission.ts ├── usePinchZoom.ts ├── usePrevious.ts ├── usePreviousDistinct.ts ├── usePromise.ts ├── useQueue.ts ├── useRaf.ts ├── useRafLoop.ts ├── useRafState.ts ├── useRendersCount.ts ├── useScratch.ts ├── useScroll.ts ├── useScrollbarWidth.ts ├── useScrolling.ts ├── useSearchParam.ts ├── useSessionStorage.ts ├── useSet.ts ├── useSetState.ts ├── useShallowCompareEffect.ts ├── useSize.tsx ├── useSlider.ts ├── useSpeech.ts ├── useSpring.ts ├── useStartTyping.ts ├── useStateList.ts ├── useStateValidator.ts ├── useStateWithHistory.ts ├── useThrottle.ts ├── useThrottleFn.ts ├── useTimeout.ts ├── useTimeoutFn.ts ├── useTitle.ts ├── useToggle.ts ├── useTween.ts ├── useUnmount.ts ├── useUnmountPromise.ts ├── useUpdate.ts ├── useUpdateEffect.ts ├── useUpsert.ts ├── useVibrate.ts ├── useVideo.ts ├── useWait.ts ├── useWindowScroll.ts └── useWindowSize.ts ├── stories ├── comps │ └── UseKey.story.tsx ├── createBreakpoint.story.tsx ├── createGlobalState.story.tsx ├── createMemo.story.tsx ├── createReducer.story.tsx ├── createReducerContext.story.tsx ├── createStateContext.story.tsx ├── useAsync.story.tsx ├── useAsyncFn.story.tsx ├── useAsyncRetry.story.tsx ├── useAudio.story.tsx ├── useBattery.story.tsx ├── useBeforeUnload.story.tsx ├── useBoolean.story.tsx ├── useClickAway.story.tsx ├── useCookie.story.tsx ├── useCopyToClipboard.story.tsx ├── useCounter.story.tsx ├── useCss.story.tsx ├── useCustomCompareEffect.story.tsx ├── useDebounce.story.tsx ├── useDeepCompareEffect.story.tsx ├── useDefault.story.tsx ├── useDrop.story.tsx ├── useDropArea.story.tsx ├── useEffectOnce.story.tsx ├── useEnsuredForwardedRef.story.tsx ├── useError.story.tsx ├── useEvent.story.tsx ├── useFavicon.story.tsx ├── useFirstMountState.story.tsx ├── useFullscreen.story.tsx ├── useGeolocation.story.tsx ├── useGetSet.story.tsx ├── useGetSetState.story.tsx ├── useHarmonicIntervalFn.story.tsx ├── useHash.story.tsx ├── useHover.story.tsx ├── useHoverDirty.story.tsx ├── useIdle.story.tsx ├── useIntersection.story.tsx ├── useInterval.story.tsx ├── useIsomorphicLayoutEffect.story.tsx ├── useKey.story.tsx ├── useKeyPress.story.tsx ├── useKeyPressEvent.story.tsx ├── useKeyboardJs.story.tsx ├── useLatest.story.tsx ├── useLifecycles.story.tsx ├── useList.story.tsx ├── useLocalStorage.story.tsx ├── useLocation.story.tsx ├── useLockBodyScroll.story.tsx ├── useLogger.story.tsx ├── useLongPress.story.tsx ├── useMap.story.tsx ├── useMeasure.story.tsx ├── useMedia.story.tsx ├── useMediaDevices.story.tsx ├── useMediatedState.story.tsx ├── useMethods.story.tsx ├── useMotion.story.tsx ├── useMount.story.tsx ├── useMountedState.story.tsx ├── useMouse.story.tsx ├── useMouseHovered.story.tsx ├── useMouseWheel.story.tsx ├── useMultiStateValidator.story.tsx ├── useNetwork.story.tsx ├── useObservable.story.tsx ├── useOrientation.story.tsx ├── usePageLeave.story.tsx ├── usePermission.story.tsx ├── usePinchZoom.story.tsx ├── usePrevious.story.tsx ├── usePreviousDistinct.story.tsx ├── usePromise.story.tsx ├── useQueue.story.tsx ├── useRaf.story.tsx ├── useRafLoop.story.tsx ├── useRafState.story.tsx ├── useRendersCount.story.tsx ├── useScratch.story.tsx ├── useScroll.story.tsx ├── useScrollbarWidth.story.tsx ├── useScrolling.story.tsx ├── useSearchParam.story.tsx ├── useSessionStorage.story.tsx ├── useSet.story.tsx ├── useSetState.story.tsx ├── useShallowCompareEffect.story.tsx ├── useSize.story.tsx ├── useSlider.story.tsx ├── useSpeech.story.tsx ├── useSpring.story.tsx ├── useStartTyping.story.tsx ├── useStateList.story.tsx ├── useStateValidator.story.tsx ├── useStateWithHistory.story.tsx ├── useThrottle.story.tsx ├── useThrottleFn.story.tsx ├── useTimeout.story.tsx ├── useTimeoutFn.story.tsx ├── useTitle.story.tsx ├── useToggle.story.tsx ├── useTween.story.tsx ├── useUnmount.story.tsx ├── useUpdate.story.tsx ├── useUpdateEffect.story.tsx ├── useUpsert.story.tsx ├── useVibrate.story.tsx ├── useVideo.story.tsx ├── useWindowScroll.story.tsx ├── useWindowSize.story.tsx └── util │ ├── CenterStory.tsx │ ├── ConsoleStory.tsx │ ├── NewTabStory.tsx │ └── ShowDocs.tsx ├── tests ├── createBreakpoint.test.ts ├── createGlobalState.test.ts ├── createMemo.test.ts ├── createReducer.test.ts ├── createReducerContext.test.tsx ├── createStateContext.test.tsx ├── misc │ └── hookState.test.ts ├── setupTests.ts ├── useAsync.test.tsx ├── useAsyncFn.test.tsx ├── useAudio.test.ts ├── useBoolean.test.ts ├── useCookie.test.tsx ├── useCopyToClipboard.test.ts ├── useCounter.test.ts ├── useCustomCompareEffect.test.ts ├── useDebounce.test.ts ├── useDeepCompareEffect.test.ts ├── useDefault.test.ts ├── useEffectOnce.test.ts ├── useEnsuredForwardedRef.test.tsx ├── useError.test.ts ├── useEvent.test.ts ├── useFavicon.test.tsx ├── useFirstMountState.test.ts ├── useGetSet.test.ts ├── useGetSetState.test.ts ├── useHash.test.ts ├── useIntersection.test.tsx ├── useInterval.test.ts ├── useLatest.test.ts ├── useList.test.ts ├── useLocalStorage.test.ts ├── useLogger.test.ts ├── useLongPress.test.tsx ├── useMap.test.ts ├── useMeasure.test.ts ├── useMedia.test.ts ├── useMediatedState.test.ts ├── useMethods.test.ts ├── useMount.test.ts ├── useMountedState.test.tsx ├── useMultiStateValidator.test.ts ├── useNetworkState.test.ts ├── useNumber.test.ts ├── useObservable.test.ts ├── useOrientation.test.ts ├── usePrevious.test.ts ├── usePreviousDistinct.test.tsx ├── useQueue.test.ts ├── useRaf.test.ts ├── useRafLoop.test.tsx ├── useRafState.test.ts ├── useRendersCount.test.ts ├── useScrollbarWidth.test.ts ├── useSearchParam.test.ts ├── useSet.test.ts ├── useSetState.test.ts ├── useShallowCompareEffect.test.ts ├── useSpring.test.ts ├── useStateList.test.ts ├── useStateValidator.test.ts ├── useStateWithHistory.test.ts ├── useThrottle.test.ts ├── useThrottleFn.test.ts ├── useTimeout.test.ts ├── useTimeoutFn.test.ts ├── useTitle.test.ts ├── useToggle.test.ts ├── useTween.test.ts ├── useUnmount.test.ts ├── useUnmountPromise.test.ts ├── useUpdate.test.ts ├── useUpdateEffect.test.ts ├── useUpsert.test.ts ├── useWindowScroll.test.tsx └── useWindowSize.test.tsx ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | max_line_length = 100 12 | 13 | [*.{ts, tsx}] 14 | ij_typescript_enforce_trailing_comma = keep 15 | ij_typescript_use_double_quotes = false 16 | ij_typescript_force_quote_style = true 17 | ij_typescript_align_imports = false 18 | ij_typescript_align_multiline_ternary_operation = false 19 | ij_typescript_align_multiline_parameters_in_calls = false 20 | ij_typescript_align_multiline_parameters = false 21 | ij_typescript_align_multiline_chained_methods = false 22 | ij_typescript_else_on_new_line = false 23 | ij_typescript_catch_on_new_line = false 24 | ij_typescript_spaces_within_interpolation_expressions = false 25 | 26 | [*.md] 27 | max_line_length = 100 28 | trim_trailing_whitespace = false 29 | 30 | [COMMIT_EDITMSG] 31 | max_line_length = 80 32 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['react-app', 'prettier'], 3 | plugins: ['prettier'], 4 | rules: { 5 | 'prettier/prettier': [ 6 | 'error', 7 | { 8 | singleQuote: true, 9 | trailingComma: 'es5', 10 | tabWidth: 2, 11 | printWidth: 100, 12 | semicolons: true, 13 | quoteProps: 'as-needed', 14 | jsxSingleQuote: false, 15 | bracketSpacing: true, 16 | jsxBracketSameLine: true, 17 | arrowParens: 'always', 18 | endOfLine: 'lf', 19 | }, 20 | ], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report if you having any problems using the package 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is the current behavior?** 11 | 12 | **Steps to reproduce it and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have extra dependencies other than `react-use`. Paste the link to your [JSFiddle](https://jsfiddle.net) or [CodeSandbox](https://codesandbox.io) example below:** 13 | 14 | **What is the expected behavior?** 15 | 16 | **A little about versions:** 17 | - _OS_: 18 | - _Browser (vendor and version)_: 19 | - _React_: 20 | - _`react-use`_: 21 | - _Did this worked in the previous package version?_ 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Have an idea? Great! Let us know, maybe we`ve been waiting only for you =) 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | -------------------------------------------------------------------------------- /.github/workflows/mirror.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | mirror: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: Push To Gitlab 15 | env: 16 | token: ${{ secrets.GITLAB_TOKEN }} 17 | run: | 18 | git config user.name "streamich" 19 | git config user.email "react-use+streamich@users.noreply.github.com" 20 | git remote add mirror "https://oauth2:${token}@gitlab.com/streamich/react-use.git" 21 | git push mirror master 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master, next] 6 | 7 | jobs: 8 | release: 9 | if: ${{ github.event_name == 'push' && (github.event.ref == 'refs/heads/master' || github.event.ref == 'refs/heads/next') }} 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [22.x] 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | cache: yarn 21 | - run: yarn install --frozen-lockfile 22 | - run: yarn lint 23 | - run: yarn test 24 | - run: yarn lint:types 25 | - run: yarn build 26 | - name: Semantic Release 27 | uses: cycjimmy/semantic-release-action@v4 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register'; 2 | import '@storybook/addon-options/register'; 3 | import '@storybook/addon-actions/register'; 4 | import '@storybook/addon-notes/register'; 5 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import {configure} from '@storybook/react'; 2 | import {setOptions} from '@storybook/addon-options'; 3 | 4 | setOptions({ 5 | sortStoriesByKind: false, 6 | showStoriesPanel: true, 7 | showAddonPanel: true, 8 | showSearchBox: false, 9 | addonPanelInRight: true, 10 | hierarchySeparator: /\//, 11 | hierarchyRootSeparator: /\|/, 12 | sidebarAnimations: false, 13 | }); 14 | 15 | const req = require.context('../stories/', true, /\.story\.tsx?$/); 16 | 17 | const loadStories = () => { 18 | req.keys().forEach((filename) => req(filename)); 19 | }; 20 | 21 | configure(loadStories, module); 22 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { compilerOptions } = require('../tsconfig.json'); 3 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 4 | 5 | const basedir = path.join(__dirname, '..'); 6 | 7 | module.exports = async ({ config, mode }) => { 8 | config.module.rules.push( 9 | { 10 | test: /\.md?$/, 11 | loader: "markdown-loader", 12 | }, 13 | { 14 | test: /\.tsx?$/, 15 | loader: 'ts-loader', 16 | include: [ 17 | path.join(basedir, 'src'), 18 | path.join(basedir, 'stories'), 19 | ], 20 | options: { 21 | transpileOnly: true, // use transpileOnly mode to speed-up compilation 22 | compilerOptions: { 23 | ...compilerOptions, 24 | declaration: false, 25 | }, 26 | }, 27 | }, 28 | ); 29 | 30 | config.plugins.push(new ForkTsCheckerWebpackPlugin()); 31 | 32 | config.resolve.extensions = ['.ts', '.tsx', '.js', '.jsx']; 33 | config.resolve.enforceExtension = false; 34 | 35 | // disable the hint about too big bundle 36 | config.performance.hints = false; 37 | 38 | return config; 39 | }; 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | os: 3 | - linux 4 | cache: 5 | yarn: true 6 | directories: 7 | - ~/.npm 8 | notifications: 9 | email: false 10 | node_js: 11 | - '10' 12 | script: 13 | - yarn lint 14 | - yarn test 15 | - yarn build 16 | - yarn storybook:build 17 | matrix: 18 | allow_failures: [] 19 | fast_finish: true 20 | branches: 21 | except: 22 | - /^v\d+\.\d+\.\d+$/ 23 | - master 24 | - next 25 | - gh-pages 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We release patches for security vulnerabilities. The latest major version 6 | will support security patches. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | Please report (suspected) security vulnerabilities to 11 | **[streamich@gmail.com](mailto:streamich@gmail.com)**. We will try to respond 12 | within 48 hours. If the issue is confirmed, we will release a patch as soon 13 | as possible depending on complexity. 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | targets: { 7 | node: "current" 8 | } 9 | } 10 | ], 11 | "@babel/preset-react", 12 | "@babel/preset-typescript" 13 | ], 14 | env: { 15 | test: { 16 | plugins: ['dynamic-import-node'] 17 | }, 18 | production: { 19 | plugins: ['@babel/plugin-syntax-dynamic-import'] 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /docs/Animations.md: -------------------------------------------------------------------------------- 1 | # Animations 2 | 3 | *"Animation Hooks"* usually interpolate numeric values over time. 4 | -------------------------------------------------------------------------------- /docs/Lifecycles.md: -------------------------------------------------------------------------------- 1 | # Lifecycle 2 | 3 | *"Lifecycle Hooks"* modify and extend built-in React hooks or imitate React Class component lifecycle patterns. 4 | -------------------------------------------------------------------------------- /docs/Sensors.md: -------------------------------------------------------------------------------- 1 | # Sensors 2 | 3 | *"Sensor Hooks"* listen to changes in some interface and force your components 4 | to be re-rendered with the new state, up-to-date state. 5 | -------------------------------------------------------------------------------- /docs/Side-effects.md: -------------------------------------------------------------------------------- 1 | # Side-effects 2 | 3 | *"Side-effect Hooks"* allow your app trigger various side-effects using browser's API. 4 | -------------------------------------------------------------------------------- /docs/State.md: -------------------------------------------------------------------------------- 1 | # State 2 | 3 | *"State Hooks"* allow you to easily manage state of booleans, arrays, and maps. -------------------------------------------------------------------------------- /docs/UI.md: -------------------------------------------------------------------------------- 1 | # UI 2 | 3 | *"UI Hooks"* allow you to control and subscribe to state changes of UI elements. 4 | -------------------------------------------------------------------------------- /docs/createMemo.md: -------------------------------------------------------------------------------- 1 | # `createMemo` 2 | 3 | Hook factory, receives a function to be memoized, returns a memoized React hook, 4 | which receives the same arguments and returns the same result as the original function. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {createMemo} from 'react-use'; 11 | 12 | const fibonacci = n => { 13 | if (n === 0) return 0; 14 | if (n === 1) return 1; 15 | return fibonacci(n - 1) + fibonacci(n - 2); 16 | }; 17 | 18 | const useMemoFibonacci = createMemo(fibonacci); 19 | 20 | const Demo = () => { 21 | const result = useMemoFibonacci(10); 22 | 23 | return ( 24 |
25 | fib(10) = {result} 26 |
27 | ); 28 | }; 29 | ``` 30 | 31 | 32 | ## Reference 33 | 34 | ```js 35 | const useMemoFn = createMemo(fn); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/useAsync.md: -------------------------------------------------------------------------------- 1 | # `useAsync` 2 | 3 | React hook that resolves an `async` function or a function that returns 4 | a promise; 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useAsync} from 'react-use'; 10 | 11 | const Demo = ({url}) => { 12 | const state = useAsync(async () => { 13 | const response = await fetch(url); 14 | const result = await response.text(); 15 | return result 16 | }, [url]); 17 | 18 | return ( 19 |
20 | {state.loading 21 | ?
Loading...
22 | : state.error 23 | ?
Error: {state.error.message}
24 | :
Value: {state.value}
25 | } 26 |
27 | ); 28 | }; 29 | ``` 30 | 31 | ## Reference 32 | 33 | ```ts 34 | useAsync(fn, args?: any[]); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/useAsyncFn.md: -------------------------------------------------------------------------------- 1 | # `useAsyncFn` 2 | 3 | React hook that returns state and a callback for an `async` function or a 4 | function that returns a promise. The state is of the same shape as `useAsync`. 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useAsyncFn} from 'react-use'; 10 | 11 | const Demo = ({url}) => { 12 | const [state, doFetch] = useAsyncFn(async () => { 13 | const response = await fetch(url); 14 | const result = await response.text(); 15 | return result 16 | }, [url]); 17 | 18 | return ( 19 |
20 | {state.loading 21 | ?
Loading...
22 | : state.error 23 | ?
Error: {state.error.message}
24 | :
Value: {state.value}
25 | } 26 | 27 |
28 | ); 29 | }; 30 | ``` 31 | 32 | ## Reference 33 | 34 | ```ts 35 | useAsyncFn(fn, deps?: any[], initialState?: AsyncState); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/useAsyncRetry.md: -------------------------------------------------------------------------------- 1 | # `useAsyncRetry` 2 | 3 | Uses `useAsync` with an additional `retry` method to easily retry/refresh the async function; 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useAsyncRetry} from 'react-use'; 9 | 10 | const Demo = ({url}) => { 11 | const state = useAsyncRetry(async () => { 12 | const response = await fetch(url); 13 | const result = await response.text(); 14 | return result; 15 | }, [url]); 16 | 17 | return ( 18 |
19 | {state.loading 20 | ?
Loading...
21 | : state.error 22 | ?
Error: {state.error.message}
23 | :
Value: {state.value}
24 | } 25 | {!loading && } 26 |
27 | ); 28 | }; 29 | ``` 30 | 31 | ## Reference 32 | 33 | ```ts 34 | useAsyncRetry(fn, args?: any[]); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/useClickAway.md: -------------------------------------------------------------------------------- 1 | # `useClickAway` 2 | 3 | React UI hook that triggers a callback when user 4 | clicks outside the target element. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {useClickAway} from 'react-use'; 11 | 12 | const Demo = () => { 13 | const ref = useRef(null); 14 | useClickAway(ref, () => { 15 | console.log('OUTSIDE CLICKED'); 16 | }); 17 | 18 | return ( 19 |
24 | ); 25 | }; 26 | ``` 27 | 28 | ## Reference 29 | 30 | ```js 31 | useClickAway(ref, onMouseEvent) 32 | useClickAway(ref, onMouseEvent, ['click']) 33 | useClickAway(ref, onMouseEvent, ['mousedown', 'touchstart']) 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/useCookie.md: -------------------------------------------------------------------------------- 1 | # `useCookie` 2 | 3 | React hook that returns the current value of a `cookie`, a callback to update the `cookie` 4 | and a callback to delete the `cookie.` 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import { useCookie } from "react-use"; 10 | 11 | const Demo = () => { 12 | const [value, updateCookie, deleteCookie] = useCookie("my-cookie"); 13 | const [counter, setCounter] = useState(1); 14 | 15 | useEffect(() => { 16 | deleteCookie(); 17 | }, []); 18 | 19 | const updateCookieHandler = () => { 20 | updateCookie(`my-awesome-cookie-${counter}`); 21 | setCounter(c => c + 1); 22 | }; 23 | 24 | return ( 25 |
26 |

Value: {value}

27 | 28 |
29 | 30 |
31 | ); 32 | }; 33 | ``` 34 | 35 | ## Reference 36 | 37 | ```ts 38 | const [value, updateCookie, deleteCookie] = useCookie(cookieName: string); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/useCopyToClipboard.md: -------------------------------------------------------------------------------- 1 | # `useCopyToClipboard` 2 | 3 | Copy text to a user's clipboard. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | const Demo = () => { 9 | const [text, setText] = React.useState(''); 10 | const [state, copyToClipboard] = useCopyToClipboard(); 11 | 12 | return ( 13 |
14 | setText(e.target.value)} /> 15 | 16 | {state.error 17 | ?

Unable to copy value: {state.error.message}

18 | : state.value &&

Copied {state.value}

} 19 |
20 | ) 21 | } 22 | ``` 23 | 24 | ## Reference 25 | 26 | ```js 27 | const [{value, error, noUserInteraction}, copyToClipboard] = useCopyToClipboard(); 28 | ``` 29 | 30 | - `value` — value that was copied to clipboard, undefined when nothing was copied. 31 | - `error` — caught error when trying to copy to clipboard. 32 | - `noUserInteraction` — boolean indicating if user interaction was required to copy the value to clipboard to expose full API from underlying [`copy-to-clipboard`](https://github.com/sudodoki/copy-to-clipboard) library. 33 | -------------------------------------------------------------------------------- /docs/useCustomCompareEffect.md: -------------------------------------------------------------------------------- 1 | # `useCustomCompareEffect` 2 | 3 | A modified useEffect hook that accepts a comparator which is used for comparison on dependencies instead of reference equality. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useCounter, useCustomCompareEffect} from 'react-use'; 9 | import isEqual from 'lodash/isEqual'; 10 | 11 | const Demo = () => { 12 | const [count, {inc: inc}] = useCounter(0); 13 | const options = { step: 2 }; 14 | 15 | useCustomCompareEffect(() => { 16 | inc(options.step) 17 | }, [options], (prevDeps, nextDeps) => isEqual(prevDeps, nextDeps)); 18 | 19 | return ( 20 |
21 |

useCustomCompareEffect with deep comparison: {count}

22 |
23 | ); 24 | }; 25 | ``` 26 | 27 | ## Reference 28 | 29 | ```ts 30 | useCustomCompareEffect(effect: () => void | (() => void | undefined), deps: any[], depsEqual: (prevDeps: any[], nextDeps: any[]) => boolean); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/useDeepCompareEffect.md: -------------------------------------------------------------------------------- 1 | # `useDeepCompareEffect` 2 | 3 | A modified useEffect hook that is using deep comparison on its dependencies instead of reference equality. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useCounter, useDeepCompareEffect} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [count, {inc: inc}] = useCounter(0); 12 | const options = { step: 2 }; 13 | 14 | useDeepCompareEffect(() => { 15 | inc(options.step) 16 | }, [options]); 17 | 18 | return ( 19 |
20 |

useDeepCompareEffect: {count}

21 |
22 | ); 23 | }; 24 | ``` 25 | 26 | ## Reference 27 | 28 | ```ts 29 | useDeepCompareEffect(effect: () => void | (() => void | undefined), deps: any[]); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/useDefault.md: -------------------------------------------------------------------------------- 1 | # `useDefault` 2 | 3 | React state hook that returns the default value when state is null or undefined. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useDefault} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const initialUser = { name: 'Marshall' } 12 | const defaultUser = { name: 'Mathers' } 13 | const [user, setUser] = useDefault(defaultUser, initialUser); 14 | 15 | return ( 16 |
17 |
User: {user.name}
18 | setUser({ name: e.target.value })} /> 19 | 20 |
21 | ); 22 | }; 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/useDrop.md: -------------------------------------------------------------------------------- 1 | # `useDrop` and `useDropArea` 2 | 3 | Triggers on file, link drop and copy-paste. 4 | 5 | `useDrop` tracks events for the whole page, `useDropArea` tracks drop events 6 | for a specific element. 7 | 8 | 9 | ## Usage 10 | 11 | `useDrop`: 12 | 13 | ```jsx 14 | import {useDrop} from 'react-use'; 15 | 16 | const Demo = () => { 17 | const state = useDrop({ 18 | onFiles: files => console.log('files', files), 19 | onUri: uri => console.log('uri', uri), 20 | onText: text => console.log('text', text), 21 | }); 22 | 23 | return ( 24 |
25 | Drop something on the page. 26 |
27 | ); 28 | }; 29 | ``` 30 | 31 | `useDropArea`: 32 | 33 | ```jsx 34 | import {useDropArea} from 'react-use'; 35 | 36 | const Demo = () => { 37 | const [bond, state] = useDropArea({ 38 | onFiles: files => console.log('files', files), 39 | onUri: uri => console.log('uri', uri), 40 | onText: text => console.log('text', text), 41 | }); 42 | 43 | return ( 44 |
45 | Drop something here. 46 |
47 | ); 48 | }; 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/useEffectOnce.md: -------------------------------------------------------------------------------- 1 | # `useEffectOnce` 2 | 3 | React lifecycle hook that runs an effect only once. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useEffectOnce} from 'react-use'; 9 | 10 | const Demo = () => { 11 | useEffectOnce(() => { 12 | console.log('Running effect once on mount') 13 | 14 | return () => { 15 | console.log('Running clean-up of effect on unmount') 16 | } 17 | }); 18 | 19 | return null; 20 | }; 21 | ``` 22 | 23 | ## Reference 24 | 25 | ```js 26 | useEffectOnce(effect: EffectCallback); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/useError.md: -------------------------------------------------------------------------------- 1 | # `useError` 2 | 3 | React side-effect hook that returns an error dispatcher. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import { useError } from 'react-use'; 9 | 10 | const Demo = () => { 11 | const dispatchError = useError(); 12 | 13 | const clickHandler = () => { 14 | dispatchError(new Error('Some error!')); 15 | }; 16 | 17 | return ; 18 | }; 19 | 20 | // In parent app 21 | const App = () => ( 22 | 23 | 24 | 25 | ); 26 | ``` 27 | 28 | ## Reference 29 | 30 | ```js 31 | const dispatchError = useError(); 32 | ``` 33 | 34 | - `dispatchError` — Callback of type `(err: Error) => void` 35 | -------------------------------------------------------------------------------- /docs/useEvent.md: -------------------------------------------------------------------------------- 1 | # `useEvent` 2 | 3 | React sensor hook that subscribes a `handler` to events. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useEvent, useList} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const [list, {push, clear}] = useList(); 13 | 14 | const onKeyDown = useCallback(({key}) => { 15 | if (key === 'r') clear(); 16 | push(key); 17 | }, []); 18 | 19 | useEvent('keydown', onKeyDown); 20 | 21 | return ( 22 |
23 |

24 | Press some keys on your keyboard, r key resets the list 25 |

26 |
27 |         {JSON.stringify(list, null, 4)}
28 |       
29 |
30 | ); 31 | }; 32 | ``` 33 | 34 | 35 | ## Examples 36 | 37 | ```js 38 | useEvent('keydown', handler) 39 | useEvent('scroll', handler, window, {capture: true}) 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/useFavicon.md: -------------------------------------------------------------------------------- 1 | # `useFavicon` 2 | 3 | React side-effect hook sets the favicon of the page. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useFavicon} from 'react-use'; 10 | 11 | const Demo = () => { 12 | useFavicon('https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico'); 13 | 14 | return null; 15 | }; 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/useFirstMountState.md: -------------------------------------------------------------------------------- 1 | # `useFirstMountState` 2 | 3 | Returns `true` if component is just mounted (on first render) and `false` otherwise. 4 | 5 | ## Usage 6 | 7 | ```typescript jsx 8 | import * as React from 'react'; 9 | import { useFirstMountState } from 'react-use'; 10 | 11 | const Demo = () => { 12 | const isFirstMount = useFirstMountState(); 13 | const update = useUpdate(); 14 | 15 | return ( 16 |
17 | This component is just mounted: {isFirstMount ? 'YES' : 'NO'} 18 |
19 | 20 |
21 | ); 22 | }; 23 | ``` 24 | 25 | ## Reference 26 | 27 | ```typescript 28 | const isFirstMount: boolean = useFirstMountState(); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/useFullscreen.md: -------------------------------------------------------------------------------- 1 | # `useFullscreen` 2 | 3 | Display an element full-screen, optional fallback for fullscreen video on iOS. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useFullscreen, useToggle} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const ref = useRef(null) 12 | const [show, toggle] = useToggle(false); 13 | const isFullscreen = useFullscreen(ref, show, {onClose: () => toggle(false)}); 14 | 15 | return ( 16 |
17 |
{isFullscreen ? 'Fullscreen' : 'Not fullscreen'}
18 | 19 |
21 | ); 22 | }; 23 | ``` 24 | 25 | ## Reference 26 | 27 | ```ts 28 | useFullscreen(ref, show, {onClose}) 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/useGeolocation.md: -------------------------------------------------------------------------------- 1 | # `useGeolocation` 2 | 3 | React sensor hook that tracks user's geographic location. This hook accepts [position options](https://developer.mozilla.org/docs/Web/API/PositionOptions). 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useGeolocation} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const state = useGeolocation(); 12 | 13 | return ( 14 |
15 |       {JSON.stringify(state, null, 2)}
16 |     
17 | ); 18 | }; 19 | ``` 20 | 21 | ## Reference 22 | 23 | ```ts 24 | useGeolocation(options: PositionOptions) 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/useGetSet.md: -------------------------------------------------------------------------------- 1 | # `useGetSet` 2 | 3 | React state hook that returns state getter function instead of 4 | raw state itself, this prevents subtle bugs when state is used 5 | in nested functions. 6 | 7 | 8 | ## Usage 9 | 10 | Below example uses `useGetSet` to increment a number after 1 second 11 | on each click. 12 | 13 | ```jsx 14 | import {useGetSet} from 'react-use'; 15 | 16 | const Demo = () => { 17 | const [get, set] = useGetSet(0); 18 | const onClick = () => { 19 | setTimeout(() => { 20 | set(get() + 1) 21 | }, 1_000); 22 | }; 23 | 24 | return ( 25 | 26 | ); 27 | }; 28 | ``` 29 | 30 | If you would do this example in a naive way using regular `useState` 31 | hook, the counter would not increment correctly if you click fast multiple times. 32 | 33 | ```jsx 34 | const DemoWrong = () => { 35 | const [cnt, set] = useState(0); 36 | const onClick = () => { 37 | setTimeout(() => { 38 | set(cnt + 1) 39 | }, 1_000); 40 | }; 41 | 42 | return ( 43 | 44 | ); 45 | }; 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/useGetSetState.md: -------------------------------------------------------------------------------- 1 | # `useGetSetState` 2 | 3 | A mix of `useGetSet` and `useGetSetState`. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useGetSetState} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const [get, setState] = useGetSetState({cnt: 0}); 13 | const onClick = () => { 14 | setTimeout(() => { 15 | setState({cnt: get().cnt + 1}) 16 | }, 1000); 17 | }; 18 | 19 | return ( 20 | 21 | ); 22 | }; 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/useHarmonicIntervalFn.md: -------------------------------------------------------------------------------- 1 | # `useHarmonicIntervalFn` 2 | 3 | Same as [`useInterval`](./useInterval.md) hook, but triggers all effects with the same delay 4 | at the same time. 5 | 6 | For example, this allows you to create ticking clocks on the page which re-render second counter 7 | all at the same time. 8 | 9 | 10 | ## Reference 11 | 12 | ```js 13 | useHarmonicIntervalFn(fn, delay?: number) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/useHash.md: -------------------------------------------------------------------------------- 1 | # `useHash` 2 | 3 | React sensor hook that tracks browser's location hash. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useHash} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [hash, setHash] = useHash(); 12 | 13 | useMount(() => { 14 | setHash('#/path/to/page?userId=123'); 15 | }); 16 | 17 | return ( 18 |
19 |
window.location.href:
20 |
21 |
{window.location.href}
22 |
23 |
Edit hash:
24 |
25 | setHash(e.target.value)} /> 26 |
27 |
28 | ); 29 | }; 30 | ``` 31 | 32 | ## API 33 | 34 | `const [hash, setHash] = useHash()` 35 | 36 | Get latest url hash with `hash` and set url hash with `setHash`. 37 | 38 | - `hash: string`: get current url hash. listen to `hashchange` event. 39 | - `setHash: (newHash: string) => void`: change url hash. Invoke this method will trigger `hashchange` event. -------------------------------------------------------------------------------- /docs/useHover.md: -------------------------------------------------------------------------------- 1 | # `useHover` and `useHoverDirty` 2 | 3 | React UI sensor hooks that track if some element is being hovered 4 | by a mouse. 5 | 6 | - `useHover` accepts a React element or a function that returns one, 7 | `useHoverDirty` accepts React ref. 8 | - `useHover` sets react `onMouseEnter` and `onMouseLeave` events, 9 | `useHoverDirty` sets DOM `onmouseover` and `onmouseout` events. 10 | 11 | 12 | ## Usage 13 | 14 | ```jsx 15 | import {useHover} from 'react-use'; 16 | 17 | const Demo = () => { 18 | const element = (hovered) => 19 |
20 | Hover me! {hovered && 'Thanks!'} 21 |
; 22 | const [hoverable, hovered] = useHover(element); 23 | 24 | return ( 25 |
26 | {hoverable} 27 |
{hovered ? 'HOVERED' : ''}
28 |
29 | ); 30 | }; 31 | ``` 32 | 33 | 34 | ## Reference 35 | 36 | ```js 37 | const [newReactElement, isHovering] = useHover(reactElement); 38 | const [newReactElement, isHovering] = useHover((isHovering) => reactElement); 39 | const isHovering = useHoverDirty(ref); 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/useIdle.md: -------------------------------------------------------------------------------- 1 | # `useIdle` 2 | 3 | React sensor hook that tracks if user on the page is idle. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useIdle} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const isIdle = useIdle(3e3); 13 | 14 | return ( 15 |
16 |
User is idle: {isIdle ? 'Yes 😴' : 'Nope'}
17 |
18 | ); 19 | }; 20 | ``` 21 | 22 | 23 | ## Reference 24 | 25 | ```js 26 | useIdle(ms, initialState); 27 | ``` 28 | 29 | - `ms` — time in milliseconds after which to consider use idle, defaults to `60e3` — one minute. 30 | - `initialState` — whether to consider user initially idle, defaults to false. 31 | -------------------------------------------------------------------------------- /docs/useIntersection.md: -------------------------------------------------------------------------------- 1 | # `useIntersection` 2 | 3 | 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). 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import * as React from 'react'; 9 | import { useIntersection } from 'react-use'; 10 | 11 | const Demo = () => { 12 | const intersectionRef = React.useRef(null); 13 | const intersection = useIntersection(intersectionRef, { 14 | root: null, 15 | rootMargin: '0px', 16 | threshold: 1 17 | }); 18 | 19 | return ( 20 |
21 | {intersection && intersection.intersectionRatio < 1 22 | ? 'Obscured' 23 | : 'Fully in view'} 24 |
25 | ); 26 | }; 27 | ``` 28 | 29 | ## Reference 30 | 31 | ```ts 32 | useIntersection( 33 | ref: RefObject, 34 | options: IntersectionObserverInit, 35 | ): IntersectionObserverEntry | null; 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/useInterval.md: -------------------------------------------------------------------------------- 1 | # `useInterval` 2 | 3 | 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`. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import * as React from 'react'; 9 | import {useInterval} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const [count, setCount] = React.useState(0); 13 | const [delay, setDelay] = React.useState(1000); 14 | const [isRunning, toggleIsRunning] = useBoolean(true); 15 | 16 | useInterval( 17 | () => { 18 | setCount(count + 1); 19 | }, 20 | isRunning ? delay : null 21 | ); 22 | 23 | return ( 24 |
25 |
26 | delay: setDelay(Number(event.target.value))} /> 27 |
28 |

count: {count}

29 |
30 | 31 |
32 |
33 | ); 34 | }; 35 | ``` 36 | 37 | ## Reference 38 | 39 | ```js 40 | useInterval(callback, delay?: number) 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/useIsomorphicLayoutEffect.md: -------------------------------------------------------------------------------- 1 | # `useIsomorphicLayoutEffect` 2 | 3 | `useLayoutEffect` that does not show warning when server-side rendering, see [Alex Reardon's article](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a) for more info. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useIsomorphicLayoutEffect} from 'react-use'; 9 | 10 | const Demo = ({value}) => { 11 | useIsomorphicLayoutEffect(() => { 12 | window.console.log(value) 13 | }, [value]); 14 | 15 | return null; 16 | }; 17 | ``` 18 | 19 | 20 | ## Reference 21 | 22 | ```ts 23 | useIsomorphicLayoutEffect(effect: EffectCallback, deps?: ReadonlyArray | undefined); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/useKey.md: -------------------------------------------------------------------------------- 1 | # `useKey` 2 | 3 | React UI sensor hook that executes a `handler` when a keyboard key is used. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useKey} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [count, set] = useState(0); 12 | const increment = () => set(count => ++count); 13 | useKey('ArrowUp', increment); 14 | 15 | return ( 16 |
17 | Press arrow up: {count} 18 |
19 | ); 20 | }; 21 | ``` 22 | 23 | Or as render-prop: 24 | 25 | ```jsx 26 | import UseKey from 'react-use/lib/component/UseKey'; 27 | 28 | alert('"a" key pressed!')} /> 29 | ``` 30 | 31 | 32 | ## Reference 33 | 34 | ```js 35 | useKey(filter, handler, options?, deps?) 36 | ``` 37 | 38 | 39 | ## Examples 40 | 41 | ```js 42 | useKey('a', () => alert('"a" pressed')); 43 | 44 | const predicate = (event) => event.key === 'a' 45 | useKey(predicate, handler, {event: 'keyup'}); 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/useKeyPress.md: -------------------------------------------------------------------------------- 1 | # `useKeyPress` 2 | 3 | React UI sensor hook that detects when the user is pressing a specific 4 | key on their keyboard. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {useKeyPress} from 'react-use'; 11 | 12 | const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; 13 | 14 | const Demo = () => { 15 | const states = []; 16 | for (const key of keys) states.push(useKeyPress(key)[0]); 17 | 18 | return ( 19 |
20 | Try pressing numbers 21 |
22 | {states.reduce((s, pressed, index) => s + (pressed ? (s ? ' + ' : '') + keys[index] : ''), '')} 23 |
24 | ); 25 | }; 26 | ``` 27 | 28 | 29 | ## Examples 30 | 31 | ```js 32 | const isPressed = useKeyPress('a'); 33 | 34 | const predicate = (event) => event.key === 'a'; 35 | const isPressed = useKeyPress(predicate); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/useKeyboardJs.md: -------------------------------------------------------------------------------- 1 | # `useKeyboardJs` 2 | 3 | React UI sensor hook that detects complex key combos like detecting when 4 | multiple keys are held down at the same time or requiring them to be held down in a specified order. 5 | 6 | Via [KeyboardJS key combos](https://github.com/RobertWHurst/KeyboardJS). 7 | Check its documentation for further details on how to make combo strings. 8 | 9 | ## Usage 10 | 11 | ```jsx 12 | import useKeyboardJs from 'react-use/lib/useKeyboardJs'; 13 | 14 | const Demo = () => { 15 | const [isPressed] = useKeyboardJs('a + b'); 16 | 17 | return ( 18 |
19 | [a + b] pressed: {isPressed ? 'Yes' : 'No'} 20 |
21 | ); 22 | }; 23 | ``` 24 | 25 | Note: Because of dependency on `keyboardjs` you have to import this hook directly like shown above. 26 | 27 | ## Requirements 28 | 29 | Install [`keyboardjs`](https://github.com/RobertWHurst/KeyboardJS) peer dependency: 30 | 31 | ```bash 32 | npm add keyboardjs 33 | # or 34 | yarn add keyboardjs 35 | ``` 36 | 37 | ## Reference 38 | 39 | ```js 40 | useKeyboardJs(combination: string | string[]): [isPressed: boolean, event?: KeyboardEvent] 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/useLatest.md: -------------------------------------------------------------------------------- 1 | # `useLatest` 2 | 3 | React state hook that returns the latest state as described in the [React hooks FAQ](https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function). 4 | 5 | This is mostly useful to get access to the latest value of some props or state inside an asynchronous callback, instead of that value at the time the callback was created from. 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import { useLatest } from 'react-use'; 11 | 12 | const Demo = () => { 13 | const [count, setCount] = React.useState(0); 14 | const latestCount = useLatest(count); 15 | 16 | function handleAlertClick() { 17 | setTimeout(() => { 18 | alert(`Latest count value: ${latestCount.current}`); 19 | }, 3000); 20 | } 21 | 22 | return ( 23 |
24 |

You clicked {count} times

25 | 26 | 27 |
28 | ); 29 | }; 30 | ``` 31 | 32 | ## Reference 33 | 34 | ```ts 35 | const latestState = useLatest = (state: T): MutableRefObject; 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/useLifecycles.md: -------------------------------------------------------------------------------- 1 | # `useLifecycles` 2 | 3 | React lifecycle hook that call `mount` and `unmount` callbacks, when 4 | component is mounted and un-mounted, respectively. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {useLifecycles} from 'react-use'; 11 | 12 | const Demo = () => { 13 | useLifecycles(() => console.log('MOUNTED'), () => console.log('UNMOUNTED')); 14 | return null; 15 | }; 16 | ``` 17 | 18 | 19 | ## Reference 20 | 21 | ```js 22 | useLifecycles(mount, unmount); 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/useLocation.md: -------------------------------------------------------------------------------- 1 | # `useLocation` 2 | 3 | React sensor hook that tracks brower's location. 4 | 5 | For Internet Explorer you need to [install a polyfill](https://github.com/streamich/react-use/issues/73). 6 | 7 | 8 | ## Usage 9 | 10 | ```jsx 11 | import {useLocation} from 'react-use'; 12 | 13 | const Demo = () => { 14 | const state = useLocation(); 15 | 16 | return ( 17 |
18 |       {JSON.stringify(state, null, 2)}
19 |     
20 | ); 21 | }; 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/useLogger.md: -------------------------------------------------------------------------------- 1 | # `useLogger` 2 | 3 | React lifecycle hook that console logs parameters as component transitions through lifecycles. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useLogger} from 'react-use'; 9 | 10 | const Demo = (props) => { 11 | useLogger('Demo', props); 12 | return null; 13 | }; 14 | ``` 15 | 16 | ## Example Output 17 | 18 | ``` 19 | Demo mounted {} 20 | Demo updated {} 21 | Demo unmounted 22 | ``` 23 | 24 | ## Reference 25 | 26 | ```js 27 | useLogger(componentName: string, ...rest); 28 | ``` 29 | 30 | - `componentName` — component name. 31 | - `...rest` — parameters to log. 32 | -------------------------------------------------------------------------------- /docs/useMap.md: -------------------------------------------------------------------------------- 1 | # `useMap` 2 | 3 | React state hook that tracks a value of an object. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useMap} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [map, {set, setAll, remove, reset}] = useMap({ 12 | hello: 'there', 13 | }); 14 | 15 | return ( 16 |
17 | 20 | 23 | 26 | 29 |
{JSON.stringify(map, null, 2)}
30 |
31 | ); 32 | }; 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/useMedia.md: -------------------------------------------------------------------------------- 1 | # `useMedia` 2 | 3 | React sensor hook that tracks state of a CSS media query. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useMedia} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const isWide = useMedia('(min-width: 480px)'); 12 | 13 | return ( 14 |
15 | Screen is wide: {isWide ? 'Yes' : 'No'} 16 |
17 | ); 18 | }; 19 | ``` 20 | 21 | ## Reference 22 | 23 | ```ts 24 | useMedia(query: string, defaultState: boolean = false): boolean; 25 | ``` 26 | 27 | The `defaultState` parameter is only used as a fallback for server side rendering. 28 | 29 | When server side rendering, it is important to set this parameter because without it the server's initial state will fallback to false, but the client will initialize to the result of the media query. When React hydrates the server render, it may not match the client's state. See the [React docs](https://reactjs.org/docs/react-dom.html#hydrate) for more on why this is can lead to costly bugs 🐛. 30 | -------------------------------------------------------------------------------- /docs/useMediaDevices.md: -------------------------------------------------------------------------------- 1 | # `useMediaDevices` 2 | 3 | React sensor hook that tracks connected hardware devices. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useMediaDevices} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const state = useMediaDevices(); 13 | 14 | return ( 15 |
16 |       {JSON.stringify(state, null, 2)}
17 |     
18 | ); 19 | }; 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/useMethods.md: -------------------------------------------------------------------------------- 1 | # `useMethods` 2 | 3 | React hook that simplifies the `useReducer` implementation. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import { useMethods } from 'react-use'; 9 | 10 | const initialState = { 11 | count: 0, 12 | }; 13 | 14 | function createMethods(state) { 15 | return { 16 | reset() { 17 | return initialState; 18 | }, 19 | increment() { 20 | return { ...state, count: state.count + 1 }; 21 | }, 22 | decrement() { 23 | return { ...state, count: state.count - 1 }; 24 | }, 25 | }; 26 | } 27 | 28 | const Demo = () => { 29 | const [state, methods] = useMethods(createMethods, initialState); 30 | 31 | return ( 32 | <> 33 |

Count: {state.count}

34 | 35 | 36 | 37 | ); 38 | }; 39 | ``` 40 | 41 | ## Reference 42 | 43 | ```js 44 | const [state, methods] = useMethods(createMethods, initialState); 45 | ``` 46 | 47 | - `createMethods` — function that takes current state and return an object containing methods that return updated state. 48 | - `initialState` — initial value of the state. 49 | -------------------------------------------------------------------------------- /docs/useMotion.md: -------------------------------------------------------------------------------- 1 | # `useMotion` 2 | 3 | React sensor hook that uses device's acceleration sensor to track its motions. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useMotion} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const state = useMotion(); 13 | 14 | return ( 15 |
16 |       {JSON.stringify(state, null, 2)}
17 |     
18 | ); 19 | }; 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/useMount.md: -------------------------------------------------------------------------------- 1 | # `useMount` 2 | 3 | React lifecycle hook that calls a function after the component is mounted. Use `useLifecycles` if you need both a mount and unmount function. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useMount} from 'react-use'; 9 | 10 | const Demo = () => { 11 | useMount(() => alert('MOUNTED')); 12 | return null; 13 | }; 14 | ``` 15 | 16 | ## Reference 17 | 18 | ```ts 19 | useMount(fn: () => void); 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/useMountedState.md: -------------------------------------------------------------------------------- 1 | # `useMountedState` 2 | 3 | > **NOTE!:** despite having `State` in its name **_this hook does not cause component re-render_**. 4 | > This component designed to be used to avoid state updates on unmounted components. 5 | 6 | Lifecycle hook providing ability to check component's mount state. 7 | Returns a function that will return `true` if component mounted and `false` otherwise. 8 | 9 | ## Usage 10 | 11 | ```jsx 12 | import * as React from 'react'; 13 | import {useMountedState} from 'react-use'; 14 | 15 | const Demo = () => { 16 | const isMounted = useMountedState(); 17 | 18 | React.useEffect(() => { 19 | setTimeout(() => { 20 | if (isMounted()) { 21 | // ... 22 | } else { 23 | // ... 24 | } 25 | }, 1000); 26 | }); 27 | }; 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/useMouse.md: -------------------------------------------------------------------------------- 1 | # `useMouse` and `useMouseHovered` 2 | 3 | React sensor hooks that re-render on mouse position changes. `useMouse` simply tracks 4 | mouse position; `useMouseHovered` allows you to specify extra options: 5 | 6 | - `bound` — to bind mouse coordinates within the element 7 | - `whenHovered` — whether to attach `mousemove` event handler only when user hovers over the element 8 | 9 | ## Usage 10 | 11 | ```jsx 12 | import {useMouse} from 'react-use'; 13 | 14 | const Demo = () => { 15 | const ref = React.useRef(null); 16 | const {docX, docY, posX, posY, elX, elY, elW, elH} = useMouse(ref); 17 | 18 | return ( 19 |
20 |
Mouse position in document - x:{docX} y:{docY}
21 |
Mouse position in element - x:{elX} y:{elY}
22 |
Element position- x:{posX} y:{posY}
23 |
Element dimensions - {elW}x{elH}
24 |
25 | ); 26 | }; 27 | ``` 28 | 29 | ## Reference 30 | 31 | ```ts 32 | useMouse(ref); 33 | useMouseHovered(ref, {bound: false, whenHovered: false}); 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/useMouseWheel.md: -------------------------------------------------------------------------------- 1 | # `useMouseWheel` 2 | React Hook to get deltaY of mouse scrolled in window. 3 | 4 | ## Usage 5 | 6 | ```jsx 7 | import { useMouseWheel } from 'react-use'; 8 | 9 | const Demo = () => { 10 | const mouseWheel = useMouseWheel() 11 | return ( 12 | <> 13 |

delta Y Scrolled: {mouseWheel}

14 | 15 | ); 16 | }; 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/useObservable.md: -------------------------------------------------------------------------------- 1 | # `useObservable` 2 | 3 | React state hook that tracks the latest value of an `Observable`. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useObservable} from 'react-use'; 10 | 11 | const counter$ = new BehaviorSubject(0); 12 | const Demo = () => { 13 | const value = useObservable(counter$, 0); 14 | 15 | return ( 16 | 19 | ); 20 | }; 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/useOrientation.md: -------------------------------------------------------------------------------- 1 | # `useOrientation` 2 | 3 | React sensor hook that tracks screen orientation of user's device. 4 | 5 | Returns state in the following shape 6 | 7 | ```js 8 | { 9 | angle: 0, 10 | type: 'landscape-primary' 11 | } 12 | ``` 13 | 14 | 15 | ## Usage 16 | 17 | ```jsx 18 | import {useOrientation} from 'react-use'; 19 | 20 | const Demo = () => { 21 | const state = useOrientation(); 22 | 23 | return ( 24 |
25 |       {JSON.stringify(state, null, 2)}
26 |     
27 | ); 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/usePageLeave.md: -------------------------------------------------------------------------------- 1 | # `usePageLeave` 2 | 3 | React sensor hook that fires a callback when mouse leaves the page. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {usePageLeave} from 'react-use'; 9 | 10 | const Demo = () => { 11 | usePageLeave(() => console.log('Page left...')); 12 | 13 | return null; 14 | }; 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/usePermission.md: -------------------------------------------------------------------------------- 1 | # `usePermission` 2 | 3 | React side-effect hook to query permission status of browser APIs. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {usePermission} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const state = usePermission({ name: 'microphone' }); 12 | 13 | return ( 14 |
15 |       {JSON.stringify(state, null, 2)}
16 |     
17 | ); 18 | }; 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/usePinchZoom.md: -------------------------------------------------------------------------------- 1 | # `usePinchZoom` 2 | 3 | React sensor hook that tracks the changes in pointer touch events and detects value of pinch difference and tell if user is zooming in or out. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import { usePinchZoom } from "react-use"; 9 | 10 | const Demo = () => { 11 | const [scale, setState] = useState(1); 12 | const scaleRef = useRef(); 13 | const { zoomingState, pinchState } = usePinchZoom(scaleRef); 14 | 15 | useEffect(() => { 16 | if (zoomingState === "ZOOM_IN") { 17 | // perform zoom in scaling 18 | setState(scale + 0.1) 19 | } else if (zoomingState === "ZOOM_OUT") { 20 | // perform zoom out in scaling 21 | setState(scale - 0.1) 22 | } 23 | }, [zoomingState]); 24 | 25 | return ( 26 |
27 | 33 |
34 | ); 35 | }; 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/usePrevious.md: -------------------------------------------------------------------------------- 1 | # `usePrevious` 2 | 3 | React state hook that returns the previous state as described in the [React hooks FAQ](https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state). 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {usePrevious} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [count, setCount] = React.useState(0); 12 | const prevCount = usePrevious(count); 13 | 14 | return ( 15 |

16 | 17 | 18 |

19 | Now: {count}, before: {prevCount} 20 |

21 |

22 | ); 23 | }; 24 | ``` 25 | 26 | ## Reference 27 | 28 | ```ts 29 | const prevState = usePrevious = (state: T): T; 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/usePromise.md: -------------------------------------------------------------------------------- 1 | # `usePromise` 2 | 3 | React Lifecycle hook that returns a helper function for wrapping promises. 4 | Promises wrapped with this function will resolve only when component is mounted. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {usePromise} from 'react-use'; 11 | 12 | const Demo = ({promise}) => { 13 | const mounted = usePromise(); 14 | const [value, setValue] = useState(); 15 | 16 | useEffect(() => { 17 | (async () => { 18 | const value = await mounted(promise); 19 | // This line will not execute if component gets unmounted. 20 | setValue(value); 21 | })(); 22 | }); 23 | }; 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/useQueue.md: -------------------------------------------------------------------------------- 1 | # `useQueue` 2 | 3 | React state hook implements simple FIFO queue. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import { useQueue } from 'react-use'; 10 | 11 | const Demo = () => { 12 | const { add, remove, first, last, size } = useQueue(); 13 | 14 | return ( 15 |
16 |
    17 |
  • first: {first}
  • 18 |
  • last: {last}
  • 19 |
  • size: {size}
  • 20 |
21 | 22 | 23 |
24 | ); 25 | }; 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/useRaf.md: -------------------------------------------------------------------------------- 1 | # `useRaf` 2 | 3 | React animation hook that forces component to re-render on each `requestAnimationFrame`, 4 | returns percentage of time elapsed. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {useRaf} from 'react-use'; 11 | 12 | const Demo = () => { 13 | const elapsed = useRaf(5000, 1000); 14 | 15 | return ( 16 |
17 | Elapsed: {elapsed} 18 |
19 | ); 20 | }; 21 | ``` 22 | 23 | 24 | ## Reference 25 | 26 | ```ts 27 | useRaf(ms?: number, delay?: number): number; 28 | ``` 29 | 30 | - `ms` — milliseconds for how long to keep re-rendering component, defaults to `1e12`. 31 | - `delay` — delay in milliseconds after which to start re-rendering component, defaults to `0`. 32 | -------------------------------------------------------------------------------- /docs/useRafState.md: -------------------------------------------------------------------------------- 1 | # `useRafState` 2 | 3 | React state hook that only updates state in the callback of [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useRafState, useMount} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [state, setState] = useRafState({ 12 | width: 0, 13 | height: 0, 14 | }); 15 | 16 | useMount(() => { 17 | const onResize = () => { 18 | setState({ 19 | width: window.clientWidth, 20 | height: window.height, 21 | }); 22 | }; 23 | 24 | window.addEventListener('resize', onResize); 25 | 26 | return () => { 27 | window.removeEventListener('resize', onResize); 28 | }; 29 | }); 30 | 31 | return
{JSON.stringify(state, null, 2)}
; 32 | }; 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/useRendersCount.md: -------------------------------------------------------------------------------- 1 | # `useRendersCount` 2 | 3 | Tracks component's renders count including the first render. 4 | 5 | ## Usage 6 | 7 | ```typescript jsx 8 | import * as React from 'react'; 9 | import { useRendersCount } from "react-use"; 10 | 11 | const Demo = () => { 12 | const update = useUpdate(); 13 | const rendersCount = useRendersCount(); 14 | 15 | return ( 16 |
17 | Renders count: {rendersCount} 18 |
19 | 20 |
21 | ); 22 | }; 23 | ``` 24 | 25 | ## Reference 26 | 27 | ```typescript 28 | const rendersCount: number = useRendersCount(); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/useScroll.md: -------------------------------------------------------------------------------- 1 | # `useScroll` 2 | 3 | React sensor hook that re-renders when the scroll position in a DOM element changes. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useScroll} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const scrollRef = React.useRef(null); 12 | const {x, y} = useScroll(scrollRef); 13 | 14 | return ( 15 |
16 |
x: {x}
17 |
y: {y}
18 |
19 | ); 20 | }; 21 | ``` 22 | 23 | ## Reference 24 | 25 | ```ts 26 | useScroll(ref: RefObject); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/useScrollbarWidth.md: -------------------------------------------------------------------------------- 1 | # `useScrollbarWidth` 2 | 3 | Hook that will return current browser's scrollbar width. 4 | In case hook been called before DOM ready, it will return `undefined` and will cause re-render on first available RAF. 5 | > **_NOTE:_** it does not work (return 0) for mobile devices, because their scrollbar width can not be determined. 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | const Demo = () => { 11 | const sbw = useScrollbarWidth(); 12 | 13 | return ( 14 |
15 | {sbw === undefined ? `DOM is not ready yet, SBW detection delayed` : `Browser's scrollbar width is ${sbw}px`} 16 |
17 | ); 18 | }; 19 | ``` 20 | 21 | ## Reference 22 | 23 | ```typescript 24 | const sbw: number | undefined = useScrollbarWidth(); 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/useScrolling.md: -------------------------------------------------------------------------------- 1 | # `useScrolling` 2 | 3 | React sensor hook that keeps track of whether the user is scrolling or not. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import { useScrolling } from "react-use"; 9 | 10 | const Demo = () => { 11 | const scrollRef = React.useRef(null); 12 | const scrolling = useScrolling(scrollRef); 13 | 14 | return ( 15 |
16 | {
{scrolling ? "Scrolling" : "Not scrolling"}
} 17 |
18 | ); 19 | }; 20 | ``` 21 | 22 | ## Reference 23 | 24 | ```ts 25 | useScrolling(ref: RefObject); 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/useSearchParam.md: -------------------------------------------------------------------------------- 1 | # `useSearchParam` 2 | 3 | React sensor hook that tracks browser's location search param. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useSearchParam} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const edit = useSearchParam('edit'); 12 | 13 | return ( 14 |
15 |
edit: {edit || '🤷‍♂️'}
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | ); 27 | }; 28 | ``` 29 | 30 | ## Caveats/Gotchas 31 | 32 | 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. 33 | -------------------------------------------------------------------------------- /docs/useSessionStorage.md: -------------------------------------------------------------------------------- 1 | # `useSessionStorage` 2 | 3 | React side-effect hook that manages a single `sessionStorage` key. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useSessionStorage} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const [value, setValue] = useSessionStorage('my-key', 'foo'); 13 | 14 | return ( 15 |
16 |
Value: {value}
17 | 18 | 19 |
20 | ); 21 | }; 22 | ``` 23 | 24 | 25 | ## Reference 26 | 27 | ```js 28 | useSessionStorage(key); 29 | useSessionStorage(key, initialValue); 30 | useSessionStorage(key, initialValue, raw); 31 | ``` 32 | 33 | - `key` — `sessionStorage` key to manage. 34 | - `initialValue` — initial value to set, if value in `sessionStorage` is empty. 35 | - `raw` — boolean, if set to `true`, hook will not attempt to JSON serialize stored values. 36 | -------------------------------------------------------------------------------- /docs/useSet.md: -------------------------------------------------------------------------------- 1 | # `useSet` 2 | 3 | React state hook that tracks a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). 4 | 5 | ## Usage 6 | 7 | What is the difference between the "clear()" method and the "reset()" method? 8 | 9 | The "reset()" method returns the "Set" to the initial value passed during "useSet 10 | The "clear()" method completely empties the "Set". 11 | 12 | ```jsx 13 | import {useSet} from 'react-use'; 14 | 15 | const Demo = () => { 16 | const [set, { add, has, remove, toggle, reset, clear }] = useSet(new Set(['hello'])); 17 | 18 | return ( 19 |
20 | 21 | 22 | 23 | 26 | 27 |
{JSON.stringify(Array.from(set), null, 2)}
28 |
29 | ); 30 | }; 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/useSetState.md: -------------------------------------------------------------------------------- 1 | # `useSetState` 2 | 3 | React state hook that creates `setState` method which works similar to how 4 | `this.setState` works in class components—it merges object changes into 5 | current state. 6 | 7 | 8 | ## Usage 9 | 10 | ```jsx 11 | import {useSetState} from 'react-use'; 12 | 13 | const Demo = () => { 14 | const [state, setState] = useSetState({}); 15 | 16 | return ( 17 |
18 |
{JSON.stringify(state, null, 2)}
19 | 20 | 21 | 30 |
31 | ); 32 | }; 33 | ``` 34 | 35 | ## Reference 36 | 37 | ```js 38 | const [state, setState] = useSetState({cnt: 0}); 39 | 40 | setState({cnt: state.cnt + 1}); 41 | setState((prevState) => ({ 42 | cnt: prevState + 1, 43 | })); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/useShallowCompareEffect.md: -------------------------------------------------------------------------------- 1 | # `useShallowCompareEffect` 2 | 3 | A modified useEffect hook that is using shallow comparison on each of its dependencies instead of reference equality. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useCounter, useShallowCompareEffect} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [count, {inc: inc}] = useCounter(0); 12 | const options = { step: 2 }; 13 | 14 | useShallowCompareEffect(() => { 15 | inc(options.step) 16 | }, [options]); 17 | 18 | return ( 19 |
20 |

useShallowCompareEffect: {count}

21 |
22 | ); 23 | }; 24 | ``` 25 | 26 | ## Reference 27 | 28 | ```ts 29 | useShallowCompareEffect(effect: () => void | (() => void | undefined), deps: any[]); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/useSize.md: -------------------------------------------------------------------------------- 1 | # `useSize` 2 | 3 | React sensor hook that tracks size of an HTML element. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useSize} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [sized, {width, height}] = useSize( 12 | ({width}) =>
Size me up! ({width}px)
, 13 | { width: 100, height: 100 } 14 | ); 15 | 16 | return ( 17 |
18 | {sized} 19 |
width: {width}
20 |
height: {height}
21 |
22 | ); 23 | }; 24 | ``` 25 | 26 | ## Reference 27 | 28 | ```js 29 | useSize(element, initialSize); 30 | ``` 31 | 32 | - `element` — sized element. 33 | - `initialSize` — initial size containing a `width` and `height` key. 34 | 35 | ## Related hooks 36 | 37 | - [useMeasure](./useMeasure.md) 38 | -------------------------------------------------------------------------------- /docs/useSlider.md: -------------------------------------------------------------------------------- 1 | # `useSlider` 2 | 3 | React UI hook that provides slide behavior over any HTML element. Supports both mouse and touch events. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useSlider} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const ref = React.useRef(null); 12 | const {isSliding, value, pos, length} = useSlider(ref); 13 | 14 | return ( 15 |
16 |
17 |

18 | {Math.round(value * 100)}% 19 |

20 |
🎚
21 |
22 |
23 | ); 24 | }; 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/useSpeech.md: -------------------------------------------------------------------------------- 1 | # `useSpeech` 2 | 3 | React UI hook that synthesizes human voice that speaks a given string. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useSpeech} from 'react-use'; 10 | 11 | const voices = window.speechSynthesis.getVoices(); 12 | 13 | const Demo = () => { 14 | const state = useSpeech('Hello world!', { rate: 0.8, pitch: 0.5, voice: voices[0] }); 15 | 16 | return ( 17 |
18 |       {JSON.stringify(state, null, 2)}
19 |     
20 | ); 21 | }; 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/useSpring.md: -------------------------------------------------------------------------------- 1 | # `useSpring` 2 | 3 | React animation hook that updates a single numeric value over time according 4 | to spring dynamics. 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import useSpring from 'react-use/lib/useSpring'; 10 | 11 | const Demo = () => { 12 | const [target, setTarget] = useState(50); 13 | const value = useSpring(target); 14 | 15 | return ( 16 |
17 | {value} 18 |
19 | 20 | 21 |
22 | ); 23 | }; 24 | ``` 25 | 26 | Note: Because of dependency on `rebound` you have to import this hook directly like shown above. 27 | 28 | ## Requirements 29 | 30 | Install [`rebound`](https://github.com/facebook/rebound-js) peer dependency: 31 | 32 | ```bash 33 | npm add rebound 34 | # or 35 | yarn add rebound 36 | ``` 37 | 38 | ## Reference 39 | 40 | ```js 41 | const currentValue = useSpring(targetValue); 42 | const currentValue = useSpring(targetValue, tension, friction); 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/useStartTyping.md: -------------------------------------------------------------------------------- 1 | # `useStartTyping` 2 | 3 | React sensor hook that fires a callback when user starts typing. Can be used 4 | to focus default input field on the page. 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useStartTyping} from 'react-use'; 10 | 11 | const Demo = () => { 12 | useStartTyping(() => alert('Started typing...')); 13 | 14 | return null; 15 | }; 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/useThrottle.md: -------------------------------------------------------------------------------- 1 | # `useThrottle` and `useThrottleFn` 2 | 3 | React hooks that throttle. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import React, { useState } from 'react'; 9 | import { useThrottle, useThrottleFn } from 'react-use'; 10 | 11 | const Demo = ({value}) => { 12 | const throttledValue = useThrottle(value); 13 | // const throttledValue = useThrottleFn(value => value, 200, [value]); 14 | 15 | return ( 16 | <> 17 |
Value: {value}
18 |
Throttled value: {throttledValue}
19 | 20 | ); 21 | }; 22 | ``` 23 | 24 | ## Reference 25 | 26 | ```ts 27 | useThrottle(value, ms?: number); 28 | useThrottleFn(fn, ms, args); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/useTitle.md: -------------------------------------------------------------------------------- 1 | # `useTitle` 2 | 3 | React side-effect hook that sets title of the page. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useTitle} from 'react-use'; 10 | 11 | const Demo = () => { 12 | useTitle('Hello world!'); 13 | 14 | return null; 15 | }; 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/useToggle.md: -------------------------------------------------------------------------------- 1 | # `useToggle` 2 | 3 | React state hook that tracks value of a boolean. 4 | 5 | `useBoolean` is an alias for `useToggle`. 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {useToggle} from 'react-use'; 11 | 12 | const Demo = () => { 13 | const [on, toggle] = useToggle(true); 14 | 15 | return ( 16 |
17 |
{on ? 'ON' : 'OFF'}
18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/useTween.md: -------------------------------------------------------------------------------- 1 | # `useTween` 2 | 3 | React animation hook that tweens a number between 0 and 1. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useTween} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const t = useTween(); 13 | 14 | return ( 15 |
16 | Tween: {t} 17 |
18 | ); 19 | }; 20 | ``` 21 | 22 | ## Reference 23 | 24 | ```ts 25 | useTween(easing?: string, ms?: number, delay?: number): number 26 | ``` 27 | 28 | Returns a number that begins with 0 and ends with 1 when animation ends. 29 | 30 | - `easing` — one of the valid [easing names](https://github.com/streamich/ts-easing/blob/master/src/index.ts), defaults to `inCirc`. 31 | - `ms` — milliseconds for how long to keep re-rendering component, defaults to `200`. 32 | - `delay` — delay in milliseconds after which to start re-rendering component, defaults to `0`. 33 | -------------------------------------------------------------------------------- /docs/useUnmount.md: -------------------------------------------------------------------------------- 1 | # `useUnmount` 2 | 3 | React lifecycle hook that calls a function when the component will unmount. Use `useLifecycles` if you need both a mount and unmount function. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useUnmount} from 'react-use'; 9 | 10 | const Demo = () => { 11 | useUnmount(() => alert('UNMOUNTED')); 12 | return null; 13 | }; 14 | ``` 15 | 16 | ## Reference 17 | 18 | ```ts 19 | useUnmount(fn: () => void | undefined); 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/useUnmountPromise.md: -------------------------------------------------------------------------------- 1 | # `useUnmountPromise` 2 | 3 | A life-cycle hook that provides a higher order promise that does not resolve if component un-mounts. 4 | 5 | 6 | ## Usage 7 | 8 | ```ts 9 | import useUnmountPromise from 'react-use/lib/useUnmountPromise'; 10 | 11 | const Demo = () => { 12 | const mounted = useUnmountPromise(); 13 | useEffect(async () => { 14 | await mounted(someFunction()); // Will not resolve if component un-mounts. 15 | }); 16 | }; 17 | ``` 18 | 19 | 20 | ## Reference 21 | 22 | ```ts 23 | const mounted = useUnmountPromise(); 24 | 25 | mounted(promise); 26 | mounted(promise, onError); 27 | ``` 28 | 29 | - `onError` — if promise rejects after the component is unmounted, `onError` 30 | callback is called with the error. 31 | -------------------------------------------------------------------------------- /docs/useUpdate.md: -------------------------------------------------------------------------------- 1 | # `useUpdate` 2 | 3 | React utility hook that returns a function that forces component 4 | to re-render when called. 5 | 6 | 7 | ## Usage 8 | 9 | ```jsx 10 | import {useUpdate} from 'react-use'; 11 | 12 | const Demo = () => { 13 | const update = useUpdate(); 14 | return ( 15 | <> 16 |
Time: {Date.now()}
17 | 18 | 19 | ); 20 | }; 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/useUpdateEffect.md: -------------------------------------------------------------------------------- 1 | # `useUpdateEffect` 2 | 3 | React effect hook that ignores the first invocation (e.g. on mount). The signature is exactly the same as the `useEffect` hook. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import React from 'react' 10 | import {useUpdateEffect} from 'react-use'; 11 | 12 | const Demo = () => { 13 | const [count, setCount] = React.useState(0); 14 | 15 | React.useEffect(() => { 16 | const interval = setInterval(() => { 17 | setCount(count => count + 1) 18 | }, 1000) 19 | 20 | return () => { 21 | clearInterval(interval) 22 | } 23 | }, []) 24 | 25 | useUpdateEffect(() => { 26 | console.log('count', count) // will only show 1 and beyond 27 | 28 | return () => { // *OPTIONAL* 29 | // do something on unmount 30 | } 31 | }) // you can include deps array if necessary 32 | 33 | return
Count: {count}
34 | }; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/useUpsert.md: -------------------------------------------------------------------------------- 1 | # `useUpsert` 2 | 3 | > DEPRECATED! 4 | > Use `useList` hook's upsert action instead 5 | 6 | Superset of [`useList`](./useList.md). Provides an additional method to upsert (update or insert) an element into the list. 7 | 8 | ## Usage 9 | 10 | ```jsx 11 | import {useUpsert} from 'react-use'; 12 | 13 | const Demo = () => { 14 | const comparisonFunction = (a: DemoType, b: DemoType) => { 15 | return a.id === b.id; 16 | }; 17 | const [list, { set, upsert, remove }] = useUpsert(comparisonFunction, initialItems); 18 | 19 | return ( 20 |
21 | {list.map((item: DemoType, index: number) => ( 22 |
23 | upsert({ ...item, text: e.target.value })} /> 24 | 25 |
26 | ))} 27 | 28 | 29 |
30 | ); 31 | }; 32 | ``` 33 | 34 | ## Related hooks 35 | 36 | - [useList](./useList.md) 37 | -------------------------------------------------------------------------------- /docs/useVibrate.md: -------------------------------------------------------------------------------- 1 | # `useVibrate` 2 | 3 | React UI hook to provide physical feedback with device vibration hardware using the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API). 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useVibrate} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const [vibrating, toggleVibrating] = useToggle(false); 12 | 13 | useVibrate(vibrating, [300, 100, 200, 100, 1000, 300], false); 14 | 15 | return ( 16 |
17 | 18 |
19 | ); 20 | }; 21 | ``` 22 | 23 | ## Reference 24 | 25 | ```ts 26 | useVibrate( 27 | enabled: boolean = true, 28 | pattern: number | number[] = [1000, 1000], 29 | loop: boolean = true 30 | ): void; 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/useWindowScroll.md: -------------------------------------------------------------------------------- 1 | # `useWindowScroll` 2 | 3 | React sensor hook that re-renders on window scroll. 4 | 5 | ## Usage 6 | 7 | ```jsx 8 | import {useWindowScroll} from 'react-use'; 9 | 10 | const Demo = () => { 11 | const {x, y} = useWindowScroll(); 12 | 13 | return ( 14 |
15 |
x: {x}
16 |
y: {y}
17 |
18 | ); 19 | }; 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/useWindowSize.md: -------------------------------------------------------------------------------- 1 | # `useWindowSize` 2 | 3 | React sensor hook that tracks dimensions of the browser window. 4 | 5 | 6 | ## Usage 7 | 8 | ```jsx 9 | import {useWindowSize} from 'react-use'; 10 | 11 | const Demo = () => { 12 | const {width, height} = useWindowSize(); 13 | 14 | return ( 15 |
16 |
width: {width}
17 |
height: {height}
18 |
19 | ); 20 | }; 21 | ``` 22 | 23 | ## Reference 24 | 25 | ```js 26 | useWindowSize(options); 27 | ``` 28 | 29 | - `initialWidth` — Initial width value for non-browser environments. 30 | - `initialHeight` — Initial height value for non-browser environments. 31 | - `onChange` — Callback function triggered when the window size changes. 32 | 33 | ## Related hooks 34 | 35 | - [useSize](./useSize.md) 36 | - [useMeasure](./useMeasure.md) -------------------------------------------------------------------------------- /jest.config.base.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | 3 | export const baseJestConfig: Config.InitialOptions = { 4 | 'preset': 'ts-jest', 5 | 'clearMocks': true, 6 | 'coverageDirectory': 'coverage', 7 | 'testMatch': [ 8 | '/tests/**/*.test.(ts|tsx)' 9 | ], 10 | 'setupFiles': [ 11 | '/tests/setupTests.ts' 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /jest.config.node.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | import { baseJestConfig } from './jest.config.base'; 3 | 4 | const config: Config.InitialOptions = { 5 | ...baseJestConfig, 6 | testEnvironment: 'node', // browser-like 7 | } 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | import { baseJestConfig } from './jest.config.base'; 3 | 4 | const config: Config.InitialOptions = { 5 | ...baseJestConfig, 6 | testEnvironment: 'jsdom', // browser-like 7 | } 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "automerge": true, 6 | "pinVersions": false, 7 | "ignoreUnstable": true, 8 | "major": { 9 | "automerge": false 10 | }, 11 | "devDependencies": { 12 | "automerge": true, 13 | "pinVersions": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/component/UseKey.tsx: -------------------------------------------------------------------------------- 1 | import useKey from '../useKey'; 2 | import createRenderProp from '../factory/createRenderProp'; 3 | 4 | const UseKey = createRenderProp(useKey, ({ filter, fn, deps, ...rest }) => [ 5 | filter, 6 | fn, 7 | rest, 8 | deps, 9 | ]); 10 | 11 | export default UseKey; 12 | -------------------------------------------------------------------------------- /src/factory/createBreakpoint.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useState } from 'react'; 2 | import { isBrowser, off, on } from '../misc/util'; 3 | 4 | const createBreakpoint = 5 | (breakpoints: { [name: string]: number } = { laptopL: 1440, laptop: 1024, tablet: 768 }) => 6 | () => { 7 | const [screen, setScreen] = useState(isBrowser ? window.innerWidth : 0); 8 | 9 | useEffect(() => { 10 | const setSideScreen = (): void => { 11 | setScreen(window.innerWidth); 12 | }; 13 | setSideScreen(); 14 | on(window, 'resize', setSideScreen); 15 | return () => { 16 | off(window, 'resize', setSideScreen); 17 | }; 18 | }); 19 | const sortedBreakpoints = useMemo( 20 | () => Object.entries(breakpoints).sort((a, b) => (a[1] >= b[1] ? 1 : -1)), 21 | [breakpoints] 22 | ); 23 | const result = sortedBreakpoints.reduce((acc, [name, width]) => { 24 | if (screen >= width) { 25 | return name; 26 | } else { 27 | return acc; 28 | } 29 | }, sortedBreakpoints[0][0]); 30 | return result; 31 | }; 32 | 33 | export default createBreakpoint; 34 | -------------------------------------------------------------------------------- /src/factory/createMemo.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | 3 | const createMemo = 4 | any>(fn: T) => 5 | (...args: Parameters) => 6 | useMemo>(() => fn(...args), args); 7 | 8 | export default createMemo; 9 | -------------------------------------------------------------------------------- /src/factory/createRenderProp.ts: -------------------------------------------------------------------------------- 1 | const defaultMapPropsToArgs = (props) => [props]; 2 | 3 | export default function createRenderProp(hook, mapPropsToArgs = defaultMapPropsToArgs) { 4 | return function RenderProp(props) { 5 | const state = hook(...mapPropsToArgs(props)); 6 | const { children, render = children } = props; 7 | return render ? render(state) || null : null; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/factory/createRouter.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface RouterProviderProps { 4 | route: string; 5 | fullRoute?: string; 6 | parent?: any; 7 | } 8 | 9 | const createRouter = () => { 10 | const context = React.createContext({ 11 | route: '', 12 | }); 13 | 14 | // not sure if this supposed to be unused, ignoring ts error for now 15 | // @ts-ignore 16 | const Router: React.SFC = (props) => { 17 | const { route, fullRoute, parent, children } = props; 18 | 19 | if (process.env.NODE_ENV !== 'production') { 20 | if (typeof route !== 'string') { 21 | throw new TypeError('Router route must be a string.'); 22 | } 23 | } 24 | 25 | return React.createElement(context.Provider as any, { 26 | value: { 27 | fullRoute: fullRoute || route, 28 | route, 29 | parent, 30 | }, 31 | children, 32 | }); 33 | }; 34 | }; 35 | 36 | export default createRouter; 37 | -------------------------------------------------------------------------------- /src/factory/createStateContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, createElement, useContext, useState } from 'react'; 2 | 3 | const createStateContext = (defaultInitialValue: T) => { 4 | const context = 5 | createContext<[T, React.Dispatch>] | undefined>(undefined); 6 | const providerFactory = (props, children) => createElement(context.Provider, props, children); 7 | 8 | const StateProvider = ({ 9 | children, 10 | initialValue, 11 | }: { 12 | children?: React.ReactNode; 13 | initialValue?: T; 14 | }) => { 15 | const state = useState(initialValue !== undefined ? initialValue : defaultInitialValue); 16 | return providerFactory({ value: state }, children); 17 | }; 18 | 19 | const useStateContext = () => { 20 | const state = useContext(context); 21 | if (state == null) { 22 | throw new Error(`useStateContext must be used inside a StateProvider.`); 23 | } 24 | return state; 25 | }; 26 | 27 | return [useStateContext, StateProvider, context] as const; 28 | }; 29 | 30 | export default createStateContext; 31 | -------------------------------------------------------------------------------- /src/misc/hookState.ts: -------------------------------------------------------------------------------- 1 | export type IHookStateInitialSetter = () => S; 2 | export type IHookStateInitAction = S | IHookStateInitialSetter; 3 | 4 | export type IHookStateSetter = ((prevState: S) => S) | (() => S); 5 | export type IHookStateSetAction = S | IHookStateSetter; 6 | 7 | export type IHookStateResolvable = S | IHookStateInitialSetter | IHookStateSetter; 8 | 9 | export function resolveHookState(nextState: IHookStateInitAction): S; 10 | export function resolveHookState( 11 | nextState: IHookStateSetAction, 12 | currentState?: C 13 | ): S; 14 | export function resolveHookState( 15 | nextState: IHookStateResolvable, 16 | currentState?: C 17 | ): S; 18 | export function resolveHookState( 19 | nextState: IHookStateResolvable, 20 | currentState?: C 21 | ): S { 22 | if (typeof nextState === 'function') { 23 | return nextState.length ? (nextState as Function)(currentState) : (nextState as Function)(); 24 | } 25 | 26 | return nextState; 27 | } 28 | -------------------------------------------------------------------------------- /src/misc/isDeepEqual.ts: -------------------------------------------------------------------------------- 1 | import isDeepEqualReact from 'fast-deep-equal/react'; 2 | 3 | export default isDeepEqualReact; 4 | -------------------------------------------------------------------------------- /src/misc/parseTimeRanges.ts: -------------------------------------------------------------------------------- 1 | export default function parseTimeRanges(ranges) { 2 | const result: { start: number; end: number }[] = []; 3 | 4 | for (let i = 0; i < ranges.length; i++) { 5 | result.push({ 6 | start: ranges.start(i), 7 | end: ranges.end(i), 8 | }); 9 | } 10 | 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /src/misc/types.ts: -------------------------------------------------------------------------------- 1 | export type PromiseType

> = P extends Promise ? T : never; 2 | 3 | export type FunctionReturningPromise = (...args: any[]) => Promise; 4 | -------------------------------------------------------------------------------- /src/misc/util.ts: -------------------------------------------------------------------------------- 1 | export const noop = () => {}; 2 | 3 | export function on( 4 | obj: T | null, 5 | ...args: Parameters | [string, Function | null, ...any] 6 | ): void { 7 | if (obj && obj.addEventListener) { 8 | obj.addEventListener(...(args as Parameters)); 9 | } 10 | } 11 | 12 | export function off( 13 | obj: T | null, 14 | ...args: Parameters | [string, Function | null, ...any] 15 | ): void { 16 | if (obj && obj.removeEventListener) { 17 | obj.removeEventListener(...(args as Parameters)); 18 | } 19 | } 20 | 21 | export const isBrowser = typeof window !== 'undefined'; 22 | 23 | export const isNavigator = typeof navigator !== 'undefined'; 24 | -------------------------------------------------------------------------------- /src/useAsync.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, useEffect } from 'react'; 2 | import useAsyncFn from './useAsyncFn'; 3 | import { FunctionReturningPromise } from './misc/types'; 4 | 5 | export { AsyncState, AsyncFnReturn } from './useAsyncFn'; 6 | 7 | export default function useAsync( 8 | fn: T, 9 | deps: DependencyList = [] 10 | ) { 11 | const [state, callback] = useAsyncFn(fn, deps, { 12 | loading: true, 13 | }); 14 | 15 | useEffect(() => { 16 | callback(); 17 | }, [callback]); 18 | 19 | return state; 20 | } 21 | -------------------------------------------------------------------------------- /src/useAsyncRetry.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, useCallback, useState } from 'react'; 2 | import useAsync, { AsyncState } from './useAsync'; 3 | 4 | export type AsyncStateRetry = AsyncState & { 5 | retry(): void; 6 | }; 7 | 8 | const useAsyncRetry = (fn: () => Promise, deps: DependencyList = []) => { 9 | const [attempt, setAttempt] = useState(0); 10 | const state = useAsync(fn, [...deps, attempt]); 11 | 12 | const stateLoading = state.loading; 13 | const retry = useCallback(() => { 14 | if (stateLoading) { 15 | if (process.env.NODE_ENV === 'development') { 16 | console.log( 17 | 'You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.' 18 | ); 19 | } 20 | 21 | return; 22 | } 23 | 24 | setAttempt((currentAttempt) => currentAttempt + 1); 25 | }, [...deps, stateLoading]); 26 | 27 | return { ...state, retry }; 28 | }; 29 | 30 | export default useAsyncRetry; 31 | -------------------------------------------------------------------------------- /src/useAudio.ts: -------------------------------------------------------------------------------- 1 | import createHTMLMediaHook from './factory/createHTMLMediaHook'; 2 | 3 | const useAudio = createHTMLMediaHook('audio'); 4 | export default useAudio; 5 | -------------------------------------------------------------------------------- /src/useBeforeUnload.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from 'react'; 2 | import { off, on } from './misc/util'; 3 | 4 | const useBeforeUnload = (enabled: boolean | (() => boolean) = true, message?: string) => { 5 | const handler = useCallback( 6 | (event: BeforeUnloadEvent) => { 7 | const finalEnabled = typeof enabled === 'function' ? enabled() : true; 8 | 9 | if (!finalEnabled) { 10 | return; 11 | } 12 | 13 | event.preventDefault(); 14 | 15 | if (message) { 16 | event.returnValue = message; 17 | } 18 | 19 | return message; 20 | }, 21 | [enabled, message] 22 | ); 23 | 24 | useEffect(() => { 25 | if (!enabled) { 26 | return; 27 | } 28 | 29 | on(window, 'beforeunload', handler); 30 | 31 | return () => off(window, 'beforeunload', handler); 32 | }, [enabled, handler]); 33 | }; 34 | 35 | export default useBeforeUnload; 36 | -------------------------------------------------------------------------------- /src/useBoolean.ts: -------------------------------------------------------------------------------- 1 | import useBoolean from './useToggle'; 2 | 3 | export default useBoolean; 4 | -------------------------------------------------------------------------------- /src/useClickAway.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useRef } from 'react'; 2 | import { off, on } from './misc/util'; 3 | 4 | const defaultEvents = ['mousedown', 'touchstart']; 5 | 6 | const useClickAway = ( 7 | ref: RefObject, 8 | onClickAway: (event: E) => void, 9 | events: string[] = defaultEvents 10 | ) => { 11 | const savedCallback = useRef(onClickAway); 12 | useEffect(() => { 13 | savedCallback.current = onClickAway; 14 | }, [onClickAway]); 15 | useEffect(() => { 16 | const handler = (event) => { 17 | const { current: el } = ref; 18 | el && !el.contains(event.target) && savedCallback.current(event); 19 | }; 20 | for (const eventName of events) { 21 | on(document, eventName, handler); 22 | } 23 | return () => { 24 | for (const eventName of events) { 25 | off(document, eventName, handler); 26 | } 27 | }; 28 | }, [events, ref]); 29 | }; 30 | 31 | export default useClickAway; 32 | -------------------------------------------------------------------------------- /src/useCookie.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import Cookies from 'js-cookie'; 3 | 4 | const useCookie = ( 5 | cookieName: string 6 | ): [string | null, (newValue: string, options?: Cookies.CookieAttributes) => void, () => void] => { 7 | const [value, setValue] = useState(() => Cookies.get(cookieName) || null); 8 | 9 | const updateCookie = useCallback( 10 | (newValue: string, options?: Cookies.CookieAttributes) => { 11 | Cookies.set(cookieName, newValue, options); 12 | setValue(newValue); 13 | }, 14 | [cookieName] 15 | ); 16 | 17 | const deleteCookie = useCallback(() => { 18 | Cookies.remove(cookieName); 19 | setValue(null); 20 | }, [cookieName]); 21 | 22 | return [value, updateCookie, deleteCookie]; 23 | }; 24 | 25 | export default useCookie; 26 | -------------------------------------------------------------------------------- /src/useCss.ts: -------------------------------------------------------------------------------- 1 | import { create, NanoRenderer } from 'nano-css'; 2 | import { addon as addonCSSOM, CSSOMAddon } from 'nano-css/addon/cssom'; 3 | import { addon as addonVCSSOM, VCSSOMAddon } from 'nano-css/addon/vcssom'; 4 | import { cssToTree } from 'nano-css/addon/vcssom/cssToTree'; 5 | import { useMemo } from 'react'; 6 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; 7 | 8 | type Nano = NanoRenderer & CSSOMAddon & VCSSOMAddon; 9 | const nano = create() as Nano; 10 | addonCSSOM(nano); 11 | addonVCSSOM(nano); 12 | 13 | let counter = 0; 14 | 15 | const useCss = (css: object): string => { 16 | const className = useMemo(() => 'react-use-css-' + (counter++).toString(36), []); 17 | const sheet = useMemo(() => new nano.VSheet(), []); 18 | 19 | useIsomorphicLayoutEffect(() => { 20 | const tree = {}; 21 | cssToTree(tree, css, '.' + className, ''); 22 | sheet.diff(tree); 23 | 24 | return () => { 25 | sheet.diff({}); 26 | }; 27 | }); 28 | 29 | return className; 30 | }; 31 | 32 | export default useCss; 33 | -------------------------------------------------------------------------------- /src/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, useEffect } from 'react'; 2 | import useTimeoutFn from './useTimeoutFn'; 3 | 4 | export type UseDebounceReturn = [() => boolean | null, () => void]; 5 | 6 | export default function useDebounce( 7 | fn: Function, 8 | ms: number = 0, 9 | deps: DependencyList = [] 10 | ): UseDebounceReturn { 11 | const [isReady, cancel, reset] = useTimeoutFn(fn, ms); 12 | 13 | useEffect(reset, deps); 14 | 15 | return [isReady, cancel]; 16 | } 17 | -------------------------------------------------------------------------------- /src/useDeepCompareEffect.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, EffectCallback } from 'react'; 2 | import useCustomCompareEffect from './useCustomCompareEffect'; 3 | import isDeepEqual from './misc/isDeepEqual'; 4 | 5 | const isPrimitive = (val: any) => val !== Object(val); 6 | 7 | const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => { 8 | if (process.env.NODE_ENV !== 'production') { 9 | if (!(deps instanceof Array) || !deps.length) { 10 | console.warn( 11 | '`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.' 12 | ); 13 | } 14 | 15 | if (deps.every(isPrimitive)) { 16 | console.warn( 17 | '`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.' 18 | ); 19 | } 20 | } 21 | 22 | useCustomCompareEffect(effect, deps, isDeepEqual); 23 | }; 24 | 25 | export default useDeepCompareEffect; 26 | -------------------------------------------------------------------------------- /src/useDefault.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const useDefault = ( 4 | defaultValue: TStateType, 5 | initialValue: TStateType | (() => TStateType) 6 | ) => { 7 | const [value, setValue] = useState(initialValue); 8 | 9 | if (value === undefined || value === null) { 10 | return [defaultValue, setValue] as const; 11 | } 12 | 13 | return [value, setValue] as const; 14 | }; 15 | 16 | export default useDefault; 17 | -------------------------------------------------------------------------------- /src/useEffectOnce.ts: -------------------------------------------------------------------------------- 1 | import { EffectCallback, useEffect } from 'react'; 2 | 3 | const useEffectOnce = (effect: EffectCallback) => { 4 | useEffect(effect, []); 5 | }; 6 | 7 | export default useEffectOnce; 8 | -------------------------------------------------------------------------------- /src/useEnsuredForwardedRef.ts: -------------------------------------------------------------------------------- 1 | import { 2 | forwardRef, 3 | ForwardRefExoticComponent, 4 | MutableRefObject, 5 | PropsWithChildren, 6 | PropsWithoutRef, 7 | RefAttributes, 8 | RefForwardingComponent, 9 | useEffect, 10 | useRef, 11 | } from 'react'; 12 | 13 | export default function useEnsuredForwardedRef( 14 | forwardedRef: MutableRefObject 15 | ): MutableRefObject { 16 | const ensuredRef = useRef(forwardedRef && forwardedRef.current); 17 | 18 | useEffect(() => { 19 | if (!forwardedRef) { 20 | return; 21 | } 22 | forwardedRef.current = ensuredRef.current; 23 | }, [forwardedRef]); 24 | 25 | return ensuredRef; 26 | } 27 | 28 | export function ensuredForwardRef( 29 | Component: RefForwardingComponent 30 | ): ForwardRefExoticComponent & RefAttributes> { 31 | return forwardRef((props: PropsWithChildren

, ref) => { 32 | const ensuredRef = useEnsuredForwardedRef(ref as MutableRefObject); 33 | return Component(props, ensuredRef); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/useError.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | 3 | const useError = (): ((err: Error) => void) => { 4 | const [error, setError] = useState(null); 5 | 6 | useEffect(() => { 7 | if (error) { 8 | throw error; 9 | } 10 | }, [error]); 11 | 12 | const dispatchError = useCallback((err: Error) => { 13 | setError(err); 14 | }, []); 15 | 16 | return dispatchError; 17 | }; 18 | 19 | export default useError; 20 | -------------------------------------------------------------------------------- /src/useFavicon.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useFavicon = (href: string) => { 4 | useEffect(() => { 5 | const link: HTMLLinkElement = 6 | document.querySelector("link[rel*='icon']") || document.createElement('link'); 7 | link.type = 'image/x-icon'; 8 | link.rel = 'shortcut icon'; 9 | link.href = href; 10 | document.getElementsByTagName('head')[0].appendChild(link); 11 | }, [href]); 12 | }; 13 | 14 | export default useFavicon; 15 | -------------------------------------------------------------------------------- /src/useFirstMountState.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | export function useFirstMountState(): boolean { 4 | const isFirst = useRef(true); 5 | 6 | if (isFirst.current) { 7 | isFirst.current = false; 8 | 9 | return true; 10 | } 11 | 12 | return isFirst.current; 13 | } 14 | -------------------------------------------------------------------------------- /src/useGetSet.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, useMemo, useRef } from 'react'; 2 | import useUpdate from './useUpdate'; 3 | import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from './misc/hookState'; 4 | 5 | export default function useGetSet( 6 | initialState: IHookStateInitAction 7 | ): [get: () => S, set: Dispatch>] { 8 | const state = useRef(resolveHookState(initialState)); 9 | const update = useUpdate(); 10 | 11 | return useMemo( 12 | () => [ 13 | () => state.current as S, 14 | (newState: IHookStateSetAction) => { 15 | state.current = resolveHookState(newState, state.current); 16 | update(); 17 | }, 18 | ], 19 | [] 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/useGetSetState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef } from 'react'; 2 | import useUpdate from './useUpdate'; 3 | 4 | const useGetSetState = ( 5 | initialState: T = {} as T 6 | ): [() => T, (patch: Partial) => void] => { 7 | if (process.env.NODE_ENV !== 'production') { 8 | if (typeof initialState !== 'object') { 9 | console.error('useGetSetState initial state must be an object.'); 10 | } 11 | } 12 | 13 | const update = useUpdate(); 14 | const state = useRef({ ...(initialState as object) } as T); 15 | const get = useCallback(() => state.current, []); 16 | const set = useCallback((patch: Partial) => { 17 | if (!patch) { 18 | return; 19 | } 20 | if (process.env.NODE_ENV !== 'production') { 21 | if (typeof patch !== 'object') { 22 | console.error('useGetSetState setter patch must be an object.'); 23 | } 24 | } 25 | Object.assign(state.current, patch); 26 | update(); 27 | }, []); 28 | 29 | return [get, set]; 30 | }; 31 | 32 | export default useGetSetState; 33 | -------------------------------------------------------------------------------- /src/useHarmonicIntervalFn.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { clearHarmonicInterval, setHarmonicInterval } from 'set-harmonic-interval'; 3 | 4 | const useHarmonicIntervalFn = (fn: Function, delay: number | null = 0) => { 5 | const latestCallback = useRef(() => {}); 6 | 7 | useEffect(() => { 8 | latestCallback.current = fn; 9 | }); 10 | 11 | useEffect(() => { 12 | if (delay !== null) { 13 | const interval = setHarmonicInterval(() => latestCallback.current(), delay); 14 | return () => clearHarmonicInterval(interval); 15 | } 16 | return undefined; 17 | }, [delay]); 18 | }; 19 | 20 | export default useHarmonicIntervalFn; 21 | -------------------------------------------------------------------------------- /src/useHash.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | import useLifecycles from './useLifecycles'; 3 | import { off, on } from './misc/util'; 4 | 5 | /** 6 | * read and write url hash, response to url hash change 7 | */ 8 | export const useHash = () => { 9 | const [hash, setHash] = useState(() => window.location.hash); 10 | 11 | const onHashChange = useCallback(() => { 12 | setHash(window.location.hash); 13 | }, []); 14 | 15 | useLifecycles( 16 | () => { 17 | on(window, 'hashchange', onHashChange); 18 | }, 19 | () => { 20 | off(window, 'hashchange', onHashChange); 21 | } 22 | ); 23 | 24 | const _setHash = useCallback( 25 | (newHash: string) => { 26 | if (newHash !== hash) { 27 | window.location.hash = newHash; 28 | } 29 | }, 30 | [hash] 31 | ); 32 | 33 | return [hash, _setHash] as const; 34 | }; 35 | -------------------------------------------------------------------------------- /src/useHover.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { noop } from './misc/util'; 3 | 4 | const { useState } = React; 5 | 6 | export type Element = ((state: boolean) => React.ReactElement) | React.ReactElement; 7 | 8 | const useHover = (element: Element): [React.ReactElement, boolean] => { 9 | const [state, setState] = useState(false); 10 | 11 | const onMouseEnter = (originalOnMouseEnter?: any) => (event: any) => { 12 | (originalOnMouseEnter || noop)(event); 13 | setState(true); 14 | }; 15 | const onMouseLeave = (originalOnMouseLeave?: any) => (event: any) => { 16 | (originalOnMouseLeave || noop)(event); 17 | setState(false); 18 | }; 19 | 20 | if (typeof element === 'function') { 21 | element = element(state); 22 | } 23 | 24 | const el = React.cloneElement(element, { 25 | onMouseEnter: onMouseEnter(element.props.onMouseEnter), 26 | onMouseLeave: onMouseLeave(element.props.onMouseLeave), 27 | }); 28 | 29 | return [el, state]; 30 | }; 31 | 32 | export default useHover; 33 | -------------------------------------------------------------------------------- /src/useIntersection.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useState } from 'react'; 2 | 3 | const useIntersection = ( 4 | ref: RefObject, 5 | options: IntersectionObserverInit 6 | ): IntersectionObserverEntry | null => { 7 | const [intersectionObserverEntry, setIntersectionObserverEntry] = 8 | useState(null); 9 | 10 | useEffect(() => { 11 | if (ref.current && typeof IntersectionObserver === 'function') { 12 | const handler = (entries: IntersectionObserverEntry[]) => { 13 | setIntersectionObserverEntry(entries[0]); 14 | }; 15 | 16 | const observer = new IntersectionObserver(handler, options); 17 | observer.observe(ref.current); 18 | 19 | return () => { 20 | setIntersectionObserverEntry(null); 21 | observer.disconnect(); 22 | }; 23 | } 24 | return () => {}; 25 | }, [ref.current, options.threshold, options.root, options.rootMargin]); 26 | 27 | return intersectionObserverEntry; 28 | }; 29 | 30 | export default useIntersection; 31 | -------------------------------------------------------------------------------- /src/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const useInterval = (callback: Function, delay?: number | null) => { 4 | const savedCallback = useRef(() => {}); 5 | 6 | useEffect(() => { 7 | savedCallback.current = callback; 8 | }); 9 | 10 | useEffect(() => { 11 | if (delay !== null) { 12 | const interval = setInterval(() => savedCallback.current(), delay || 0); 13 | return () => clearInterval(interval); 14 | } 15 | 16 | return undefined; 17 | }, [delay]); 18 | }; 19 | 20 | export default useInterval; 21 | -------------------------------------------------------------------------------- /src/useIsomorphicLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useLayoutEffect } from 'react'; 2 | import { isBrowser } from './misc/util'; 3 | 4 | const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect; 5 | 6 | export default useIsomorphicLayoutEffect; 7 | -------------------------------------------------------------------------------- /src/useKeyPress.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import useKey, { KeyFilter } from './useKey'; 3 | 4 | const useKeyPress = (keyFilter: KeyFilter) => { 5 | const [state, set] = useState<[boolean, null | KeyboardEvent]>([false, null]); 6 | useKey(keyFilter, (event) => set([true, event]), { event: 'keydown' }, [state]); 7 | useKey(keyFilter, (event) => set([false, event]), { event: 'keyup' }, [state]); 8 | return state; 9 | }; 10 | 11 | export default useKeyPress; 12 | -------------------------------------------------------------------------------- /src/useKeyPressEvent.ts: -------------------------------------------------------------------------------- 1 | import { Handler, KeyFilter } from './useKey'; 2 | import useKeyPressDefault from './useKeyPress'; 3 | import useUpdateEffect from './useUpdateEffect'; 4 | 5 | const useKeyPressEvent = ( 6 | key: string | KeyFilter, 7 | keydown?: Handler | null | undefined, 8 | keyup?: Handler | null | undefined, 9 | useKeyPress = useKeyPressDefault 10 | ) => { 11 | const [pressed, event] = useKeyPress(key); 12 | useUpdateEffect(() => { 13 | if (!pressed && keyup) { 14 | keyup(event!); 15 | } else if (pressed && keydown) { 16 | keydown(event!); 17 | } 18 | }, [pressed]); 19 | }; 20 | 21 | export default useKeyPressEvent; 22 | -------------------------------------------------------------------------------- /src/useKeyboardJs.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import useMount from './useMount'; 3 | 4 | const useKeyboardJs = (combination: string | string[]) => { 5 | const [state, set] = useState<[boolean, null | KeyboardEvent]>([false, null]); 6 | const [keyboardJs, setKeyboardJs] = useState(null); 7 | 8 | useMount(() => { 9 | import('keyboardjs').then((k) => setKeyboardJs(k.default || k)); 10 | }); 11 | 12 | useEffect(() => { 13 | if (!keyboardJs) { 14 | return; 15 | } 16 | 17 | const down = (event) => set([true, event]); 18 | const up = (event) => set([false, event]); 19 | keyboardJs.bind(combination, down, up, true); 20 | 21 | return () => { 22 | keyboardJs.unbind(combination, down, up); 23 | }; 24 | }, [combination, keyboardJs]); 25 | 26 | return state; 27 | }; 28 | 29 | export default useKeyboardJs; 30 | -------------------------------------------------------------------------------- /src/useLatest.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | const useLatest = (value: T): { readonly current: T } => { 4 | const ref = useRef(value); 5 | ref.current = value; 6 | return ref; 7 | }; 8 | 9 | export default useLatest; 10 | -------------------------------------------------------------------------------- /src/useLifecycles.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useLifecycles = (mount, unmount?) => { 4 | useEffect(() => { 5 | if (mount) { 6 | mount(); 7 | } 8 | return () => { 9 | if (unmount) { 10 | unmount(); 11 | } 12 | }; 13 | }, []); 14 | }; 15 | 16 | export default useLifecycles; 17 | -------------------------------------------------------------------------------- /src/useLogger.ts: -------------------------------------------------------------------------------- 1 | import useEffectOnce from './useEffectOnce'; 2 | import useUpdateEffect from './useUpdateEffect'; 3 | 4 | const useLogger = (componentName: string, ...rest) => { 5 | useEffectOnce(() => { 6 | console.log(`${componentName} mounted`, ...rest); 7 | return () => console.log(`${componentName} unmounted`); 8 | }); 9 | 10 | useUpdateEffect(() => { 11 | console.log(`${componentName} updated`, ...rest); 12 | }); 13 | }; 14 | 15 | export default useLogger; 16 | -------------------------------------------------------------------------------- /src/useMediaDevices.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { isNavigator, noop, off, on } from './misc/util'; 3 | 4 | const useMediaDevices = () => { 5 | const [state, setState] = useState({}); 6 | 7 | useEffect(() => { 8 | let mounted = true; 9 | 10 | const onChange = () => { 11 | navigator.mediaDevices 12 | .enumerateDevices() 13 | .then((devices) => { 14 | if (mounted) { 15 | setState({ 16 | devices: devices.map(({ deviceId, groupId, kind, label }) => ({ 17 | deviceId, 18 | groupId, 19 | kind, 20 | label, 21 | })), 22 | }); 23 | } 24 | }) 25 | .catch(noop); 26 | }; 27 | 28 | on(navigator.mediaDevices, 'devicechange', onChange); 29 | onChange(); 30 | 31 | return () => { 32 | mounted = false; 33 | off(navigator.mediaDevices, 'devicechange', onChange); 34 | }; 35 | }, []); 36 | 37 | return state; 38 | }; 39 | 40 | const useMediaDevicesMock = () => ({}); 41 | 42 | export default isNavigator && !!navigator.mediaDevices ? useMediaDevices : useMediaDevicesMock; 43 | -------------------------------------------------------------------------------- /src/useMount.ts: -------------------------------------------------------------------------------- 1 | import useEffectOnce from './useEffectOnce'; 2 | 3 | const useMount = (fn: () => void) => { 4 | useEffectOnce(() => { 5 | fn(); 6 | }); 7 | }; 8 | 9 | export default useMount; 10 | -------------------------------------------------------------------------------- /src/useMountedState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from 'react'; 2 | 3 | export default function useMountedState(): () => boolean { 4 | const mountedRef = useRef(false); 5 | const get = useCallback(() => mountedRef.current, []); 6 | 7 | useEffect(() => { 8 | mountedRef.current = true; 9 | 10 | return () => { 11 | mountedRef.current = false; 12 | }; 13 | }, []); 14 | 15 | return get; 16 | } 17 | -------------------------------------------------------------------------------- /src/useMouseHovered.ts: -------------------------------------------------------------------------------- 1 | import { RefObject } from 'react'; 2 | import useHoverDirty from './useHoverDirty'; 3 | import useMouse, { State } from './useMouse'; 4 | 5 | export interface UseMouseHoveredOptions { 6 | whenHovered?: boolean; 7 | bound?: boolean; 8 | } 9 | 10 | const nullRef = { current: null }; 11 | 12 | const useMouseHovered = (ref: RefObject, options: UseMouseHoveredOptions = {}): State => { 13 | const whenHovered = !!options.whenHovered; 14 | const bound = !!options.bound; 15 | 16 | const isHovered = useHoverDirty(ref, whenHovered); 17 | const state = useMouse(whenHovered && !isHovered ? nullRef : ref); 18 | 19 | if (bound) { 20 | state.elX = Math.max(0, Math.min(state.elX, state.elW)); 21 | state.elY = Math.max(0, Math.min(state.elY, state.elH)); 22 | } 23 | 24 | return state; 25 | }; 26 | 27 | export default useMouseHovered; 28 | -------------------------------------------------------------------------------- /src/useMouseWheel.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { off, on } from './misc/util'; 3 | 4 | export default () => { 5 | const [mouseWheelScrolled, setMouseWheelScrolled] = useState(0); 6 | useEffect(() => { 7 | const updateScroll = (e: MouseWheelEvent) => { 8 | setMouseWheelScrolled(e.deltaY + mouseWheelScrolled); 9 | }; 10 | on(window, 'wheel', updateScroll, false); 11 | return () => off(window, 'wheel', updateScroll); 12 | }); 13 | return mouseWheelScrolled; 14 | }; 15 | -------------------------------------------------------------------------------- /src/useNumber.ts: -------------------------------------------------------------------------------- 1 | import useNumber from './useCounter'; 2 | 3 | export default useNumber; 4 | -------------------------------------------------------------------------------- /src/useObservable.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; 3 | 4 | export interface Observable { 5 | subscribe: (listener: (value: T) => void) => { 6 | unsubscribe: () => void; 7 | }; 8 | } 9 | 10 | function useObservable(observable$: Observable): T | undefined; 11 | function useObservable(observable$: Observable, initialValue: T): T; 12 | function useObservable(observable$: Observable, initialValue?: T): T | undefined { 13 | const [value, update] = useState(initialValue); 14 | 15 | useIsomorphicLayoutEffect(() => { 16 | const s = observable$.subscribe(update); 17 | return () => s.unsubscribe(); 18 | }, [observable$]); 19 | 20 | return value; 21 | } 22 | 23 | export default useObservable; 24 | -------------------------------------------------------------------------------- /src/usePageLeave.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { off, on } from './misc/util'; 3 | 4 | const usePageLeave = (onPageLeave, args = []) => { 5 | useEffect(() => { 6 | if (!onPageLeave) { 7 | return; 8 | } 9 | 10 | const handler = (event) => { 11 | event = event ? event : (window.event as any); 12 | const from = event.relatedTarget || event.toElement; 13 | if (!from || (from as any).nodeName === 'HTML') { 14 | onPageLeave(); 15 | } 16 | }; 17 | 18 | on(document, 'mouseout', handler); 19 | return () => { 20 | off(document, 'mouseout', handler); 21 | }; 22 | }, args); 23 | }; 24 | 25 | export default usePageLeave; 26 | -------------------------------------------------------------------------------- /src/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export default function usePrevious(state: T): T | undefined { 4 | const ref = useRef(); 5 | 6 | useEffect(() => { 7 | ref.current = state; 8 | }); 9 | 10 | return ref.current; 11 | } 12 | -------------------------------------------------------------------------------- /src/usePreviousDistinct.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | import { useFirstMountState } from './useFirstMountState'; 3 | 4 | export type Predicate = (prev: T | undefined, next: T) => boolean; 5 | 6 | const strictEquals = (prev: T | undefined, next: T) => prev === next; 7 | 8 | export default function usePreviousDistinct( 9 | value: T, 10 | compare: Predicate = strictEquals 11 | ): T | undefined { 12 | const prevRef = useRef(); 13 | const curRef = useRef(value); 14 | const isFirstMount = useFirstMountState(); 15 | 16 | if (!isFirstMount && !compare(curRef.current, value)) { 17 | prevRef.current = curRef.current; 18 | curRef.current = value; 19 | } 20 | 21 | return prevRef.current; 22 | } 23 | -------------------------------------------------------------------------------- /src/usePromise.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import useMountedState from './useMountedState'; 3 | 4 | export type UsePromise = () => (promise: Promise) => Promise; 5 | 6 | const usePromise: UsePromise = () => { 7 | const isMounted = useMountedState(); 8 | return useCallback( 9 | (promise: Promise) => 10 | new Promise((resolve, reject) => { 11 | const onValue = (value) => { 12 | isMounted() && resolve(value); 13 | }; 14 | const onError = (error) => { 15 | isMounted() && reject(error); 16 | }; 17 | promise.then(onValue, onError); 18 | }), 19 | [] 20 | ); 21 | }; 22 | 23 | export default usePromise; 24 | -------------------------------------------------------------------------------- /src/useQueue.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export interface QueueMethods { 4 | add: (item: T) => void; 5 | remove: () => T; 6 | first: T; 7 | last: T; 8 | size: number; 9 | } 10 | 11 | const useQueue = (initialValue: T[] = []): QueueMethods => { 12 | const [state, set] = useState(initialValue); 13 | return { 14 | add: (value) => { 15 | set((queue) => [...queue, value]); 16 | }, 17 | remove: () => { 18 | let result; 19 | set(([first, ...rest]) => { 20 | result = first; 21 | return rest; 22 | }); 23 | return result; 24 | }, 25 | get first() { 26 | return state[0]; 27 | }, 28 | get last() { 29 | return state[state.length - 1]; 30 | }, 31 | get size() { 32 | return state.length; 33 | }, 34 | }; 35 | }; 36 | 37 | export default useQueue; 38 | -------------------------------------------------------------------------------- /src/useRaf.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; 3 | 4 | const useRaf = (ms: number = 1e12, delay: number = 0): number => { 5 | const [elapsed, set] = useState(0); 6 | 7 | useIsomorphicLayoutEffect(() => { 8 | let raf; 9 | let timerStop; 10 | let start; 11 | 12 | const onFrame = () => { 13 | const time = Math.min(1, (Date.now() - start) / ms); 14 | set(time); 15 | loop(); 16 | }; 17 | const loop = () => { 18 | raf = requestAnimationFrame(onFrame); 19 | }; 20 | const onStart = () => { 21 | timerStop = setTimeout(() => { 22 | cancelAnimationFrame(raf); 23 | set(1); 24 | }, ms); 25 | start = Date.now(); 26 | loop(); 27 | }; 28 | const timerDelay = setTimeout(onStart, delay); 29 | 30 | return () => { 31 | clearTimeout(timerStop); 32 | clearTimeout(timerDelay); 33 | cancelAnimationFrame(raf); 34 | }; 35 | }, [ms, delay]); 36 | 37 | return elapsed; 38 | }; 39 | 40 | export default useRaf; 41 | -------------------------------------------------------------------------------- /src/useRafState.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useCallback, useRef, useState } from 'react'; 2 | 3 | import useUnmount from './useUnmount'; 4 | 5 | const useRafState = (initialState: S | (() => S)): [S, Dispatch>] => { 6 | const frame = useRef(0); 7 | const [state, setState] = useState(initialState); 8 | 9 | const setRafState = useCallback((value: S | ((prevState: S) => S)) => { 10 | cancelAnimationFrame(frame.current); 11 | 12 | frame.current = requestAnimationFrame(() => { 13 | setState(value); 14 | }); 15 | }, []); 16 | 17 | useUnmount(() => { 18 | cancelAnimationFrame(frame.current); 19 | }); 20 | 21 | return [state, setRafState]; 22 | }; 23 | 24 | export default useRafState; 25 | -------------------------------------------------------------------------------- /src/useRendersCount.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | export function useRendersCount(): number { 4 | return ++useRef(0).current; 5 | } 6 | -------------------------------------------------------------------------------- /src/useScrollbarWidth.ts: -------------------------------------------------------------------------------- 1 | import { scrollbarWidth } from '@xobotyi/scrollbar-width'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | export function useScrollbarWidth(): number | undefined { 5 | const [sbw, setSbw] = useState(scrollbarWidth()); 6 | 7 | // this needed to ensure the scrollbar width in case hook called before the DOM is ready 8 | useEffect(() => { 9 | if (typeof sbw !== 'undefined') { 10 | return; 11 | } 12 | 13 | const raf = requestAnimationFrame(() => { 14 | setSbw(scrollbarWidth()); 15 | }); 16 | 17 | return () => cancelAnimationFrame(raf); 18 | }, []); 19 | 20 | return sbw; 21 | } 22 | -------------------------------------------------------------------------------- /src/useScrolling.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useState } from 'react'; 2 | import { off, on } from './misc/util'; 3 | 4 | const useScrolling = (ref: RefObject): boolean => { 5 | const [scrolling, setScrolling] = useState(false); 6 | 7 | useEffect(() => { 8 | if (ref.current) { 9 | let scrollingTimeout; 10 | 11 | const handleScrollEnd = () => { 12 | setScrolling(false); 13 | }; 14 | 15 | const handleScroll = () => { 16 | setScrolling(true); 17 | clearTimeout(scrollingTimeout); 18 | scrollingTimeout = setTimeout(() => handleScrollEnd(), 150); 19 | }; 20 | 21 | on(ref.current, 'scroll', handleScroll, false); 22 | return () => { 23 | if (ref.current) { 24 | off(ref.current, 'scroll', handleScroll, false); 25 | } 26 | }; 27 | } 28 | return () => {}; 29 | }, [ref]); 30 | 31 | return scrolling; 32 | }; 33 | 34 | export default useScrolling; 35 | -------------------------------------------------------------------------------- /src/useSearchParam.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { isBrowser, off, on } from './misc/util'; 3 | 4 | const getValue = (search: string, param: string) => new URLSearchParams(search).get(param); 5 | 6 | export type UseQueryParam = (param: string) => string | null; 7 | 8 | const useSearchParam: UseQueryParam = (param) => { 9 | const location = window.location; 10 | const [value, setValue] = useState(() => getValue(location.search, param)); 11 | 12 | useEffect(() => { 13 | const onChange = () => { 14 | setValue(getValue(location.search, param)); 15 | }; 16 | 17 | on(window, 'popstate', onChange); 18 | on(window, 'pushstate', onChange); 19 | on(window, 'replacestate', onChange); 20 | 21 | return () => { 22 | off(window, 'popstate', onChange); 23 | off(window, 'pushstate', onChange); 24 | off(window, 'replacestate', onChange); 25 | }; 26 | }, []); 27 | 28 | return value; 29 | }; 30 | 31 | const useSearchParamServer = () => null; 32 | 33 | export default isBrowser ? useSearchParam : useSearchParamServer; 34 | -------------------------------------------------------------------------------- /src/useSetState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | const useSetState = ( 4 | initialState: T = {} as T 5 | ): [T, (patch: Partial | ((prevState: T) => Partial)) => void] => { 6 | const [state, set] = useState(initialState); 7 | const setState = useCallback((patch) => { 8 | set((prevState) => 9 | Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch) 10 | ); 11 | }, []); 12 | 13 | return [state, setState]; 14 | }; 15 | 16 | export default useSetState; 17 | -------------------------------------------------------------------------------- /src/useShallowCompareEffect.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, EffectCallback } from 'react'; 2 | import { equal as isShallowEqual } from 'fast-shallow-equal'; 3 | import useCustomCompareEffect from './useCustomCompareEffect'; 4 | 5 | const isPrimitive = (val: any) => val !== Object(val); 6 | const shallowEqualDepsList = (prevDeps: DependencyList, nextDeps: DependencyList) => 7 | prevDeps.every((dep, index) => isShallowEqual(dep, nextDeps[index])); 8 | 9 | const useShallowCompareEffect = (effect: EffectCallback, deps: DependencyList) => { 10 | if (process.env.NODE_ENV !== 'production') { 11 | if (!(deps instanceof Array) || !deps.length) { 12 | console.warn( 13 | '`useShallowCompareEffect` should not be used with no dependencies. Use React.useEffect instead.' 14 | ); 15 | } 16 | 17 | if (deps.every(isPrimitive)) { 18 | console.warn( 19 | '`useShallowCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.' 20 | ); 21 | } 22 | } 23 | 24 | useCustomCompareEffect(effect, deps, shallowEqualDepsList); 25 | }; 26 | 27 | export default useShallowCompareEffect; 28 | -------------------------------------------------------------------------------- /src/useThrottle.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import useUnmount from './useUnmount'; 3 | 4 | const useThrottle = (value: T, ms: number = 200) => { 5 | const [state, setState] = useState(value); 6 | const timeout = useRef>(); 7 | const nextValue = useRef(null) as any; 8 | const hasNextValue = useRef(0) as any; 9 | 10 | useEffect(() => { 11 | if (!timeout.current) { 12 | setState(value); 13 | const timeoutCallback = () => { 14 | if (hasNextValue.current) { 15 | hasNextValue.current = false; 16 | setState(nextValue.current); 17 | timeout.current = setTimeout(timeoutCallback, ms); 18 | } else { 19 | timeout.current = undefined; 20 | } 21 | }; 22 | timeout.current = setTimeout(timeoutCallback, ms); 23 | } else { 24 | nextValue.current = value; 25 | hasNextValue.current = true; 26 | } 27 | }, [value]); 28 | 29 | useUnmount(() => { 30 | timeout.current && clearTimeout(timeout.current); 31 | }); 32 | 33 | return state; 34 | }; 35 | 36 | export default useThrottle; 37 | -------------------------------------------------------------------------------- /src/useThrottleFn.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import useUnmount from './useUnmount'; 3 | 4 | const useThrottleFn = (fn: (...args: U) => T, ms: number = 200, args: U) => { 5 | const [state, setState] = useState(null); 6 | const timeout = useRef>(); 7 | const nextArgs = useRef(); 8 | 9 | useEffect(() => { 10 | if (!timeout.current) { 11 | setState(fn(...args)); 12 | const timeoutCallback = () => { 13 | if (nextArgs.current) { 14 | setState(fn(...nextArgs.current)); 15 | nextArgs.current = undefined; 16 | timeout.current = setTimeout(timeoutCallback, ms); 17 | } else { 18 | timeout.current = undefined; 19 | } 20 | }; 21 | timeout.current = setTimeout(timeoutCallback, ms); 22 | } else { 23 | nextArgs.current = args; 24 | } 25 | }, args); 26 | 27 | useUnmount(() => { 28 | timeout.current && clearTimeout(timeout.current); 29 | }); 30 | 31 | return state; 32 | }; 33 | 34 | export default useThrottleFn; 35 | -------------------------------------------------------------------------------- /src/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import useTimeoutFn from './useTimeoutFn'; 2 | import useUpdate from './useUpdate'; 3 | 4 | export type UseTimeoutReturn = [() => boolean | null, () => void, () => void]; 5 | 6 | export default function useTimeout(ms: number = 0): UseTimeoutReturn { 7 | const update = useUpdate(); 8 | 9 | return useTimeoutFn(update, ms); 10 | } 11 | -------------------------------------------------------------------------------- /src/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export interface UseTitleOptions { 4 | restoreOnUnmount?: boolean; 5 | } 6 | 7 | const DEFAULT_USE_TITLE_OPTIONS: UseTitleOptions = { 8 | restoreOnUnmount: false, 9 | }; 10 | 11 | function useTitle(title: string, options: UseTitleOptions = DEFAULT_USE_TITLE_OPTIONS) { 12 | const prevTitleRef = useRef(document.title); 13 | 14 | if (document.title !== title) document.title = title; 15 | 16 | useEffect(() => { 17 | if (options && options.restoreOnUnmount) { 18 | return () => { 19 | document.title = prevTitleRef.current; 20 | }; 21 | } else { 22 | return; 23 | } 24 | }, []); 25 | } 26 | 27 | export default typeof document !== 'undefined' ? useTitle : (_title: string) => {}; 28 | -------------------------------------------------------------------------------- /src/useToggle.ts: -------------------------------------------------------------------------------- 1 | import { Reducer, useReducer } from 'react'; 2 | 3 | const toggleReducer = (state: boolean, nextValue?: any) => 4 | typeof nextValue === 'boolean' ? nextValue : !state; 5 | 6 | const useToggle = (initialValue: boolean): [boolean, (nextValue?: any) => void] => { 7 | return useReducer>(toggleReducer, initialValue); 8 | }; 9 | 10 | export default useToggle; 11 | -------------------------------------------------------------------------------- /src/useTween.ts: -------------------------------------------------------------------------------- 1 | import { easing } from 'ts-easing'; 2 | import useRaf from './useRaf'; 3 | 4 | export type Easing = (t: number) => number; 5 | 6 | const useTween = (easingName: string = 'inCirc', ms: number = 200, delay: number = 0): number => { 7 | const fn: Easing = easing[easingName]; 8 | const t = useRaf(ms, delay); 9 | 10 | if (process.env.NODE_ENV !== 'production') { 11 | if (typeof fn !== 'function') { 12 | console.error( 13 | 'useTween() expected "easingName" property to be a valid easing function name, like:' + 14 | '"' + 15 | Object.keys(easing).join('", "') + 16 | '".' 17 | ); 18 | console.trace(); 19 | return 0; 20 | } 21 | } 22 | 23 | return fn(t); 24 | }; 25 | 26 | export default useTween; 27 | -------------------------------------------------------------------------------- /src/useUnmount.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | import useEffectOnce from './useEffectOnce'; 3 | 4 | const useUnmount = (fn: () => any): void => { 5 | const fnRef = useRef(fn); 6 | 7 | // update the ref each render so if it change the newest callback will be invoked 8 | fnRef.current = fn; 9 | 10 | useEffectOnce(() => () => fnRef.current()); 11 | }; 12 | 13 | export default useUnmount; 14 | -------------------------------------------------------------------------------- /src/useUnmountPromise.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react'; 2 | import useEffectOnce from './useEffectOnce'; 3 | 4 | export type Race =

, E = any>(promise: P, onError?: (error: E) => void) => P; 5 | 6 | const useUnmountPromise = (): Race => { 7 | const refUnmounted = useRef(false); 8 | useEffectOnce(() => () => { 9 | refUnmounted.current = true; 10 | }); 11 | 12 | const wrapper = useMemo(() => { 13 | const race =

, E>(promise: P, onError?: (error: E) => void) => { 14 | const newPromise: P = new Promise((resolve, reject) => { 15 | promise.then( 16 | (result) => { 17 | if (!refUnmounted.current) resolve(result); 18 | }, 19 | (error) => { 20 | if (!refUnmounted.current) reject(error); 21 | else if (onError) onError(error); 22 | else console.error('useUnmountPromise', error); 23 | } 24 | ); 25 | }) as P; 26 | return newPromise; 27 | }; 28 | return race; 29 | }, []); 30 | 31 | return wrapper; 32 | }; 33 | 34 | export default useUnmountPromise; 35 | -------------------------------------------------------------------------------- /src/useUpdate.ts: -------------------------------------------------------------------------------- 1 | import { useReducer } from 'react'; 2 | 3 | const updateReducer = (num: number): number => (num + 1) % 1_000_000; 4 | 5 | export default function useUpdate(): () => void { 6 | const [, update] = useReducer(updateReducer, 0); 7 | 8 | return update; 9 | } 10 | -------------------------------------------------------------------------------- /src/useUpdateEffect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useFirstMountState } from './useFirstMountState'; 3 | 4 | const useUpdateEffect: typeof useEffect = (effect, deps) => { 5 | const isFirstMount = useFirstMountState(); 6 | 7 | useEffect(() => { 8 | if (!isFirstMount) { 9 | return effect(); 10 | } 11 | }, deps); 12 | }; 13 | 14 | export default useUpdateEffect; 15 | -------------------------------------------------------------------------------- /src/useUpsert.ts: -------------------------------------------------------------------------------- 1 | import useList, { ListActions } from './useList'; 2 | import { IHookStateInitAction } from './misc/hookState'; 3 | 4 | export interface UpsertListActions extends Omit, 'upsert'> { 5 | upsert: (newItem: T) => void; 6 | } 7 | 8 | /** 9 | * @deprecated Use `useList` hook's upsert action instead 10 | */ 11 | export default function useUpsert( 12 | predicate: (a: T, b: T) => boolean, 13 | initialList: IHookStateInitAction = [] 14 | ): [T[], UpsertListActions] { 15 | const [list, listActions] = useList(initialList); 16 | 17 | return [ 18 | list, 19 | { 20 | ...listActions, 21 | upsert: (newItem: T) => { 22 | listActions.upsert(predicate, newItem); 23 | }, 24 | } as UpsertListActions, 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /src/useVibrate.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { isNavigator, noop } from './misc/util'; 3 | 4 | export type VibrationPattern = number | number[]; 5 | 6 | const isVibrationApiSupported = isNavigator && 'vibrate' in navigator; 7 | 8 | function useVibrate( 9 | enabled: boolean = true, 10 | pattern: VibrationPattern = [1000, 1000], 11 | loop: boolean = true 12 | ): void { 13 | useEffect(() => { 14 | let interval; 15 | 16 | if (enabled) { 17 | navigator.vibrate(pattern); 18 | 19 | if (loop) { 20 | const duration = 21 | pattern instanceof Array ? pattern.reduce((a, b) => a + b) : (pattern as number); 22 | 23 | interval = setInterval(() => { 24 | navigator.vibrate(pattern); 25 | }, duration); 26 | } 27 | } 28 | 29 | return () => { 30 | if (enabled) { 31 | navigator.vibrate(0); 32 | 33 | if (loop) { 34 | clearInterval(interval); 35 | } 36 | } 37 | }; 38 | }, [enabled]); 39 | } 40 | 41 | export default isVibrationApiSupported ? useVibrate : noop; 42 | -------------------------------------------------------------------------------- /src/useVideo.ts: -------------------------------------------------------------------------------- 1 | import createHTMLMediaHook from './factory/createHTMLMediaHook'; 2 | 3 | const useVideo = createHTMLMediaHook('video'); 4 | 5 | export default useVideo; 6 | -------------------------------------------------------------------------------- /src/useWait.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamich/react-use/ad33f76dfff7ddb041a9ef74b80656a94affaa80/src/useWait.ts -------------------------------------------------------------------------------- /stories/comps/UseKey.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import UseKey from '../../src/component/UseKey'; 4 | 5 | storiesOf('Components/', module).add('Demo', () => ( 6 |

7 | Press "q" key! 8 | alert('Q pressed!')} /> 9 |
10 | )); 11 | -------------------------------------------------------------------------------- /stories/createBreakpoint.story.tsx: -------------------------------------------------------------------------------- 1 | import { withKnobs } from '@storybook/addon-knobs'; 2 | import { storiesOf } from '@storybook/react'; 3 | import React from 'react'; 4 | import { createBreakpoint } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const useBreakpointA = createBreakpoint(); 8 | const useBreakpointB = createBreakpoint({ mobileM: 350, laptop: 1024, tablet: 768 }); 9 | 10 | const Demo = () => { 11 | const breakpointA = useBreakpointA(); 12 | const breakpointB = useBreakpointB(); 13 | return ( 14 |
15 |

{'try resize your window'}

16 |

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

17 |

{breakpointA}

18 |

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

19 |

{breakpointB}

20 |
21 | ); 22 | }; 23 | 24 | storiesOf('sensors/createBreakpoint', module) 25 | .addDecorator(withKnobs) 26 | .add('Docs', () => ) 27 | .add('Demo', () => { 28 | return ; 29 | }); 30 | -------------------------------------------------------------------------------- /stories/createGlobalState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import React, { FC } from 'react'; 3 | import { createGlobalState } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const useGlobalValue = createGlobalState(0); 7 | 8 | const CompA: FC = () => { 9 | const [value, setValue] = useGlobalValue(); 10 | 11 | return ; 12 | }; 13 | 14 | const CompB: FC = () => { 15 | const [value, setValue] = useGlobalValue(); 16 | 17 | return ; 18 | }; 19 | 20 | const Demo: FC = () => { 21 | const [value] = useGlobalValue(); 22 | return ( 23 |
24 |

{value}

25 | 26 | 27 |
28 | ); 29 | }; 30 | 31 | storiesOf('State/createGlobalState', module) 32 | .add('Docs', () => ) 33 | .add('Demo', () => ); 34 | -------------------------------------------------------------------------------- /stories/createMemo.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { createMemo } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const fibonacci = (n) => { 7 | if (n === 0) { 8 | return 0; 9 | } 10 | if (n === 1) { 11 | return 1; 12 | } 13 | return fibonacci(n - 1) + fibonacci(n - 2); 14 | }; 15 | 16 | const useMemoFibonacci = createMemo(fibonacci); 17 | 18 | const Demo = () => { 19 | const result = useMemoFibonacci(10); 20 | 21 | return
fib(10) = {result}
; 22 | }; 23 | 24 | storiesOf('State/createMemo', module) 25 | .add('Docs', () => ) 26 | .add('Demo', () => ); 27 | -------------------------------------------------------------------------------- /stories/useAsyncFn.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useAsyncFn } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [state, callback] = useAsyncFn( 8 | () => 9 | new Promise((resolve, reject) => { 10 | setTimeout(() => { 11 | if (Math.random() > 0.5) { 12 | resolve('✌️'); 13 | } else { 14 | reject(new Error('A pseudo random error occurred')); 15 | } 16 | }, 1000); 17 | }) 18 | ); 19 | 20 | return ( 21 |
22 | {state.loading ? ( 23 |

Loading...

24 | ) : state.error ? ( 25 |

Error: {state.error.message}

26 | ) : ( 27 |

Value: {state.value}

28 | )} 29 | 30 |
{JSON.stringify(state, null, 2)}
31 |
32 | ); 33 | }; 34 | 35 | storiesOf('Side effects/useAsyncFn', module) 36 | .add('Docs', () => ) 37 | .add('Demo', () => ); 38 | -------------------------------------------------------------------------------- /stories/useBoolean.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useBoolean } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [on, toggle] = useBoolean(true); 8 | 9 | return ( 10 |
11 |
{on ? 'ON' : 'OFF'}
12 | 13 | 14 | 15 |
16 | ); 17 | }; 18 | 19 | storiesOf('State/useBoolean', module) 20 | .add('Docs', () => ) 21 | .add('Demo', () => ); 22 | -------------------------------------------------------------------------------- /stories/useClickAway.story.tsx: -------------------------------------------------------------------------------- 1 | import { action } from '@storybook/addon-actions'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import { useRef } from 'react'; 5 | import { useClickAway } from '../src'; 6 | import ShowDocs from './util/ShowDocs'; 7 | 8 | const Demo = () => { 9 | const ref = useRef(null); 10 | useClickAway(ref, action('outside clicked')); 11 | 12 | return ( 13 |
21 | ); 22 | }; 23 | 24 | storiesOf('UI/useClickAway', module) 25 | .add('Docs', () => ) 26 | .add('Demo', () => ); 27 | -------------------------------------------------------------------------------- /stories/useCookie.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import React, { useState, useEffect } from 'react'; 3 | import { useCookie } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [value, updateCookie, deleteCookie] = useCookie('my-cookie'); 8 | const [counter, setCounter] = useState(1); 9 | 10 | useEffect(() => { 11 | deleteCookie(); 12 | }, []); 13 | 14 | const updateCookieHandler = () => { 15 | updateCookie(`my-awesome-cookie-${counter}`); 16 | setCounter((c) => c + 1); 17 | }; 18 | 19 | return ( 20 |
21 |

Value: {value}

22 | 23 |
24 | 25 |
26 | ); 27 | }; 28 | 29 | storiesOf('Side effects/useCookie', module) 30 | .add('Docs', () => ) 31 | .add('Demo', () => ); 32 | -------------------------------------------------------------------------------- /stories/useCss.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useCss } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const className = useCss({ 8 | color: 'red', 9 | border: '1px solid red', 10 | '&:hover': { 11 | color: 'blue', 12 | }, 13 | }); 14 | 15 | return
hello
; 16 | }; 17 | 18 | storiesOf('UI/useCss', module) 19 | .add('Docs', () => ) 20 | .add('Demo', () => ); 21 | -------------------------------------------------------------------------------- /stories/useCustomCompareEffect.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useCounter, useCustomCompareEffect } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | import isDeepEqual from '../src/misc/isDeepEqual'; 6 | 7 | const Demo = () => { 8 | const [countNormal, { inc: incNormal }] = useCounter(0); 9 | const [countDeep, { inc: incDeep }] = useCounter(0); 10 | const options = { max: 500 }; 11 | 12 | React.useEffect(() => { 13 | if (countNormal < options.max) { 14 | incNormal(); 15 | } 16 | }, [options]); 17 | 18 | useCustomCompareEffect( 19 | () => { 20 | if (countNormal < options.max) { 21 | incDeep(); 22 | } 23 | }, 24 | [options], 25 | (prevDeps, nextDeps) => isDeepEqual(prevDeps, nextDeps) 26 | ); 27 | 28 | return ( 29 |
30 |

useEffect: {countNormal}

31 |

useCustomCompareEffect: {countDeep}

32 |
33 | ); 34 | }; 35 | 36 | storiesOf('Lifecycle/useCustomCompareEffect', module) 37 | .add('Docs', () => ) 38 | .add('Demo', () => ); 39 | -------------------------------------------------------------------------------- /stories/useDeepCompareEffect.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useCounter, useDeepCompareEffect } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [countNormal, { inc: incNormal }] = useCounter(0); 8 | const [countDeep, { inc: incDeep }] = useCounter(0); 9 | const options = { max: 500 }; 10 | 11 | React.useEffect(() => { 12 | if (countNormal < options.max) { 13 | incNormal(); 14 | } 15 | }, [options]); 16 | 17 | useDeepCompareEffect(() => { 18 | if (countNormal < options.max) { 19 | incDeep(); 20 | } 21 | }, [options]); 22 | 23 | return ( 24 |
25 |

useEffect: {countNormal}

26 |

useDeepCompareEffect: {countDeep}

27 |
28 | ); 29 | }; 30 | 31 | storiesOf('Lifecycle/useDeepCompareEffect', module) 32 | .add('Docs', () => ) 33 | .add('Demo', () => ); 34 | -------------------------------------------------------------------------------- /stories/useDefault.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useDefault } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const initialUser = { name: 'Marshall' }; 8 | const defaultUser = { name: 'Mathers' }; 9 | const [user, setUser] = useDefault(defaultUser, initialUser); 10 | 11 | return ( 12 |
13 |
User: {user.name}
14 | setUser({ name: e.target.value })} /> 15 | 16 |
17 | ); 18 | }; 19 | 20 | storiesOf('State/useDefault', module) 21 | .add('Docs', () => ) 22 | .add('Demo', () => ); 23 | -------------------------------------------------------------------------------- /stories/useEffectOnce.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useEffectOnce } from '../src'; 4 | import ConsoleStory from './util/ConsoleStory'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | useEffectOnce(() => { 9 | console.log('Running effect once on mount'); 10 | 11 | return () => { 12 | console.log('Running clean-up of effect on unmount'); 13 | }; 14 | }); 15 | 16 | return ; 17 | }; 18 | 19 | storiesOf('Lifecycle/useEffectOnce', module) 20 | .add('Docs', () => ) 21 | .add('Demo', () => ); 22 | -------------------------------------------------------------------------------- /stories/useEvent.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useEvent, useList } from '../src'; 4 | import { CenterStory } from './util/CenterStory'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const { useCallback } = React; 8 | 9 | const Demo = () => { 10 | const [list, { push, clear }] = useList(); 11 | 12 | const onKeyDown = useCallback(({ key }) => { 13 | if (key === 'r') { 14 | clear(); 15 | } 16 | push(key); 17 | }, []); 18 | 19 | useEvent('keydown', onKeyDown); 20 | 21 | return ( 22 | 23 |

24 | Press some keys on your keyboard, r key resets the 25 | list 26 |

27 |
{JSON.stringify(list, null, 4)}
28 |
29 | ); 30 | }; 31 | 32 | storiesOf('Sensors/useEvent', module) 33 | .add('Docs', () => ) 34 | .add('Demo', () => ); 35 | -------------------------------------------------------------------------------- /stories/useFavicon.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useFavicon } from '../src'; 4 | import NewTabStory from './util/NewTabStory'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | useFavicon('https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico'); 9 | 10 | return Favicon should be the Stack Overflow logo; 11 | }; 12 | 13 | storiesOf('Side effects/useFavicon', module) 14 | .add('Docs', () => ) 15 | .add('Demo', () => ); 16 | -------------------------------------------------------------------------------- /stories/useFirstMountState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useFirstMountState } from '../src/useFirstMountState'; 4 | import useUpdate from '../src/useUpdate'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | const isFirstMount = useFirstMountState(); 9 | const update = useUpdate(); 10 | 11 | return ( 12 |
13 | This component is just mounted: {isFirstMount ? 'YES' : 'NO'} 14 |
15 | 16 |
17 | ); 18 | }; 19 | 20 | storiesOf('State/useFirstMountState', module) 21 | .add('Docs', () => ) 22 | .add('Demo', () => ); 23 | -------------------------------------------------------------------------------- /stories/useGeolocation.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useGeolocation } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const state = useGeolocation(); 8 | 9 | return
{JSON.stringify(state, null, 2)}
; 10 | }; 11 | 12 | storiesOf('Sensors/useGeolocation', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/useGetSet.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useState } from 'react'; 4 | import { useGetSet } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | const [get, set] = useGetSet(0); 9 | const onClick = () => { 10 | setTimeout(() => { 11 | set(get() + 1); 12 | }, 1_000); 13 | }; 14 | 15 | return ; 16 | }; 17 | 18 | const DemoWrong = () => { 19 | const [cnt, set] = useState(0); 20 | const onClick = () => { 21 | setTimeout(() => { 22 | set(cnt + 1); 23 | }, 1_000); 24 | }; 25 | 26 | return ; 27 | }; 28 | 29 | storiesOf('State/useGetSet', module) 30 | .add('Docs', () => ) 31 | .add('Demo, 1s delay', () => ) 32 | .add('DemoWrong, 1s delay', () => ); 33 | -------------------------------------------------------------------------------- /stories/useGetSetState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useGetSetState } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [get, setState] = useGetSetState<{ cnt: number }>({ cnt: 0 }); 8 | const onClick = () => { 9 | setTimeout(() => { 10 | setState({ cnt: get().cnt + 1 }); 11 | }, 1_000); 12 | }; 13 | 14 | return ; 15 | }; 16 | 17 | storiesOf('State/useGetSetState', module) 18 | .add('Docs', () => ) 19 | .add('Demo', () => ); 20 | -------------------------------------------------------------------------------- /stories/useHash.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useHash, useMount } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [hash, setHash] = useHash(); 8 | 9 | useMount(() => { 10 | setHash('#/path/to/page?userId=123'); 11 | }); 12 | 13 | return ( 14 |
15 |
window.location.href:
16 |
17 |
{window.location.href}
18 |
19 |
Edit hash:
20 |
21 | setHash(e.target.value)} /> 22 |
23 |
24 | ); 25 | }; 26 | 27 | storiesOf('Sensors/useHash', module) 28 | .add('Docs', () => ) 29 | .add('Demo', () => ); 30 | -------------------------------------------------------------------------------- /stories/useHover.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useHover } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const element = (hasHovered: boolean) =>
Hover me! {hasHovered && 'Thanks!'}
; 8 | const [hoverable, hovered] = useHover(element); 9 | 10 | return ( 11 |
12 | {hoverable} 13 |
{hovered ? 'HOVERED' : ''}
14 |
15 | ); 16 | }; 17 | 18 | storiesOf('Sensors/useHover', module) 19 | .add('Docs', () => ) 20 | .add('Demo', () => ); 21 | -------------------------------------------------------------------------------- /stories/useHoverDirty.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useRef } from 'react'; 4 | import { useHoverDirty } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | const ref = useRef(null); 9 | const isHovered = useHoverDirty(ref); 10 | 11 | return
{isHovered ? '😁' : '☹️'}
; 12 | }; 13 | 14 | storiesOf('Sensors/useHoverDirty', module) 15 | .add('Docs', () => ) 16 | .add('Demo', () => ); 17 | -------------------------------------------------------------------------------- /stories/useIdle.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useIdle } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [idleDelay, setIdleDelay] = React.useState(3e3); 8 | const isIdle = useIdle(idleDelay); 9 | 10 | return ( 11 |
12 | Idle delay ms:{' '} 13 | setIdleDelay(+target.value)} 17 | /> 18 |
User is idle: {isIdle ? 'Yes' : 'No'}
19 |
20 | ); 21 | }; 22 | 23 | storiesOf('Sensors/useIdle', module) 24 | .add('Docs', () => ) 25 | .add('Demo', () => ); 26 | -------------------------------------------------------------------------------- /stories/useInterval.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useInterval, useBoolean } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [count, setCount] = React.useState(0); 8 | const [delay, setDelay] = React.useState(1000); 9 | const [isRunning, toggleIsRunning] = useBoolean(true); 10 | 11 | useInterval( 12 | () => { 13 | setCount(count + 1); 14 | }, 15 | isRunning ? delay : null 16 | ); 17 | 18 | return ( 19 |
20 |
21 | delay: setDelay(Number(event.target.value))} /> 22 |
23 |

count: {count}

24 |
25 | 26 |
27 |
28 | ); 29 | }; 30 | 31 | storiesOf('Animation/useInterval', module) 32 | .add('Docs', () => ) 33 | .add('Demo', () => ); 34 | -------------------------------------------------------------------------------- /stories/useIsomorphicLayoutEffect.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import ShowDocs from './util/ShowDocs'; 4 | 5 | storiesOf('Lifecycle/useIsomorphicLayoutEffect', module).add('Docs', () => ( 6 | 7 | )); 8 | -------------------------------------------------------------------------------- /stories/useKeyPress.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useKeyPress } from '../src'; 4 | import { CenterStory } from './util/CenterStory'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; 8 | 9 | const Demo = () => { 10 | const states: boolean[] = []; 11 | for (const key of keys) { 12 | // eslint-disable-next-line react-hooks/rules-of-hooks 13 | states.push(useKeyPress(key)[0]); 14 | } 15 | 16 | return ( 17 | 18 |
19 | Try pressing numbers 20 |
21 | {states.reduce( 22 | (s, pressed, index) => s + (pressed ? (s ? ' + ' : '') + keys[index] : ''), 23 | '' 24 | )} 25 |
26 |
27 | ); 28 | }; 29 | 30 | storiesOf('Sensors/useKeyPress', module) 31 | .add('Docs', () => ) 32 | .add('Demo', () => ); 33 | -------------------------------------------------------------------------------- /stories/useKeyboardJs.story.tsx: -------------------------------------------------------------------------------- 1 | import { text, withKnobs } from '@storybook/addon-knobs'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import useKeyboardJs from '../src/useKeyboardJs'; 5 | import { CenterStory } from './util/CenterStory'; 6 | import ShowDocs from './util/ShowDocs'; 7 | 8 | const Demo = ({ combo }) => { 9 | const [pressed] = useKeyboardJs(combo); 10 | 11 | return ( 12 | 13 |
14 | Press{' '} 15 | 17 | {combo} 18 | {' '} 19 | combo 20 |
21 |
22 |
{pressed ? '💋' : ''}
23 |
24 |
25 | ); 26 | }; 27 | 28 | storiesOf('Sensors/useKeyboardJs', module) 29 | .addDecorator(withKnobs) 30 | .add('Docs', () => ) 31 | .add('Demo', () => { 32 | const combo = text('Combo', 'i + l + u'); 33 | return ; 34 | }); 35 | -------------------------------------------------------------------------------- /stories/useLatest.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useLatest } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [count, setCount] = React.useState(0); 8 | const latestCount = useLatest(count); 9 | const timeoutMs = 3000; 10 | 11 | function handleAlertClick() { 12 | setTimeout(() => { 13 | alert(`Latest count value: ${latestCount.current}`); 14 | }, timeoutMs); 15 | } 16 | 17 | return ( 18 |
19 |

You clicked {count} times

20 | 21 | 22 |
23 | ); 24 | }; 25 | 26 | storiesOf('State/useLatest', module) 27 | .add('Docs', () => ) 28 | .add('Demo', () => ); 29 | -------------------------------------------------------------------------------- /stories/useLifecycles.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useLifecycles } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | useLifecycles( 8 | () => console.log('MOUNTED'), 9 | () => console.log('UNMOUNTED') 10 | ); 11 | return null; 12 | }; 13 | 14 | storiesOf('Lifecycle/useLifecycles', module) 15 | .add('Docs', () => ) 16 | .add('Demo', () => ); 17 | -------------------------------------------------------------------------------- /stories/useLocalStorage.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useLocalStorage } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [value, setValue] = useLocalStorage('hello-key', 'foo'); 8 | const [removableValue, setRemovableValue, remove] = useLocalStorage('removeable-key'); 9 | 10 | return ( 11 |
12 |
Value: {value}
13 | 14 | 15 |
16 |
17 |
Removable Value: {removableValue}
18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | storiesOf('Side effects/useLocalStorage', module) 26 | .add('Docs', () => ) 27 | .add('Demo', () => ); 28 | -------------------------------------------------------------------------------- /stories/useLocation.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useLocation } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const go = (page) => window.history.pushState({}, '', page); 7 | 8 | const Demo = () => { 9 | const state = useLocation(); 10 | 11 | return ( 12 |
13 | 14 | 15 |
{JSON.stringify(state, null, 2)}
16 |
17 | ); 18 | }; 19 | 20 | storiesOf('Sensors/useLocation', module) 21 | .add('Docs', () => ) 22 | .add('Demo', () => ); 23 | -------------------------------------------------------------------------------- /stories/useLogger.story.tsx: -------------------------------------------------------------------------------- 1 | import { boolean, text, withKnobs } from '@storybook/addon-knobs'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import { useCounter, useLogger } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = (props) => { 8 | const [state, { inc }] = useCounter(0); 9 | 10 | useLogger('Demo', props, state); 11 | 12 | return ( 13 | <> 14 |

{props.title}

15 | 16 | 17 | ); 18 | }; 19 | 20 | storiesOf('Lifecycle/useLogger', module) 21 | .addDecorator(withKnobs) 22 | .add('Docs', () => ) 23 | .add('Demo', () => { 24 | const props = { 25 | title: text('title', 'Open the developer console to see logs'), 26 | bold: boolean('bold', false), 27 | }; 28 | 29 | return ; 30 | }); 31 | -------------------------------------------------------------------------------- /stories/useLongPress.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useLongPress } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const onLongPress = () => { 8 | console.log('calls callback after long pressing 300ms'); 9 | }; 10 | 11 | const defaultOptions = { 12 | isPreventDefault: true, 13 | delay: 300, 14 | }; 15 | const longPressEvent = useLongPress(onLongPress, defaultOptions); 16 | 17 | return ; 18 | }; 19 | 20 | storiesOf('Sensors/useLongPress', module) 21 | .add('Docs', () => ) 22 | .add('Demo', () => ); 23 | -------------------------------------------------------------------------------- /stories/useMap.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMap } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [map, { set, remove, reset }] = useMap({ 8 | hello: 'there', 9 | }); 10 | 11 | return ( 12 |
13 | 14 | 15 | 18 |
{JSON.stringify(map, null, 2)}
19 |
20 | ); 21 | }; 22 | 23 | storiesOf('State/useMap', module) 24 | .add('Docs', () => ) 25 | .add('Demo', () => ); 26 | -------------------------------------------------------------------------------- /stories/useMeasure.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import React from 'react'; 3 | import { useMeasure } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [ref, state] = useMeasure(); 8 | 9 | return ( 10 | <> 11 |
{JSON.stringify(state, null, 2)}
12 |
13 | resize me 14 |
15 | 16 | ); 17 | }; 18 | 19 | storiesOf('Sensors/useMeasure', module) 20 | .add('Docs', () => ) 21 | .add('Demo', () => ); 22 | -------------------------------------------------------------------------------- /stories/useMedia.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMedia } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const isWide = useMedia('(min-width: 480px)'); 8 | 9 | return
Screen is wide: {isWide ? 'Yes' : 'No'}
; 10 | }; 11 | 12 | storiesOf('Sensors/useMedia', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/useMediaDevices.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMediaDevices } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const state = useMediaDevices(); 8 | 9 | return
{JSON.stringify(state, null, 2)}
; 10 | }; 11 | 12 | storiesOf('Sensors/useMediaDevices', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/useMediatedState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMediatedState } from '../src/useMediatedState'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const inputMediator = (s) => s.replace(/[\s]+/g, ' '); 7 | const Demo = () => { 8 | const [state, setState] = useMediatedState(inputMediator, ''); 9 | 10 | return ( 11 |
12 |
You will not be able to enter more than one space
13 | ) => { 19 | setState(ev.target.value); 20 | }} 21 | /> 22 |
23 | ); 24 | }; 25 | 26 | storiesOf('State/useMediatedState', module) 27 | .add('Docs', () => ) 28 | .add('Demo', () => ); 29 | -------------------------------------------------------------------------------- /stories/useMethods.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMethods } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const initialState = { 7 | count: 0, 8 | }; 9 | 10 | function createMethods(state) { 11 | return { 12 | reset() { 13 | return initialState; 14 | }, 15 | increment() { 16 | return { ...state, count: state.count + 1 }; 17 | }, 18 | decrement() { 19 | return { ...state, count: state.count - 1 }; 20 | }, 21 | }; 22 | } 23 | 24 | const Demo = () => { 25 | const [state, methods] = useMethods(createMethods, initialState); 26 | 27 | return ( 28 | <> 29 |

Count: {state.count}

30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | storiesOf('State/useMethods', module) 37 | .add('Docs', () => ) 38 | .add('Demo', () => ); 39 | -------------------------------------------------------------------------------- /stories/useMotion.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMotion } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const state = useMotion(); 8 | 9 | return
{JSON.stringify(state, null, 2)}
; 10 | }; 11 | 12 | storiesOf('Sensors/useMotion', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/useMount.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMount } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | useMount(() => alert('MOUNTED')); 8 | 9 | return ( 10 |
11 | useMount() hook can be used to perform a side-effect when component is mounted. 12 |
13 | ); 14 | }; 15 | 16 | storiesOf('Lifecycle/useMount', module) 17 | .add('Docs', () => ) 18 | .add('Demo', () => ); 19 | -------------------------------------------------------------------------------- /stories/useMountedState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMountedState } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const isMounted = useMountedState(); 8 | const [, updateState] = React.useState(); 9 | 10 | requestAnimationFrame(updateState); 11 | 12 | return
This component is {isMounted() ? 'MOUNTED' : 'NOT MOUNTED'}
; 13 | }; 14 | 15 | storiesOf('Lifecycle/useMountedState', module) 16 | .add('Docs', () => ) 17 | .add('Demo', () => ); 18 | -------------------------------------------------------------------------------- /stories/useMouse.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMouse } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const ref = React.useRef(null); 8 | const state = useMouse(ref); 9 | 10 | return ( 11 | <> 12 |
{JSON.stringify(state, null, 2)}
13 |
14 |
15 |
23 | 31 | 🐭 32 | 33 |
34 | 35 | ); 36 | }; 37 | 38 | storiesOf('Sensors/useMouse', module) 39 | .add('Docs', () => ) 40 | .add('Demo', () => ); 41 | -------------------------------------------------------------------------------- /stories/useMouseWheel.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useMouseWheel } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const mouseWheel = useMouseWheel(); 8 | return ( 9 | <> 10 |

delta Y Scrolled: {mouseWheel}

11 | 12 | ); 13 | }; 14 | 15 | storiesOf('Sensors/useMouseWheel', module) 16 | .add('Docs', () => ) 17 | .add('Demo', () => ); 18 | -------------------------------------------------------------------------------- /stories/useNetwork.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useEffect } from 'react'; 4 | import { useNetworkState } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | const state = useNetworkState(); 9 | 10 | useEffect(() => { 11 | console.log(state); 12 | }, [state]); 13 | 14 | return ( 15 |
16 |
Since JSON do not output `undefined` fields look the console to see whole the state
17 |
{JSON.stringify(state, null, 2)}
18 |
19 | ); 20 | }; 21 | 22 | storiesOf('Sensors/useNetworkState', module) 23 | .add('Docs', () => ) 24 | .add('Demo', () => ); 25 | -------------------------------------------------------------------------------- /stories/useObservable.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | import { useObservable } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const counter$ = new BehaviorSubject(0); 8 | const Demo = () => { 9 | const value = useObservable(counter$, 0); 10 | 11 | return ; 12 | }; 13 | 14 | storiesOf('State/useObservable', module) 15 | .add('Docs', () => ) 16 | .add('Demo', () => ); 17 | -------------------------------------------------------------------------------- /stories/useOrientation.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useOrientation } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const state = useOrientation(); 8 | 9 | return
{JSON.stringify(state, null, 2)}
; 10 | }; 11 | 12 | storiesOf('Sensors/useOrientation', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/usePageLeave.story.tsx: -------------------------------------------------------------------------------- 1 | import { action } from '@storybook/addon-actions'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import { usePageLeave } from '../src'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | usePageLeave(action('onPageLeave')); 9 | 10 | return ( 11 |
12 | Try leaving the page and see logs in Actions tab. 13 |
14 | ); 15 | }; 16 | 17 | storiesOf('Sensors/usePageLeave', module) 18 | .add('Docs', () => ) 19 | .add('Default', () => ); 20 | -------------------------------------------------------------------------------- /stories/usePermission.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { usePermission } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const state = usePermission({ name: 'microphone' }); 8 | 9 | return
{JSON.stringify(state, null, 2)}
; 10 | }; 11 | 12 | storiesOf('Side effects/usePermission', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/usePrevious.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { usePrevious } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [count, setCount] = React.useState(0); 8 | const prevCount = usePrevious(count); 9 | 10 | return ( 11 |
12 |

13 | Now: {count}, before: {String(prevCount)} 14 |

15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | storiesOf('State/usePrevious', module) 22 | .add('Docs', () => ) 23 | .add('Demo', () => ); 24 | -------------------------------------------------------------------------------- /stories/usePreviousDistinct.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { usePreviousDistinct, useCounter } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [count, { inc: relatedInc }] = useCounter(0); 8 | const [unrelatedCount, { inc }] = useCounter(0); 9 | const prevCount = usePreviousDistinct(count); 10 | 11 | return ( 12 |

13 | Now: {count}, before: {prevCount} 14 | 15 | Unrelated: {unrelatedCount} 16 | 17 |

18 | ); 19 | }; 20 | 21 | storiesOf('State/usePreviousDistinct', module) 22 | .add('Docs', () => ) 23 | .add('Demo', () => ); 24 | -------------------------------------------------------------------------------- /stories/useQueue.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useQueue } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const { add, remove, first, last, size } = useQueue(); 8 | return ( 9 |
10 |
    11 |
  • first: {first}
  • 12 |
  • last: {last}
  • 13 |
  • size: {size}
  • 14 |
15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | storiesOf('State/useQueue', module) 22 | .add('Docs', () => ) 23 | .add('Demo', () => ); 24 | -------------------------------------------------------------------------------- /stories/useRaf.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useRaf } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const frames = useRaf(5000, 1000); 8 | 9 | return
Elapsed: {frames}
; 10 | }; 11 | 12 | storiesOf('Animation/useRaf', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/useRafLoop.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useRafLoop, useUpdate } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [ticks, setTicks] = React.useState(0); 8 | const [lastCall, setLastCall] = React.useState(0); 9 | const update = useUpdate(); 10 | 11 | const [loopStop, loopStart, isActive] = useRafLoop((time) => { 12 | setTicks((ticks) => ticks + 1); 13 | setLastCall(time); 14 | }); 15 | 16 | return ( 17 |
18 |
RAF triggered: {ticks} (times)
19 |
Last high res timestamp: {lastCall}
20 |
21 | 28 |
29 | ); 30 | }; 31 | 32 | storiesOf('Side effects/useRafLoop', module) 33 | .add('Docs', () => ) 34 | .add('Demo', () => ); 35 | -------------------------------------------------------------------------------- /stories/useRafState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useRafState, useMount } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [state, setState] = useRafState({ x: 0, y: 0 }); 8 | 9 | useMount(() => { 10 | const onMouseMove = (event: MouseEvent) => { 11 | setState({ x: event.clientX, y: event.clientY }); 12 | }; 13 | const onTouchMove = (event: TouchEvent) => { 14 | setState({ x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY }); 15 | }; 16 | 17 | document.addEventListener('mousemove', onMouseMove); 18 | document.addEventListener('touchmove', onTouchMove); 19 | 20 | return () => { 21 | document.removeEventListener('mousemove', onMouseMove); 22 | document.removeEventListener('touchmove', onTouchMove); 23 | }; 24 | }); 25 | 26 | return
{JSON.stringify(state, null, 2)}
; 27 | }; 28 | 29 | storiesOf('State/useRafState', module) 30 | .add('Docs', () => ) 31 | .add('Demo', () => ); 32 | -------------------------------------------------------------------------------- /stories/useRendersCount.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useRendersCount } from '../src/useRendersCount'; 4 | import useUpdate from '../src/useUpdate'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | const update = useUpdate(); 9 | const rendersCount = useRendersCount(); 10 | 11 | return ( 12 |
13 | Renders count: {rendersCount} 14 |
15 | 16 |
17 | ); 18 | }; 19 | 20 | storiesOf('State/useRendersCount', module) 21 | .add('Docs', () => ) 22 | .add('Demo', () => ); 23 | -------------------------------------------------------------------------------- /stories/useScroll.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useScroll } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const scrollRef = React.useRef(null); 8 | const { x, y } = useScroll(scrollRef); 9 | 10 | return ( 11 | <> 12 |
x: {x}
13 |
y: {y}
14 |
22 |
Scroll me
23 |
24 | 25 | ); 26 | }; 27 | 28 | storiesOf('Sensors/useScroll', module) 29 | .add('Docs', () => ) 30 | .add('Demo', () => ); 31 | -------------------------------------------------------------------------------- /stories/useScrollbarWidth.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useScrollbarWidth } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const sbw = useScrollbarWidth(); 8 | 9 | return ( 10 |
11 | {sbw === undefined 12 | ? `DOM is not ready yet, SBW detection delayed` 13 | : `Browser's scrollbar width is ${sbw}px`} 14 |
15 | ); 16 | }; 17 | 18 | storiesOf('Sensors/useScrollbarWidth', module) 19 | .add('Docs', () => ) 20 | .add('Demo', () => ); 21 | -------------------------------------------------------------------------------- /stories/useScrolling.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useScrolling } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const scrollRef = React.useRef(null); 8 | const scrolling = useScrolling(scrollRef); 9 | 10 | return ( 11 | <> 12 | {
{scrolling ? 'Scrolling' : 'Not scrolling'}
} 13 |
14 |
22 |
Scroll me
23 |
24 | 25 | ); 26 | }; 27 | 28 | storiesOf('Sensors/useScrolling', module) 29 | .add('Docs', () => ) 30 | .add('Demo', () => ); 31 | -------------------------------------------------------------------------------- /stories/useSearchParam.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useSearchParam } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const edit = useSearchParam('edit'); 8 | 9 | return ( 10 |
11 |
edit: {edit || '🤷‍♂️'}
12 |
13 | 17 |
18 |
19 | 23 |
24 |
25 | 28 |
29 |
30 | ); 31 | }; 32 | 33 | storiesOf('Sensors/useSearchParam', module) 34 | .add('Docs', () => ) 35 | .add('Demo', () => ); 36 | -------------------------------------------------------------------------------- /stories/useSessionStorage.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useSessionStorage } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [value, setValue] = useSessionStorage('hello-key', 'foo'); 8 | 9 | return ( 10 |
11 |
Value: {value}
12 | 13 | 14 |
15 | ); 16 | }; 17 | 18 | storiesOf('Side effects/useSessionStorage', module) 19 | .add('Docs', () => ) 20 | .add('Demo', () => ); 21 | -------------------------------------------------------------------------------- /stories/useSet.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useSet } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [set, { add, has, remove, reset, clear, toggle }] = useSet(new Set(['hello'])); 8 | 9 | return ( 10 |
11 | 12 | 13 | 14 | 17 | 18 |
{JSON.stringify(Array.from(set), null, 2)}
19 |
20 | ); 21 | }; 22 | 23 | storiesOf('State/useSet', module) 24 | .add('Docs', () => ) 25 | .add('Demo', () => ); 26 | -------------------------------------------------------------------------------- /stories/useSetState.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useSetState } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [state, setState] = useSetState({}); 8 | 9 | return ( 10 |
11 |
{JSON.stringify(state, null, 2)}
12 | 13 | 14 | 22 |
23 | ); 24 | }; 25 | 26 | storiesOf('State/useSetState', module) 27 | .add('Docs', () => ) 28 | .add('Demo', () => ); 29 | -------------------------------------------------------------------------------- /stories/useSize.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useSize } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [sized, state] = useSize(({ width: currentWidth }) => ( 8 |
Size me up! ({currentWidth}px)
9 | )); 10 | 11 | return ( 12 |
13 |
{JSON.stringify(state, null, 2)}
14 | {sized} 15 |
16 | ); 17 | }; 18 | 19 | storiesOf('Sensors/useSize', module) 20 | .add('Docs', () => ) 21 | .add('Demo', () => ); 22 | -------------------------------------------------------------------------------- /stories/useSpeech.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useSpeech } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const voices = window.speechSynthesis.getVoices(); 8 | const state = useSpeech('Hello world!', { rate: 0.8, pitch: 0.5, voice: voices[0] }); 9 | 10 | return
{JSON.stringify(state, null, 2)}
; 11 | }; 12 | 13 | storiesOf('UI/useSpeech', module) 14 | .add('Docs', () => ) 15 | .add('Demo', () => ); 16 | -------------------------------------------------------------------------------- /stories/useSpring.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import useSpring from '../src/useSpring'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [target, setTarget] = (React as any).useState(50); 8 | const value = useSpring(target); 9 | 10 | return ( 11 |
12 | {value} 13 |
14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | storiesOf('Animation/useSpring', module) 21 | .add('Docs', () => ) 22 | .add('Demo', () => ); 23 | -------------------------------------------------------------------------------- /stories/useStartTyping.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useStartTyping } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const input = React.useRef(null); 8 | useStartTyping(() => { 9 | if (input.current) { 10 | input.current.focus(); 11 | } 12 | }); 13 | 14 | return ( 15 |
16 |

Start typing, and below field will get focused.

17 | 18 | 19 |
20 |
21 | 22 |

Try focusing below elements and see what happens.

23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 |
Editable DIV
33 |
34 | ); 35 | }; 36 | 37 | storiesOf('Sensors/useStartTyping', module) 38 | .add('Docs', () => ) 39 | .add('Demo', () => ); 40 | -------------------------------------------------------------------------------- /stories/useStateValidator.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import useStateValidator from '../src/useStateValidator'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const DemoStateValidator = (s) => 7 | [s === '' ? undefined : (s * 1) % 2 === 0] as [boolean | undefined]; 8 | const Demo = () => { 9 | const [state, setState] = React.useState(0); 10 | const [[isValid]] = useStateValidator(state, DemoStateValidator); 11 | 12 | return ( 13 |
14 |
Below field is valid only if number is even
15 | ) => { 21 | setState(ev.target.value as unknown as number); 22 | }} 23 | /> 24 | {isValid !== undefined && {isValid ? 'Valid!' : 'Invalid'}} 25 |
26 | ); 27 | }; 28 | 29 | storiesOf('State/useStateValidator', module) 30 | .add('Docs', () => ) 31 | .add('Demo', () => ); 32 | -------------------------------------------------------------------------------- /stories/useTimeout.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useTimeout } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | function TestComponent(props: { ms?: number } = {}) { 7 | const ms = props.ms || 5000; 8 | const [isReady, cancel] = useTimeout(ms); 9 | 10 | return ( 11 |
12 | {isReady() ? "I'm reloaded after timeout" : `I will be reloaded after ${ms / 1000}s`} 13 | {isReady() === false ? : ''} 14 |
15 | ); 16 | } 17 | 18 | const Demo = () => { 19 | return ( 20 |
21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | storiesOf('Animation/useTimeout', module) 28 | .add('Docs', () => ) 29 | .add('Demo', () => ); 30 | -------------------------------------------------------------------------------- /stories/useTitle.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useTitle } from '../src'; 4 | import NewTabStory from './util/NewTabStory'; 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | useTitle('Hello world!'); 9 | 10 | return Title should be "Hello world!"; 11 | }; 12 | 13 | storiesOf('Side effects/useTitle', module) 14 | .add('Docs', () => ) 15 | .add('Demo', () => ); 16 | -------------------------------------------------------------------------------- /stories/useToggle.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useToggle } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [on, toggle] = useToggle(true); 8 | 9 | return ( 10 |
11 |
{on ? 'ON' : 'OFF'}
12 | 13 | 14 | 15 |
16 | ); 17 | }; 18 | 19 | storiesOf('State/useToggle', module) 20 | .add('Docs', () => ) 21 | .add('Demo', () => ); 22 | -------------------------------------------------------------------------------- /stories/useTween.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useTween } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const t = useTween(); 8 | 9 | return
Tween: {t}
; 10 | }; 11 | 12 | storiesOf('Animation/useTween', module) 13 | .add('Docs', () => ) 14 | .add('Demo', () => ); 15 | -------------------------------------------------------------------------------- /stories/useUnmount.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useUnmount } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | useUnmount(() => alert('UNMOUNTED')); 8 | 9 | return ( 10 |
11 | useUnmount() hook can be used to perform side-effects when component unmounts. 12 | This component will alert you when it is un-mounted. 13 |
14 | ); 15 | }; 16 | 17 | storiesOf('Lifecycle/useUnmount', module) 18 | .add('Docs', () => ) 19 | .add('Demo', () => ); 20 | -------------------------------------------------------------------------------- /stories/useUpdate.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useUpdate } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const update = useUpdate(); 8 | return ( 9 | <> 10 |
Time: {Date.now()}
11 | 12 | 13 | ); 14 | }; 15 | 16 | storiesOf('Animation/useUpdate', module) 17 | .add('Docs', () => ) 18 | .add('Demo', () => ); 19 | -------------------------------------------------------------------------------- /stories/useUpdateEffect.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useUpdateEffect } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [count, setCount] = React.useState(0); 8 | const [didUpdate, setDidUpdate] = React.useState(false); 9 | 10 | useUpdateEffect(() => { 11 | setDidUpdate(true); 12 | }, [count]); 13 | 14 | return ( 15 |
16 | 17 |

Updated: {didUpdate}

18 |
19 | ); 20 | }; 21 | 22 | storiesOf('Lifecycle/useUpdateEffect', module) 23 | .add('Docs', () => ) 24 | .add('Demo', () => ); 25 | -------------------------------------------------------------------------------- /stories/useVibrate.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useVibrate, useToggle } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const [vibrating, toggleVibrating] = useToggle(false); 8 | 9 | useVibrate(vibrating, [300, 100, 200, 100, 1000, 300]); 10 | 11 | return ( 12 |
13 | 14 |
15 | ); 16 | }; 17 | 18 | storiesOf('UI/useVibrate', module) 19 | .add('Docs', () => ) 20 | .add('Demo', () => ); 21 | -------------------------------------------------------------------------------- /stories/useWindowScroll.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useWindowScroll } from '../src'; 4 | import ShowDocs from './util/ShowDocs'; 5 | 6 | const Demo = () => { 7 | const { x, y } = useWindowScroll(); 8 | 9 | return ( 10 |
15 |
21 |
x: {x}
22 |
y: {y}
23 |
24 |
25 | ); 26 | }; 27 | 28 | storiesOf('Sensors/useWindowScroll', module) 29 | .add('Docs', () => ) 30 | .add('Demo', () => ); 31 | -------------------------------------------------------------------------------- /stories/useWindowSize.story.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { useWindowSize } from '../src'; 4 | import { action } from '@storybook/addon-actions'; // Import addon-actions 5 | import ShowDocs from './util/ShowDocs'; 6 | 7 | const Demo = () => { 8 | const { width, height } = useWindowSize({ 9 | // Log the resize event to the Storybook actions panel 10 | onChange: action('window resize'), 11 | }); 12 | 13 | return ( 14 |
15 |
width: {width}
16 |
height: {height}
17 |
18 | ); 19 | }; 20 | 21 | storiesOf('Sensors/useWindowSize', module) 22 | .add('Docs', () => ) 23 | .add('Demo', () => ); 24 | -------------------------------------------------------------------------------- /stories/util/CenterStory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const CenterStory = ({ children }) => ( 4 |
12 |
{children}
13 |
14 | ); 15 | -------------------------------------------------------------------------------- /stories/util/ConsoleStory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const ConsoleStory = ({ message = 'Open the developer console to see logs' }) =>

{message}

; 4 | 5 | export default ConsoleStory; 6 | -------------------------------------------------------------------------------- /stories/util/NewTabStory.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const NewTabStory = ({ children }) => { 4 | if (window.location === window.parent.location) { 5 | return children; 6 | } 7 | 8 | return ( 9 |

10 | This story should be{' '} 11 | 12 | opened in a new tab 13 | 14 | . 15 |

16 | ); 17 | }; 18 | 19 | export default NewTabStory; 20 | -------------------------------------------------------------------------------- /tests/misc/hookState.test.ts: -------------------------------------------------------------------------------- 1 | import { resolveHookState } from '../../src/misc/hookState'; 2 | 3 | describe('resolveHookState', () => { 4 | it('should defined', () => { 5 | expect(resolveHookState).toBeDefined(); 6 | }); 7 | 8 | it(`should return value as is if it's not a function`, () => { 9 | expect(resolveHookState(1)).toBe(1); 10 | expect(resolveHookState('HI!')).toBe('HI!'); 11 | expect(resolveHookState(undefined)).toBe(undefined); 12 | }); 13 | 14 | it('should call passed function', () => { 15 | const spy = jest.fn(); 16 | resolveHookState(spy); 17 | expect(spy).toHaveBeenCalled(); 18 | }); 19 | 20 | it('should pass 2nd parameter to function if it awaited', () => { 21 | const spy = jest.fn((n: number) => n); 22 | resolveHookState(spy, 123); 23 | expect(spy).toHaveBeenCalled(); 24 | expect(spy.mock.calls[0][0]).toBe(123); 25 | }); 26 | 27 | it('should not pass 2nd parameter to function if it not awaited', () => { 28 | const spy = jest.fn(() => {}); 29 | /* @ts-expect-error */ 30 | resolveHookState(spy, 123); 31 | expect(spy).toHaveBeenCalled(); 32 | expect(spy.mock.calls[0].length).toBe(0); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/setupTests.ts: -------------------------------------------------------------------------------- 1 | import 'jest-localstorage-mock'; 2 | import { isBrowser } from '../src/misc/util'; 3 | 4 | if (isBrowser) { 5 | (window as any).ResizeObserver = class ResizeObserver { 6 | observe() {} 7 | 8 | disconnect() {} 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /tests/useBoolean.test.ts: -------------------------------------------------------------------------------- 1 | import useBoolean from '../src/useBoolean'; 2 | import useToggle from '../src/useToggle'; 3 | 4 | it('should be an alias for useToggle ', () => { 5 | expect(useBoolean).toBe(useToggle); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/useEffectOnce.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useEffectOnce } from '../src'; 3 | 4 | const mockEffectCleanup = jest.fn(); 5 | const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup); 6 | 7 | it('should run provided effect only once', () => { 8 | const { rerender } = renderHook(() => useEffectOnce(mockEffectCallback)); 9 | expect(mockEffectCallback).toHaveBeenCalledTimes(1); 10 | 11 | rerender(); 12 | expect(mockEffectCallback).toHaveBeenCalledTimes(1); 13 | }); 14 | 15 | it('should run clean-up provided on unmount', () => { 16 | const { unmount } = renderHook(() => useEffectOnce(mockEffectCallback)); 17 | expect(mockEffectCleanup).not.toHaveBeenCalled(); 18 | 19 | unmount(); 20 | expect(mockEffectCleanup).toHaveBeenCalledTimes(1); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/useError.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { useError } from '../src'; 3 | 4 | const setup = () => renderHook(() => useError()); 5 | 6 | beforeEach(() => { 7 | jest.spyOn(console, 'error').mockImplementation(() => {}); 8 | }); 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks(); 12 | }); 13 | 14 | it('should throw an error on error dispatch', () => { 15 | const errorStr = 'some_error'; 16 | 17 | try { 18 | const { result } = setup(); 19 | 20 | act(() => { 21 | result.current(new Error(errorStr)); 22 | }); 23 | } catch (err) { 24 | expect(err.message).toEqual(errorStr); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /tests/useFirstMountState.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useFirstMountState } from '../src'; 3 | 4 | describe('useFirstMountState', () => { 5 | it('should be defined', () => { 6 | expect(useFirstMountState).toBeDefined(); 7 | }); 8 | 9 | it('should return boolean', () => { 10 | expect(renderHook(() => useFirstMountState()).result.current).toEqual(expect.any(Boolean)); 11 | }); 12 | 13 | it('should return true on first render and false on all others', () => { 14 | const hook = renderHook(() => useFirstMountState()); 15 | 16 | expect(hook.result.current).toBe(true); 17 | hook.rerender(); 18 | expect(hook.result.current).toBe(false); 19 | hook.rerender(); 20 | expect(hook.result.current).toBe(false); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/useLatest.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import useLatest from '../src/useLatest'; 3 | 4 | const setUp = () => renderHook(({ state }) => useLatest(state), { initialProps: { state: 0 } }); 5 | 6 | it('should return a ref with the latest value on initial render', () => { 7 | const { result } = setUp(); 8 | 9 | expect(result.current).toEqual({ current: 0 }); 10 | }); 11 | 12 | it('should always return a ref with the latest value after each update', () => { 13 | const { result, rerender } = setUp(); 14 | 15 | rerender({ state: 2 }); 16 | expect(result.current).toEqual({ current: 2 }); 17 | 18 | rerender({ state: 4 }); 19 | expect(result.current).toEqual({ current: 4 }); 20 | 21 | rerender({ state: 6 }); 22 | expect(result.current).toEqual({ current: 6 }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/useMount.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useMount } from '../src'; 3 | 4 | const mockCallback = jest.fn(); 5 | 6 | afterEach(() => { 7 | jest.resetAllMocks(); 8 | }); 9 | 10 | it('should call provided callback on mount', () => { 11 | renderHook(() => useMount(mockCallback)); 12 | 13 | expect(mockCallback).toHaveBeenCalledTimes(1); 14 | }); 15 | 16 | it('should not call provided callback on unmount', () => { 17 | const { unmount } = renderHook(() => useMount(mockCallback)); 18 | expect(mockCallback).toHaveBeenCalledTimes(1); 19 | 20 | unmount(); 21 | 22 | expect(mockCallback).toHaveBeenCalledTimes(1); 23 | }); 24 | 25 | it('should not call provided callback on rerender', () => { 26 | const { rerender } = renderHook(() => useMount(mockCallback)); 27 | expect(mockCallback).toHaveBeenCalledTimes(1); 28 | 29 | rerender(); 30 | 31 | expect(mockCallback).toHaveBeenCalledTimes(1); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/useMountedState.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import useMountedState from '../src/useMountedState'; 3 | 4 | describe('useMountedState', () => { 5 | it('should be defined', () => { 6 | expect(useMountedState).toBeDefined(); 7 | }); 8 | 9 | it('should return a function', () => { 10 | const hook = renderHook(() => useMountedState(), { initialProps: false }); 11 | 12 | expect(typeof hook.result.current).toEqual('function'); 13 | }); 14 | 15 | it('should return true if component is mounted', () => { 16 | const hook = renderHook(() => useMountedState(), { initialProps: false }); 17 | 18 | expect(hook.result.current()).toBeTruthy(); 19 | }); 20 | 21 | it('should return false if component is unmounted', () => { 22 | const hook = renderHook(() => useMountedState(), { initialProps: false }); 23 | 24 | hook.unmount(); 25 | 26 | expect(hook.result.current()).toBeFalsy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/useNetworkState.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useNetworkState } from '../src'; 3 | 4 | //ToDo: improve tests 5 | describe(`useNetworkState`, () => { 6 | it('should be defined', () => { 7 | expect(useNetworkState).toBeDefined(); 8 | }); 9 | 10 | it('should return an object of certain structure', () => { 11 | const hook = renderHook(() => useNetworkState(), { initialProps: false }); 12 | 13 | expect(typeof hook.result.current).toEqual('object'); 14 | expect(Object.keys(hook.result.current)).toEqual([ 15 | 'online', 16 | 'previous', 17 | 'since', 18 | 'downlink', 19 | 'downlinkMax', 20 | 'effectiveType', 21 | 'rtt', 22 | 'saveData', 23 | 'type', 24 | ]); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/useNumber.test.ts: -------------------------------------------------------------------------------- 1 | import useCounter from '../src/useCounter'; 2 | import useNumber from '../src/useNumber'; 3 | 4 | it('should be an alias for useCounter', () => { 5 | expect(useNumber).toBe(useCounter); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/usePrevious.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import usePrevious from '../src/usePrevious'; 3 | 4 | const setUp = () => renderHook(({ state }) => usePrevious(state), { initialProps: { state: 0 } }); 5 | 6 | it('should return undefined on initial render', () => { 7 | const { result } = setUp(); 8 | 9 | expect(result.current).toBeUndefined(); 10 | }); 11 | 12 | it('should always return previous state after each update', () => { 13 | const { result, rerender } = setUp(); 14 | 15 | rerender({ state: 2 }); 16 | expect(result.current).toBe(0); 17 | 18 | rerender({ state: 4 }); 19 | expect(result.current).toBe(2); 20 | 21 | rerender({ state: 6 }); 22 | expect(result.current).toBe(4); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/useQueue.test.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from '@testing-library/react-hooks'; 2 | import useQueue from '../src/useQueue'; 3 | 4 | const setUp = (initialQueue?: any[]) => renderHook(() => useQueue(initialQueue)); 5 | 6 | it('takes initial state', () => { 7 | const { result } = setUp([1, 2, 3]); 8 | const { first, last, size } = result.current; 9 | expect(first).toEqual(1); 10 | expect(last).toEqual(3); 11 | expect(size).toEqual(3); 12 | }); 13 | 14 | it('appends new member', () => { 15 | const { result } = setUp([1, 2]); 16 | act(() => { 17 | result.current.add(3); 18 | }); 19 | const { first, last, size } = result.current; 20 | expect(first).toEqual(1); 21 | expect(last).toEqual(3); 22 | expect(size).toEqual(3); 23 | }); 24 | 25 | it('pops oldest member', () => { 26 | const { result } = setUp([1, 2]); 27 | act(() => { 28 | result.current.remove(); 29 | }); 30 | const { first, size } = result.current; 31 | expect(first).toEqual(2); 32 | expect(size).toEqual(1); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/useRendersCount.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useRendersCount } from '../src'; 3 | 4 | describe('useRendersCount', () => { 5 | it('should be defined', () => { 6 | expect(useRendersCount).toBeDefined(); 7 | }); 8 | 9 | it('should return number', () => { 10 | expect(renderHook(() => useRendersCount()).result.current).toEqual(expect.any(Number)); 11 | }); 12 | 13 | it('should return actual number of renders', () => { 14 | const hook = renderHook(() => useRendersCount()); 15 | 16 | expect(hook.result.current).toBe(1); 17 | hook.rerender(); 18 | expect(hook.result.current).toBe(2); 19 | hook.rerender(); 20 | expect(hook.result.current).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/useTitle.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import useTitle from '../src/useTitle'; 3 | 4 | describe('useTitle', () => { 5 | it('should be defined', () => { 6 | expect(useTitle).toBeDefined(); 7 | }); 8 | 9 | it('should update document title', () => { 10 | const hook = renderHook((props) => useTitle(props), { initialProps: 'My page title' }); 11 | 12 | expect(document.title).toBe('My page title'); 13 | hook.rerender('My other page title'); 14 | expect(document.title).toBe('My other page title'); 15 | }); 16 | 17 | it('should restore document title on unmount', () => { 18 | renderHook((props) => useTitle(props), { initialProps: 'Old Title' }); 19 | expect(document.title).toBe('Old Title'); 20 | 21 | const hook = renderHook((props) => useTitle(props.title, { restoreOnUnmount: props.restore }), { 22 | initialProps: { title: 'New Title', restore: true }, 23 | }); 24 | expect(document.title).toBe('New Title'); 25 | hook.unmount(); 26 | expect(document.title).toBe('Old Title'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/useUpdateEffect.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useUpdateEffect } from '../src'; 3 | 4 | it('should run effect on update', () => { 5 | const effect = jest.fn(); 6 | 7 | const { rerender } = renderHook(() => useUpdateEffect(effect)); 8 | expect(effect).not.toHaveBeenCalled(); 9 | 10 | rerender(); 11 | expect(effect).toHaveBeenCalledTimes(1); 12 | }); 13 | 14 | it('should run cleanup on unmount', () => { 15 | const cleanup = jest.fn(); 16 | const effect = jest.fn().mockReturnValue(cleanup); 17 | const hook = renderHook(() => useUpdateEffect(effect)); 18 | 19 | hook.rerender(); 20 | hook.unmount(); 21 | 22 | expect(cleanup).toHaveBeenCalledTimes(1); 23 | }); 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "declaration": true, 8 | "pretty": true, 9 | "rootDir": "src", 10 | "sourceMap": false, 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noImplicitAny": false, 17 | "noFallthroughCasesInSwitch": true, 18 | "outDir": "lib", 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ], 23 | "importHelpers": true 24 | }, 25 | "exclude": [ 26 | "node_modules", 27 | "lib", 28 | "esm", 29 | "tests", 30 | "stories", 31 | "jest.config.ts", 32 | "jest.config.*.ts" 33 | ] 34 | } 35 | --------------------------------------------------------------------------------