{"id":19460432,"url":"https://github.com/haensl/hooks","last_synced_at":"2026-02-27T01:04:38.819Z","repository":{"id":42451893,"uuid":"272522677","full_name":"haensl/hooks","owner":"haensl","description":"Assorted React hooks.","archived":false,"fork":false,"pushed_at":"2024-10-18T23:59:57.000Z","size":1714,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-14T07:51:33.619Z","etag":null,"topics":["debounce","hooks","react","react-components","react-hooks","reactjs","requestanimationframe"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@haensl/hooks","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haensl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["haensl"]}},"created_at":"2020-06-15T19:10:29.000Z","updated_at":"2024-10-18T23:54:17.000Z","dependencies_parsed_at":"2023-02-09T03:46:09.510Z","dependency_job_id":"23209ac1-0347-4ba3-9dd6-916d0cdee71a","html_url":"https://github.com/haensl/hooks","commit_stats":{"total_commits":84,"total_committers":3,"mean_commits":28.0,"dds":0.5,"last_synced_commit":"c709c7445ec002f3f8c61ff4afb5478b858f8afd"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/haensl/hooks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haensl%2Fhooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haensl%2Fhooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haensl%2Fhooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haensl%2Fhooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haensl","download_url":"https://codeload.github.com/haensl/hooks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haensl%2Fhooks/sbom","scorecard":{"id":452609,"data":{"date":"2025-08-11","repo":{"name":"github.com/haensl/hooks","commit":"89e7325fb3de41c912af35adc7d632871436e12c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/9 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v1.14.2 not signed: https://api.github.com/repos/haensl/hooks/releases/180773000","Warn: release artifact v1.14.1 not signed: https://api.github.com/repos/haensl/hooks/releases/161733529","Warn: release artifact v1.14.0 not signed: https://api.github.com/repos/haensl/hooks/releases/145347876","Warn: release artifact v1.13.1 not signed: https://api.github.com/repos/haensl/hooks/releases/122530159","Warn: release artifact v1.13.0 not signed: https://api.github.com/repos/haensl/hooks/releases/110863261","Warn: release artifact v1.14.2 does not have provenance: https://api.github.com/repos/haensl/hooks/releases/180773000","Warn: release artifact v1.14.1 does not have provenance: https://api.github.com/repos/haensl/hooks/releases/161733529","Warn: release artifact v1.14.0 does not have provenance: https://api.github.com/repos/haensl/hooks/releases/145347876","Warn: release artifact v1.13.1 does not have provenance: https://api.github.com/repos/haensl/hooks/releases/122530159","Warn: release artifact v1.13.0 does not have provenance: https://api.github.com/repos/haensl/hooks/releases/110863261"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"12 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T08:35:13.580Z","repository_id":42451893,"created_at":"2025-08-19T08:35:13.580Z","updated_at":"2025-08-19T08:35:13.580Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29879920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T23:51:21.483Z","status":"ssl_error","status_checked_at":"2026-02-26T23:50:46.793Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debounce","hooks","react","react-components","react-hooks","reactjs","requestanimationframe"],"created_at":"2024-11-10T17:37:08.148Z","updated_at":"2026-02-27T01:04:38.801Z","avatar_url":"https://github.com/haensl.png","language":"JavaScript","readme":"# @haensl/react-hooks\n\nAssorted React hooks.\n\n[![NPM](https://nodei.co/npm/@haensl%2Freact-hooks.png?downloads=true)](https://nodei.co/npm/@haensl%2Freact-hooks/)\n\n[![npm version](https://badge.fury.io/js/@haensl%2Freact-hooks.svg)](http://badge.fury.io/js/@haensl%2Freact-hooks)\n[![CircleCI](https://circleci.com/gh/haensl/hooks.svg?style=svg)](https://circleci.com/gh/haensl/hooks)\n\n## Installation\u003ca name=\"installation\"\u003e\u003c/a\u003e\n\n### Via `npm`\n\n```bash\n$ npm install -S @haensl/react-hooks\n```\n\n### Via `yarn`\n\n```bash\n$ yarn add @haensl/react-hooks\n```\n\n## Usage\n\n1. [Install @haensl/react-hooks](#installation)\n\n2. Use hooks in your components:\n\n```javascript\nimport { useDebounce } from '@haensl/react-hooks';\n\nconst DebouncedButton = () =\u003e {\n  const handler = useDebounce(() =\u003e {\n    console.log('click');\n  }, 50);\n\n  return (\n    \u003cbutton\n      onClick={ handler }\n    \u003eclick me\u003c/button\u003e\n  );\n};\n```\n\n## Available hooks\n\n* [`useAnimationFrame`](#useAnimationFrame): animate a function.\n* [`useBoundingClientRect`](#useBoundingClientRect): keep track of a container's [DOM rectangle](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).\n* [`useClassNames`](#useClassNames): compile CSS class names from state.\n* [`useDebounce`](#useDeboune): debounce a function.\n* [`useInterval`](#useInterval): use an interval.\n* [`useIsMounted`](#useIsMounted): keep track of whether or not a component is mounted.\n* [`useIsomorphicLayoutEffect`](#useIsomorphicLayoutEffect): use this instead of [`useLayoutEffect`](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) if your app uses serverside rendering (SSR).\n* [`useIsScrolling`](#useIsScrolling): keep track of whether or not the user is scrolling.\n* [`useLang`](#useLang): use the browser's language setting.\n* [`useOnScroll`](#useOnScroll): subscribe to scroll events.\n* [`usePrevious`](#usePrevious): keep track of a variable's previous value.\n* [`useTimeout`](#useTimeout): use a timeout.\n* [`useWindowScroll`](#useWindowScroll): keep track of the `window`'s scroll position.\n* [`useWindowSize`](#useWindowSize): keep track of the `window`'s size.\n\n### `useAnimationFrame(fn)` \u003ca name=\"useAnimationFrame\"\u003e\u003c/a\u003e\n\nUses [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to animate a function `fn`. The callback is passed one single argument, the time delta in milliseconds that has passed between this and the last call. Please check the [example](#useAnimationFrameExample) below as well as the [Codepen example](https://codepen.io/haensl/pen/GRoNGNB).\n\n##### Example\u003ca name=\"useAnimationFrameExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useState, useEffect } from 'react';\nimport { useAnimationFrame } from '@haensl/react-hooks';\n\nconst AnimatedTimer = () =\u003e {\n  const [seconds, setSeconds] = useState(0);\n  const [elapsed, setElapsed] = useState(0);\n\n  useAnimationFrame((dt) =\u003e {\n    setElapsed(elapsed + dt);\n  });\n\n  useEffect(() =\u003e {\n    if (elapsed \u003e= 1000) {\n      setSeconds(seconds + 1);\n      setElapsed(elapsed - 1000);\n    }\n  }, [elapsed]);\n\n  return (\n    \u003cspan\u003e{ seconds }\u003c/span\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/GRoNGNB)\n\n### `useBoundingClientRect(ref, [debounceMs = 25])` \u003ca name=\"useBoundingClientRect\"\u003e\u003c/a\u003e\nReturns the DOM rectangle _(initially `null`)_ as returned by [`getBoundingClientRect`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) for the given container `ref`. Changes are debounced by 25 milliseconds by default. Customize the debounce interval via the optional `debounceMs` argument. Please check out the [example below](#useBoundingClientRectExample) as well as the [Codepen example](https://codepen.io/haensl/pen/YzwxqOq).\n\n#### Example\n```javascript\nimport React, { useRef } from 'react';\nimport { useBoundingClientRect } from '@haensl/react-hooks';\n\nconst RectTracker = () =\u003e {\n  const ref = useRef();\n  const containerRect = useBoundingClientRect(ref);\n\n  if (!containerRect) {\n    return (\n      \u003cdiv ref={ ref }\u003e\n        \u003cspan\u003eno container rect\u003c/span\u003e\n      \u003c/div\u003e\n    );\n  }\n\n  return (\n    \u003cdiv ref={ ref }\u003e\n      \u003cspan\u003eContainer rect:\u003c/span\u003e\n      \u003cspan\u003eWidth: {containerRect.width}\u003c/span\u003e\n      \u003cspan\u003eHeight: {containerRect.height}\u003c/span\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/YzwxqOq)\n\n### `useClassNames(states, [separator = ' '])` \u003ca name=\"useClassNames\"\u003e\u003c/a\u003e\n\nCompiles a `states` object into a CSS class name string. By default all keys in `states` are joined by a space (`' '`) but you can supply a custom `separator` to cater to the needs of your CSS module naming methodology of choice. Please check the [examples below](#useClassNamesExample).\n\n#### Example\u003ca name=\"useClassNamesExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useState } from 'react';\nimport { useClassNames } from '@haensl/react-hooks';\n\nconst MyComponent = () =\u003e {\n  const [stateA, setStateA] = useState(false);\n  const className = useClassNames({\n    MyComponent: true, // always have MyComponent in class name\n    MyComponent--stateA: stateA // add MyComponent--stateA when stateA is true\n  });\n\n  // className will be 'MyComponent' or 'MyComponent MyComponent--stateA'\n\n  return (\n    \u003cdiv className={ className }\u003e\n      {\n        // render content\n      }\n    \u003c/div\u003e\n  );\n};\n```\n\n#### Example: custom separator\u003ca name=\"useClassNamesExampleSeparator\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useState } from 'react';\nimport { useClassNames } from '@haensl/react-hooks';\n\nconst MyComponent = () =\u003e {\n  const [stateA, setStateA] = useState(false);\n  const className = useClassNames(\n    {\n      MyComponent: true, // always have MyComponent in class name\n      stateA // add --stateA when stateA is true\n    },\n    '--'\n  );\n\n  // className will either be 'MyComponent' or 'MyComponent--stateA'\n\n  return (\n    \u003cdiv className={ className }\u003e\n      {\n        // render content\n      }\n    \u003c/div\u003e\n  );\n};\n```\n\n### `useDebounce(fn, debounceMs)` \u003ca name=\"useDebounce\"\u003e\u003c/a\u003e\n\nUses [memoization](https://reactjs.org/docs/hooks-reference.html#usememo) to debounce `fn` by `debounceMs` milliseconds. Please check the [example below](#useDebounceExample) as well as the [Codepen example](https://codepen.io/haensl/pen/eYJBKEZ).\n\n##### Example\u003ca name=\"useDebounceExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React from 'react';\nimport { useDebounce } from '@haensl/react-hooks';\n\nconst DebouncedButton = () =\u003e {\n  const handler = useDebounce(() =\u003e {\n    console.log('click');\n  }, 50); // handler only fires when there were no calls for 50ms.\n\n  return (\n    \u003cbutton\n      onClick={ handler }\n    \u003eclick me\u003c/button\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/eYJBKEZ)\n\n### `useInterval(fn, intervalMs)` \u003ca name=\"useInterval\"\u003e\u003c/a\u003e\n\nCalls a `fn` repeatedly every `intervalMs` milliseconds.\n\n##### Example\n\n```javascript\nimport React, { useState, useCallback } from 'react';\nimport { useInterval } from '@haensl/react-hooks';\n\nconst MyAnimation = () =\u003e {\n  const [frame, setFrame] = useState(0);\n\n  // Update frame every 100 milliseconds\n  useInterval(() =\u003e {\n    setFrame((frame) =\u003e frame + 1);\n  }, 100);\n\n  return (\n    \u003cdiv\u003e{ frame }\u003c/div\u003e\n  );\n};\n```\n\n\n### `useIsMounted()` \u003ca name=\"useIsMounted\"\u003e\u003c/a\u003e\n\nReturns a `function` to check whether or not the component invoking the hook is mounted.\n\n##### Example\u003ca name=\"useIsMountedExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useEffect } from 'react';\nimport { useIsMounted } from '@haensl/react-hooks';\nimport api from 'somewhere';\n\nconst MyComponent = () =\u003e {\n  const isMounted = useIsMounted();\n  // load some data from the backend\n  useEffect(() =\u003e {\n    api.fetchData()\n      .then((data) =\u003e {\n        if (isMounted()) {\n          // use data only if component is still mounted\n        }\n      });\n  }, []);\n}\n```\n\n### `useIsomorphicLayoutEffect(fn, deps)` \u003ca name=\"useIsomorphicLayoutEffect\"\u003e\u003c/a\u003e\n\nThis hooks resolves the common React warning when using `useLayoutEffect` in a serverside environment:\n\n*Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer’s output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.*\n\n`useIsomorphicLayoutEffect` resolves to [`useLayoutEffect`](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) on the client and [`useEffect`](https://reactjs.org/docs/hooks-reference.html#useeffect) on the server. Use this hook instead of `useLayoutEffect` if your app uses serverside rendering (SSR).\n\n##### Example\u003ca name=\"useIsomorphicLayoutEffectExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useRef } from 'react';\nimport { useIsomorphicLayoutEffect } from '@haensl/react-hooks';\n\nconst MyComponent = () =\u003e {\n  const ref = useRef();\n\n  // prevents serverside rendering warning\n  useIsomorphicLayoutEffect(() =\u003e {\n    if (ref.current) {\n      // do stuff with ref\n    }\n  }, [ref]);\n\n  return (\n    \u003cdiv ref={ ref }\u003e\n      // ...\n    \u003c/div\u003e\n  )\n}\n```\n\n### `useIsScrolling([el = window, scrollEndMs = 100])` \u003ca name=\"useIsScrolling\"\u003e\u003c/a\u003e\n\nReturns a `boolean` indicating whether or not the user is scrolling. You can subscribe to a specific element via the first argument, `el` _(default: `window`)_. End of scrolling is determined by no incoming scroll events for `scrollEndMs` milliseconds _(default: `100`)_. Please check the [example blow](#useIsScrollingExample) as well as the [Codepen example](https://codepen.io/haensl/pen/qBbqeWz)\n\n##### Example\u003ca name=\"useIsScrollingExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React from 'react';\nimport { useIsScrolling } from '@haensl/react-hooks';\n\nconst UserScrollTracker = () =\u003e {\n  const isScrolling = useIsScrolling();\n\n  return (\n    \u003cspan\u003eThe user is currently { isScrolling ? '' : 'not' } scrolling\u003c/span\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/qBbqeWz)\n\n### `useLang({ defaultLang = 'en' }) =\u003e string` \u003ca name=\"useLang\"\u003e\u003c/a\u003e\n\nReturns the user's language setting from [`navigator.language`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language). Use the `defaultLang` of the options parameter to set a default language. _(default: `'en`)_.\n\n##### Example \u003ca name=\"useLangExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React from 'react';\nimport { useLang } from '@haensl/react-hooks';\n\nconst MyComponent = () =\u003e {\n  const lang = useLang();\n\n  return (\n    \u003cspan\u003eThe user's preferred language is { lang }.\u003c/span\u003e\n  );\n};\n```\n\n### `useOnScroll(fn, [el = window])` \u003ca name=\"useOnScroll\"\u003e\u003c/a\u003e\n\nSubscribes to [`scroll`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event) events on the given element `el` _(default: `window`)_. The callback function `fn` is passed the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event). Please check the [example below](#useOnScrollExample) as well as the [Codepen example](https://codepen.io/haensl/pen/wvMoLJK).\n\n##### Example\n\n```javascript\nimport React, { useState } from 'react';\nimport { useOnScroll } from '@haensl/react-hooks';\n\nconst WindowScrollTracker = () =\u003e {\n  const [windowScroll, setWindowScroll] = useState(0);\n\n  useOnScroll(() =\u003e {\n    setWindowScroll(window.scrollY);\n  });\n\n  return (\n    \u003cdiv className=\"WindowScrollTracker\"\u003e\n      \u003cspan\u003eWindow has scrolled down\u003c/span\u003e\n      \u003cspan\u003e{ windowScroll }px\u003c/span\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/wvMoLJK)\n\n### `usePrevious(value)` \u003ca name=\"usePrevious\"\u003e\u003c/a\u003e\n\nKeeps track of changes to a value, storing it's _previous_ state.\n\n##### Example\n\n```javascript\nimport { useEffect, useState } from 'react';\nimport { usePrevious, useWindowScroll } from '@haensl/react-hooks';\n\nconst ScrollDirectionTracker = () =\u003e {\n  const scrollPosition = useWindowScroll();\n  const previousScrollPosition = usePrevious(scrollPosition);\n  const [scrollDirection, setScrollDirection] = useState('down');\n\n  useEffect(() =\u003e {\n    if (previousScrollPosition.y \u003c scrollPosition.y) {\n      setScrollDirection('down');\n    } else if (previousScrollPosition.y \u003e scrollPosition.y) {\n      setScrollDirection('up');\n    }\n  }, [scrollPosition, previousScrollPosition]);\n\n  return (\n    \u003cdiv className=\"ScrollDirectionTracker\"\u003e\n      \u003cspan\u003eUser is scrolling\u003c/span\u003e\n      \u003cspan\u003e{ scrollDirection }px\u003c/span\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### `useTimeout(fn, timeoutMs = 0)` \u003ca name=\"useTimeout\"\u003e\u003c/a\u003e\n\nCalls `fn` once after `intervalMs` milliseconds.\n\n##### Example\n\n```javascript\nimport React, { useState, useCallback } from 'react';\nimport { useClassNames, useTimeout } from '@haensl/react-hooks';\n\nconst MyComponent = () =\u003e {\n  const [animate, setAnimate] = useState(false);\n\n  // Start animation after 1s\n  useTimeout(() =\u003e {\n    setAnimate(true);\n  }, 1000);\n\n  const className = useClassNames({\n    animate\n  });\n\n  return (\n    \u003cdiv\n        className={ className }\n    \u003e\n        // ...\n    \u003c/div\u003e\n  );\n};\n```\n\n### `useWindowScroll([debounceMs = 25])` \u003ca name=\"useWindowScroll\"\u003e\u003c/a\u003e\nReturns an object _(`null` if there is no `window`)_ with properties `x` and `y` reflecting the the scroll position of the `window` or `document`. Scroll position updates are by default debounced by 25 milliseconds. This debounce interval can be customized via the optional `debounceMs` argument. Please check the [example below](#useWindowScrollExample) as well as the [Codepen example](https://codepen.io/haensl/pen/VweMJGm).\n\n#### Example\u003ca name=\"useWindowScrollExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useState } from 'react';\nimport { useWindowScroll } from '@haensl/react-hooks';\n\nconst windowScrollTracker = () =\u003e {\n  const windowScroll = useWindowScroll();\n\n  if (!windowScroll) {\n    return (\n      \u003cdiv className=\"WindowScrollTracker\"\u003e\n        no scroll poistion\n      \u003c/div\u003e\n    );\n  }\n\n  return (\n    \u003cdiv className=\"WindowScrollTracker\"\u003e\n      \u003cspan\u003eScroll x: {windowScroll.x}\u003c/span\u003e\n      \u003cspan\u003eScroll y: {windowScroll.y}\u003c/span\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/VweMJGm)\n\n### `useWindowSize([debounceMs = 25])` \u003ca name=\"useWindowSize\"\u003e\u003c/a\u003e\nReturns an object _(initially `null`)_ with properties `width` and `height` reflecting the `innerWidth` and `innerHeight` of the `window` object. Size updates are by default debounced by 25 milliseconds. This debounce interval can be customized via the optional `debounceMs` argument. Please check the [example below](#useWindowSizeExample) as well as the [Codepen example](https://codepen.io/haensl/pen/mdVMVxY).\n\n#### Example\u003ca name=\"useWindowSizeExample\"\u003e\u003c/a\u003e\n\n```javascript\nimport React, { useState } from 'react';\nimport { useWindowSize } from '@haensl/react-hooks';\n\nconst WindowSizeTracker = () =\u003e {\n  const windowSize = useWindowSize();\n\n  if (!windowSize) {\n    return (\n      \u003cdiv className=\"WindowSizeTracker\"\u003e\n        \u003cspan\u003eNo window size\u003c/span\u003e\n      \u003c/div\u003e\n    );\n  }\n\n  return (\n    \u003cdiv className=\"WindowSizeTracker\"\u003e\n      \u003cspan\u003eWindow Size:\u003c/span\u003e\n      \u003cspan\u003ewidth: { windowSize.width }px\u003c/span\u003e\n      \u003cspan\u003eheight: { windowSize.height }px\u003c/span\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### [→ Codepen example](https://codepen.io/haensl/pen/mdVMVxY)\n\n## [Changelog](CHANGELOG.md)\n\n## [License](LICENSE)\n","funding_links":["https://github.com/sponsors/haensl"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaensl%2Fhooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaensl%2Fhooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaensl%2Fhooks/lists"}