├── .gitignore ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json ├── tsconfig.json └── usehooks.com ├── .astro └── types.d.ts ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── README.md ├── astro.config.mjs ├── generate-og-images.mjs ├── package-lock.json ├── package.json ├── public ├── favicon.png ├── fonts │ ├── firacode-regular.ttf │ ├── firacode-regular.woff2 │ ├── outfit-bold.ttf │ ├── outfit-medium.ttf │ ├── outfit-regular.ttf │ ├── outfit-v4-latin-500.woff2 │ ├── outfit-v4-latin-700.woff2 │ ├── outfit-v4-latin-900.woff2 │ ├── outfit-v4-latin-regular.woff2 │ └── paytone-one-v16-latin-regular.woff2 ├── img │ ├── banner-sale-reactgg.svg │ ├── bytes-tshirt.png │ ├── d20.svg │ ├── favicon.png │ ├── hot-sauce.svg │ ├── icon-copy.svg │ ├── icon-github.svg │ ├── icon-search.svg │ ├── logo-useHooks.svg │ ├── money.svg │ ├── og-hook.jpg │ ├── react-gg-logo-sticker.svg │ ├── react-gg-logo.svg │ ├── spinner.svg │ └── ui-logo.svg └── meta │ └── og.jpg ├── src ├── components │ ├── Button.astro │ ├── CodePreview.astro │ ├── CountdownTimer.tsx │ ├── HookDescription.astro │ ├── Install.astro │ ├── Logo.astro │ ├── LogoGithub.astro │ ├── QueryGGBanner.astro │ ├── RelatedHook.astro │ ├── StaticCodeContainer.astro │ ├── Svg.astro │ ├── codepreview │ │ ├── CodePreview.tsx │ │ ├── CodeWrapper.tsx │ │ └── utils.ts │ └── search │ │ ├── Callout.module.css │ │ ├── Callout.tsx │ │ ├── HookCard.module.css │ │ ├── HookCard.tsx │ │ ├── HookSearch.module.css │ │ ├── HookSearch.tsx │ │ ├── HookSort.module.css │ │ ├── HookSort.tsx │ │ ├── HooksList.module.css │ │ └── HooksList.tsx ├── content │ ├── config.ts │ └── hooks │ │ ├── useBattery.mdx │ │ ├── useClickAway.mdx │ │ ├── useContinuousRetry.mdx │ │ ├── useCopyToClipboard.mdx │ │ ├── useCountdown.mdx │ │ ├── useCounter.mdx │ │ ├── useDebounce.mdx │ │ ├── useDefault.mdx │ │ ├── useDocumentTitle.mdx │ │ ├── useEventListener.mdx │ │ ├── useFavicon.mdx │ │ ├── useFetch.mdx │ │ ├── useGeolocation.mdx │ │ ├── useHistoryState.mdx │ │ ├── useHover.mdx │ │ ├── useIdle.mdx │ │ ├── useIntersectionObserver.mdx │ │ ├── useInterval.mdx │ │ ├── useIntervalWhen.mdx │ │ ├── useIsClient.mdx │ │ ├── useIsFirstRender.mdx │ │ ├── useKeyPress.mdx │ │ ├── useList.mdx │ │ ├── useLocalStorage.mdx │ │ ├── useLockBodyScroll.mdx │ │ ├── useLogger.mdx │ │ ├── useLongPress.mdx │ │ ├── useMap.mdx │ │ ├── useMeasure.mdx │ │ ├── useMediaQuery.mdx │ │ ├── useMouse.mdx │ │ ├── useNetworkState.mdx │ │ ├── useObjectState.mdx │ │ ├── useOrientation.mdx │ │ ├── usePageLeave.mdx │ │ ├── usePreferredLanguage.mdx │ │ ├── usePrevious.mdx │ │ ├── useQueue.mdx │ │ ├── useRandomInterval.mdx │ │ ├── useRenderCount.mdx │ │ ├── useRenderInfo.mdx │ │ ├── useScript.mdx │ │ ├── useSessionStorage.mdx │ │ ├── useSet.mdx │ │ ├── useThrottle.mdx │ │ ├── useTimeout.mdx │ │ ├── useToggle.mdx │ │ ├── useVisibilityChange.mdx │ │ ├── useWindowScroll.mdx │ │ └── useWindowSize.mdx ├── env.d.ts ├── layouts │ └── Layout.astro ├── pages │ ├── 404.astro │ ├── [hook].astro │ └── index.astro ├── sections │ ├── Footer.astro │ ├── HomeHero.astro │ ├── NavInternal.astro │ └── NavMain.astro └── styles │ └── globals.css ├── tailwind.config.cjs ├── theme.json ├── tsconfig.json └── vercel.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # dotenv environment variables file 52 | .env 53 | 54 | 55 | # Mac files 56 | .DS_Store 57 | 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ui.dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![useHooks](https://usehooks.com/meta/og.jpg) 2 | 3 | # useHooks 4 | 5 | A collection of modern, server-safe React hooks – from the [ui.dev](https://ui.dev) team. 6 | 7 | Compatible with React v18.0.0+. 8 | 9 | ## Standard 10 | 11 | ### Install 12 | 13 | `npm i @uidotdev/usehooks` 14 | 15 | ### Hooks 16 | 17 | - [useBattery](https://usehooks.com/usebattery) 18 | - [useClickAway](https://usehooks.com/useclickaway) 19 | - [useCopyToClipboard](https://usehooks.com/usecopytoclipboard) 20 | - [useCounter](https://usehooks.com/usecounter) 21 | - [useDebounce](https://usehooks.com/usedebounce) 22 | - [useDefault](https://usehooks.com/usedefault) 23 | - [useDocumentTitle](https://usehooks.com/usedocumenttitle) 24 | - [useFavicon](https://usehooks.com/usefavicon) 25 | - [useGeolocation](https://usehooks.com/usegeolocation) 26 | - [useHistoryState](https://usehooks.com/usehistorystate) 27 | - [useHover](https://usehooks.com/usehover) 28 | - [useIdle](https://usehooks.com/useidle) 29 | - [useIntersectionObserver](https://usehooks.com/useintersectionobserver) 30 | - [useIsClient](https://usehooks.com/useisclient) 31 | - [useIsFirstRender](https://usehooks.com/useisfirstrender) 32 | - [useList](https://usehooks.com/uselist) 33 | - [useLocalStorage](https://usehooks.com/uselocalstorage) 34 | - [useLockBodyScroll](https://usehooks.com/uselockbodyscroll) 35 | - [useLongPress](https://usehooks.com/uselongpress) 36 | - [useMap](https://usehooks.com/usemap) 37 | - [useMeasure](https://usehooks.com/usemeasure) 38 | - [useMediaQuery](https://usehooks.com/usemediaquery) 39 | - [useMouse](https://usehooks.com/usemouse) 40 | - [useNetworkState](https://usehooks.com/usenetworkstate) 41 | - [useObjectState](https://usehooks.com/useobjectstate) 42 | - [useOrientation](https://usehooks.com/useorientation) 43 | - [usePreferredLanguage](https://usehooks.com/usepreferredlanguage) 44 | - [usePrevious](https://usehooks.com/useprevious) 45 | - [useQueue](https://usehooks.com/usequeue) 46 | - [useRenderCount](https://usehooks.com/userendercount) 47 | - [useRenderInfo](https://usehooks.com/userenderinfo) 48 | - [useScript](https://usehooks.com/usescript) 49 | - [useSessionStorage](https://usehooks.com/usesessionstorage) 50 | - [useSet](https://usehooks.com/useset) 51 | - [useThrottle](https://usehooks.com/usethrottle) 52 | - [useToggle](https://usehooks.com/usetoggle) 53 | - [useVisibilityChange](https://usehooks.com/usevisibilitychange) 54 | - [useWindowScroll](https://usehooks.com/usewindowscroll) 55 | - [useWindowSize](https://usehooks.com/usewindowsize) 56 | 57 | ## Experimental 58 | 59 | ### Install 60 | 61 | `npm i @uidotdev/usehooks@experimental react@experimental react-dom@experimental` 62 | 63 | ### Hooks 64 | 65 | - [useContinuousRetry](https://usehooks.com/usecontinuousretry) 66 | - [useCountdown](https://usehooks.com/usecountdown) 67 | - [useEventListener](https://usehooks.com/useeventlistener) 68 | - [useFetch](https://usehooks.com/usefetch) 69 | - [useInterval](https://usehooks.com/useinterval) 70 | - [useIntervalWhen](https://usehooks.com/useintervalwhen) 71 | - [useKeyPress](https://usehooks.com/usekeypress) 72 | - [useLogger](https://usehooks.com/uselogger) 73 | - [usePageLeave](https://usehooks.com/usepageleave) 74 | - [useRandomInterval](https://usehooks.com/userandominterval) 75 | - [useTimeout](https://usehooks.com/usetimeout) 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uidotdev/usehooks", 3 | "version": "2.4.1", 4 | "description": "A collection of modern, server-safe React hooks – from the ui.dev team", 5 | "type": "module", 6 | "repository": "uidotdev/usehooks", 7 | "devDependencies": { 8 | "@types/react": "^18.2.20", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "typescript": "^5.1.6" 12 | }, 13 | "exports": { 14 | "default": "./index.js" 15 | }, 16 | "engines": { 17 | "node": ">=16" 18 | }, 19 | "files": [ 20 | "index.js", 21 | "index.d.ts" 22 | ], 23 | "types": "index.d.ts", 24 | "peerDependencies": { 25 | "react": ">=18.0.0", 26 | "react-dom": ">=18.0.0" 27 | }, 28 | "author": "Tyler McGinnis, Ben Adam", 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "noImplicitAny": true, 10 | "allowJs": true, 11 | "outDir": "./irrelevant/unused" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /usehooks.com/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /usehooks.com/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /node_modules 3 | .DS_Store 4 | .env 5 | .env.* 6 | .next 7 | dist/* 8 | /dist 9 | public/meta/* 10 | !public/meta/og.jpg -------------------------------------------------------------------------------- /usehooks.com/.prettierrc: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /usehooks.com/README.md: -------------------------------------------------------------------------------- 1 | # usehooks.com 2 | 3 | ## 🚀 Project Structure 4 | 5 | Inside this project, you'll see the following folders and files: 6 | 7 | ``` 8 | / 9 | ├── public/ 10 | │ └── favicon.png 11 | │ └── img 12 | ├── src/ 13 | │ ├── components/ 14 | │ │ └── Button.astro 15 | │ ├── layouts/ 16 | │ │ └── Layout.astro 17 | │ ├── pages/ 18 | │ │ └── index.astro 19 | │ ├── sections/ 20 | │ │ └── HomeHero.astro 21 | └── package.json 22 | ``` 23 | 24 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 25 | 26 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 27 | 28 | Any static assets, like images, can be placed in the `public/` directory. 29 | 30 | ## 🧞 Commands 31 | 32 | All commands are run from the root of the project, from a terminal: 33 | 34 | | Command | Action | 35 | | :--------------------- | :----------------------------------------------- | 36 | | `npm install` | Installs dependencies | 37 | | `npm run dev` | Starts local dev server at `localhost:3000` | 38 | | `npm run build` | Build your production site to `./dist/` | 39 | | `npm run preview` | Preview your build locally, before deploying | 40 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 41 | | `npm run astro --help` | Get help using the Astro CLI | 42 | 43 | Check out [Astro documentation](https://docs.astro.build). -------------------------------------------------------------------------------- /usehooks.com/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import react from "@astrojs/react"; 3 | import customTheme from "./theme.json"; 4 | import tailwind from "@astrojs/tailwind"; 5 | 6 | // import vercel from "@astrojs/vercel/edge"; 7 | 8 | import mdx from "@astrojs/mdx"; 9 | 10 | // https://astro.build/config 11 | import sitemap from "@astrojs/sitemap"; 12 | 13 | // https://astro.build/config 14 | export default defineConfig({ 15 | site: "https://usehooks.com", 16 | trailingSlash: "never", 17 | integrations: [react(), tailwind(), mdx(), sitemap()], 18 | output: "static", 19 | // adapter: vercel(), 20 | markdown: { 21 | shikiConfig: { 22 | // Choose from Shiki's built-in themes (or add your own) 23 | // https://github.com/shikijs/shiki/blob/main/docs/themes.md 24 | theme: customTheme, 25 | // Add custom languages 26 | // Note: Shiki has countless langs built-in, including .astro! 27 | // https://github.com/shikijs/shiki/blob/main/docs/languages.md 28 | langs: [], 29 | // Enable word wrap to prevent horizontal scrolling 30 | wrap: false 31 | } 32 | } 33 | }); -------------------------------------------------------------------------------- /usehooks.com/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reacthooks.com", 3 | "type": "module", 4 | "version": "2.0.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "node ./generate-og-images.mjs && astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/mdx": "^0.19.3", 15 | "@astrojs/react": "^2.2.0", 16 | "@astrojs/sitemap": "^1.3.1", 17 | "@astrojs/tailwind": "^3.1.3", 18 | "@astrojs/ts-plugin": "^1.0.6", 19 | "@codesandbox/sandpack-react": "^2.6.6", 20 | "@types/react": "^18.2.7", 21 | "@types/react-dom": "^18.2.4", 22 | "astro": "^2.5.2", 23 | "classnames": "^2.3.2", 24 | "framer-motion": "^10.9.1", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "tailwindcss": "^3.2.7" 28 | }, 29 | "devDependencies": { 30 | "@resvg/resvg-js": "^2.4.1", 31 | "satori": "^0.9.1", 32 | "satori-html": "^0.3.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /usehooks.com/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/favicon.png -------------------------------------------------------------------------------- /usehooks.com/public/fonts/firacode-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/firacode-regular.ttf -------------------------------------------------------------------------------- /usehooks.com/public/fonts/firacode-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/firacode-regular.woff2 -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-bold.ttf -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-medium.ttf -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-regular.ttf -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-v4-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-v4-latin-500.woff2 -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-v4-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-v4-latin-700.woff2 -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-v4-latin-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-v4-latin-900.woff2 -------------------------------------------------------------------------------- /usehooks.com/public/fonts/outfit-v4-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/outfit-v4-latin-regular.woff2 -------------------------------------------------------------------------------- /usehooks.com/public/fonts/paytone-one-v16-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/fonts/paytone-one-v16-latin-regular.woff2 -------------------------------------------------------------------------------- /usehooks.com/public/img/bytes-tshirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/img/bytes-tshirt.png -------------------------------------------------------------------------------- /usehooks.com/public/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/img/favicon.png -------------------------------------------------------------------------------- /usehooks.com/public/img/icon-copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usehooks.com/public/img/icon-github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usehooks.com/public/img/icon-search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usehooks.com/public/img/logo-useHooks.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usehooks.com/public/img/og-hook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/img/og-hook.jpg -------------------------------------------------------------------------------- /usehooks.com/public/img/react-gg-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usehooks.com/public/img/ui-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usehooks.com/public/meta/og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/usehooks/945436df0037bc21133379a5e13f1bd73f1ffc36/usehooks.com/public/meta/og.jpg -------------------------------------------------------------------------------- /usehooks.com/src/components/Button.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | text: string; 4 | size: string; 5 | href?: string; 6 | type: "link" | "button" | "submit" | "reset"; 7 | color: string; 8 | class?: string; 9 | } 10 | 11 | const { type, href, text, size, color } = Astro.props; 12 | const { class: className } = Astro.props; 13 | --- 14 | 15 | { type === "link" ? {text} : } 16 | 17 | 70 | -------------------------------------------------------------------------------- /usehooks.com/src/components/CodePreview.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import CodePreview from "./codepreview/CodePreview"; 3 | import { getFiles } from "./codepreview/utils"; 4 | 5 | const { sandboxId, previewHeight } = Astro.props; 6 | const files = await getFiles({ id: sandboxId }); 7 | --- 8 | 9 |
10 |

Demo:

11 |
12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /usehooks.com/src/components/CountdownTimer.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useEffect, useState } from "react"; 2 | 3 | interface CountdownProps { 4 | targetDate: string; // YYYY-MM-DD format 5 | } 6 | 7 | interface TimeLeft { 8 | days: number; 9 | hours: number; 10 | minutes: number; 11 | seconds: number; 12 | } 13 | 14 | function calculateTimeLeft(targetDate: string): TimeLeft { 15 | const target = new Date(`${targetDate}T00:00:00-08:00`); 16 | const now = new Date(); 17 | const difference = +target - +now; 18 | 19 | if (difference <= 0) { 20 | return { 21 | days: 0, 22 | hours: 0, 23 | minutes: 0, 24 | seconds: 0, 25 | }; 26 | } 27 | 28 | return { 29 | days: Math.floor(difference / (1000 * 60 * 60 * 24)), 30 | hours: Math.floor((difference / (1000 * 60 * 60)) % 24), 31 | minutes: Math.floor((difference / 1000 / 60) % 60), 32 | seconds: Math.floor((difference / 1000) % 60), 33 | }; 34 | } 35 | 36 | const formatNumber = (number: number) => number.toString().padStart(2, "0"); 37 | 38 | const Countdown: React.FC = ({ targetDate }) => { 39 | const [timeLeft, setTimeLeft] = useState( 40 | calculateTimeLeft(targetDate) 41 | ); 42 | 43 | useEffect(() => { 44 | const timer = setInterval(() => { 45 | const newTimeLeft = calculateTimeLeft(targetDate); 46 | setTimeLeft(newTimeLeft); 47 | if ( 48 | newTimeLeft.days === 0 && 49 | newTimeLeft.hours === 0 && 50 | newTimeLeft.minutes === 0 && 51 | newTimeLeft.seconds === 0 52 | ) { 53 | clearInterval(timer); 54 | } 55 | }, 1000); 56 | 57 | return () => clearInterval(timer); 58 | }, [targetDate]); 59 | 60 | if ( 61 | timeLeft.days === 0 && 62 | timeLeft.hours === 0 && 63 | timeLeft.minutes === 0 && 64 | timeLeft.seconds === 0 65 | ) { 66 | return null; 67 | } 68 | 69 | return ( 70 |
71 | {["days", "hours", "minutes", "seconds"].map((unit, index) => ( 72 | 73 | {index > 0 && :} 74 |
75 | 76 | {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)} 77 | 78 | 79 | {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)} 80 | 81 |

{unit}

82 |
83 |
84 | ))} 85 |
86 | ); 87 | }; 88 | 89 | export default Countdown; 90 | -------------------------------------------------------------------------------- /usehooks.com/src/components/HookDescription.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Button from "./Button.astro"; 3 | const { name } = Astro.props; 4 | --- 5 | 6 |
7 |
8 |

Description:

9 | 10 |
11 | 33 |
34 | 35 | 87 | -------------------------------------------------------------------------------- /usehooks.com/src/components/Install.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { text } = Astro.props; 3 | const { class: className } = Astro.props; 4 | --- 5 | 6 |
7 | {text} 8 | 9 |
10 | 11 | 40 | 41 | 83 | -------------------------------------------------------------------------------- /usehooks.com/src/components/Logo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { class: className } = Astro.props; 3 | --- 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /usehooks.com/src/components/LogoGithub.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { class: className } = Astro.props; 3 | --- 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /usehooks.com/src/components/QueryGGBanner.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Button from "./Button.astro"; 3 | import CountdownTimer from "./CountdownTimer"; 4 | --- 5 | 6 | 33 | 34 | 139 | -------------------------------------------------------------------------------- /usehooks.com/src/components/RelatedHook.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getEntry } from "astro:content"; 3 | import HookCard from "./search/HookCard"; 4 | 5 | const { slug } = Astro.props; 6 | const relatedHook = await getEntry("hooks", slug); 7 | const { name, tagline } = relatedHook.data; 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /usehooks.com/src/components/StaticCodeContainer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import CodeWrapper from './codepreview/CodeWrapper' 3 | --- 4 |
5 |

Example:

6 |
7 | 8 | 9 | 10 |
11 |
12 | 13 | -------------------------------------------------------------------------------- /usehooks.com/src/components/Svg.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | name: string; 4 | } 5 | 6 | const { name } = Astro.props as Props; 7 | const { default: innerHTML } = await import(`../svg/${name}.svg?raw`); 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /usehooks.com/src/components/codepreview/CodePreview.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | SandpackProvider, 3 | SandpackPreview, 4 | SandpackStack, 5 | SandpackFiles, 6 | } from "@codesandbox/sandpack-react"; 7 | import cx from "classnames"; 8 | 9 | export type PreviewProps = { 10 | previewHeight?: string; 11 | files?: SandpackFiles | undefined; 12 | }; 13 | 14 | export default function CodePreview({ 15 | previewHeight = "250px", 16 | files, 17 | }: PreviewProps) { 18 | const sandpackProviderProps = { 19 | files, 20 | initMode: "user-visible", 21 | autorun: false, 22 | logLevel: 0, 23 | }; 24 | 25 | return ( 26 |
27 | 37 | 38 | 44 | 45 | 46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /usehooks.com/src/components/codepreview/CodeWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | import { set } from "astro/zod"; 4 | 5 | const springConfig = { 6 | damping: 20, 7 | stiffness: 120, 8 | mass: 0.15, 9 | }; 10 | 11 | export default function CodeWrapper({ 12 | children, 13 | }: { 14 | children: React.ReactNode; 15 | }) { 16 | const [expanded, setExpanded] = useState(false); 17 | const [hideExpand, setHideExpand] = useState(false); 18 | 19 | return ( 20 |
21 | 35 |
{ 37 | if (el) { 38 | const { height } = el.getBoundingClientRect(); 39 | if (height < 500) { 40 | setExpanded(true); 41 | setHideExpand(true); 42 | } 43 | } 44 | }} 45 | > 46 | {children} 47 |
48 |
49 | {!hideExpand && ( 50 |
55 | 61 |
62 | )} 63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /usehooks.com/src/components/codepreview/utils.ts: -------------------------------------------------------------------------------- 1 | import { SandpackFile } from "@codesandbox/sandpack-react"; 2 | 3 | export async function getFiles({ id }: { id: string }) { 4 | const configUrl = `https://codesandbox.io/api/v1/sandboxes/${id}/sandpack`; 5 | const response = await fetch(configUrl); 6 | if (response.ok) { 7 | const data = await response.json(); 8 | // This hardcodes the active file, we should find a better way to do this 9 | return updateFiles(data.files); 10 | } 11 | return {}; 12 | } 13 | 14 | // https://codesandbox.io/s/challenge-ui-test-zurc8s 15 | 16 | function getChallengeConfig(json: string) { 17 | const csb = JSON.parse(json); 18 | 19 | if (csb?.previewConfig) { 20 | return csb.previewConfig; 21 | } 22 | 23 | return { 24 | visibleFiles: [], 25 | activeFile: "/src/App.js", 26 | }; 27 | } 28 | 29 | export function updateFiles(files: { [key: string]: SandpackFile }) { 30 | const previewConfig = getChallengeConfig(files["/package.json"].code); 31 | Object.keys(files).map((key) => { 32 | if (key === previewConfig.activeFile) { 33 | files[key].active = true; 34 | } 35 | if (!previewConfig.visibleFiles.includes(key)) { 36 | files[key].hidden = true; 37 | } 38 | }); 39 | return files; 40 | } 41 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/Callout.module.css: -------------------------------------------------------------------------------- 1 | .callout :global(a) { 2 | height: 100%; 3 | padding: var(--body-padding); 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | gap: 1rem; 8 | border: var(--border-dark); 9 | border-radius: 0.5rem; 10 | font-size: clamp(0.9rem, 2vw, 1.1rem); 11 | text-align: center; 12 | transition: all 200ms ease-in-out; 13 | } 14 | 15 | .callout :global(a:hover) { 16 | transform: scale(1.03); 17 | } 18 | 19 | .callout :global(img:not(.logo)) { 20 | max-width: 120px; 21 | margin-top: calc(var(--body-padding) * -1.2); 22 | margin-bottom: 0.5rem; 23 | } 24 | 25 | .callout :global(img.d20) { 26 | width: 70px; 27 | } 28 | 29 | .callout :global(img.money) { 30 | width: 110px; 31 | } 32 | 33 | .callout :global(img.spinner) { 34 | width: 80px; 35 | } 36 | 37 | .callout :global(img.hot-sauce) { 38 | width: 80px; 39 | } 40 | 41 | .callout :global(img.logo) { 42 | max-width: 150px; 43 | } 44 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/Callout.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./Callout.module.css"; 2 | 3 | type Props = { 4 | image: string; 5 | imageWidth: string; 6 | imageHeight: string; 7 | imageAlt: string; 8 | pitch: string; 9 | }; 10 | 11 | export default function Callout({ 12 | image, 13 | imageWidth, 14 | imageHeight, 15 | imageAlt, 16 | pitch, 17 | }) { 18 | return ( 19 |
  • 20 | 21 | {imageAlt} 28 | React.gg 35 |

    {pitch}

    36 |
    37 |
  • 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HookCard.module.css: -------------------------------------------------------------------------------- 1 | .hook > :global(a) { 2 | height: 100%; 3 | padding: var(--body-padding); 4 | display: flex; 5 | flex-direction: column; 6 | gap: 0.6rem; 7 | background-color: var(--charcoal); 8 | border-radius: 0.5rem; 9 | transition: transform 200ms ease-in-out; 10 | } 11 | 12 | .hook > :global(a:hover) { 13 | transform: scale(1.03); 14 | } 15 | 16 | .card-title { 17 | color: var(--blue); 18 | text-transform: none; 19 | font-family: var(--font-outfit); 20 | font-size: clamp(1.1rem, 3vw, 1.4rem); 21 | font-weight: 600; 22 | } 23 | 24 | .card-description { 25 | margin-bottom: 0.5rem; 26 | font-size: clamp(0.9rem, 2vw, 1.1rem); 27 | } 28 | 29 | .arrow { 30 | width: 28px; 31 | aspect-ratio: 3 / 2; 32 | margin-top: auto; 33 | align-self: flex-end; 34 | transition: all 200ms ease-in-out; 35 | } 36 | 37 | :global(a:hover) .arrow { 38 | transform: translateX(0.6rem); 39 | } 40 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HookCard.tsx: -------------------------------------------------------------------------------- 1 | import styles from './HookCard.module.css'; 2 | 3 | export default function HookCard({ 4 | name, 5 | tagline, 6 | }: { 7 | name: string; 8 | tagline: string; 9 | }) { 10 | return ( 11 |
  • 12 | 13 |

    {name}

    14 |

    {tagline}

    15 | 20 | 28 | 29 |
    30 |
  • 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HookSearch.module.css: -------------------------------------------------------------------------------- 1 | .hooks-search { 2 | position: relative; 3 | align-self: flex-start; 4 | } 5 | 6 | .input { 7 | padding: 0.4rem 1rem; 8 | padding-right: 2rem; 9 | background-color: var(--coal); 10 | border: var(--border-light); 11 | border-radius: 1.5rem; 12 | font-size: clamp(0.8rem, 2vw, 1rem); 13 | color: var(--white); 14 | transition: all 150ms ease-in-out; 15 | } 16 | 17 | .input:focus { 18 | outline: none; 19 | border-color: var(--charcoal); 20 | box-shadow: var(--focus-object); 21 | } 22 | 23 | .input + :global(button) { 24 | position: absolute; 25 | right: 0.8rem; 26 | top: 50%; 27 | transform: translateY(-50%); 28 | font-size: 1rem; 29 | transition: all 150ms ease-in-out; 30 | } 31 | 32 | :global(input[type="search"]::-webkit-search-decoration), 33 | :global(input[type="search"]::-webkit-search-cancel-button), 34 | :global(input[type="search"]::-webkit-search-results-button), 35 | :global(input[type="search"]::-webkit-search-results-decoration) { 36 | display: none; 37 | } 38 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HookSearch.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./HookSearch.module.css"; 2 | 3 | export default function HookSearch({ 4 | handleChange, 5 | handleClear, 6 | value, 7 | }: { 8 | handleClear: () => void; 9 | handleChange: (e: React.ChangeEvent) => void; 10 | value: string; 11 | }) { 12 | return ( 13 |
    14 | 22 | 25 |
    26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HookSort.module.css: -------------------------------------------------------------------------------- 1 | .toggle { 2 | padding: 0.2rem 0.4rem; 3 | border: var(--border-light); 4 | border-radius: 0.3rem; 5 | font-size: clamp(0.7rem, 2vw, 0.9rem); 6 | font-weight: 500; 7 | transition: all 200ms ease-in-out; 8 | } 9 | 10 | .toggle.active { 11 | background-color: var(--yellow); 12 | border-color: var(--yellow); 13 | color: var(--charcoal); 14 | cursor: default; 15 | } 16 | 17 | .toggle:not(.active):hover { 18 | background-color: var(--charcoal); 19 | } 20 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HookSort.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./HookSort.module.css"; 2 | 3 | export default function HookSort({ 4 | setSort, 5 | value, 6 | }: { 7 | setSort: (value: "name" | "popular") => void; 8 | value: "name" | "popular"; 9 | }) { 10 | return ( 11 |
    12 | Sort: 13 | 21 | 27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HooksList.module.css: -------------------------------------------------------------------------------- 1 | .hooks-grid { 2 | max-width: 980px; 3 | margin: 2rem auto; 4 | } 5 | 6 | .hooks-controls { 7 | padding: 1rem var(--body-padding); 8 | display: flex; 9 | justify-content: flex-end; 10 | } 11 | 12 | .hooks-list { 13 | display: grid; 14 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 15 | align-items: stretch; 16 | gap: 1.6rem; 17 | } 18 | -------------------------------------------------------------------------------- /usehooks.com/src/components/search/HooksList.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Callout from "./Callout"; 3 | import HookCard from "./HookCard"; 4 | import HookSort from "./HookSort"; 5 | import styles from "./HooksList.module.css"; 6 | 7 | function insertAtIntervals(arr, items) { 8 | let newArr = [...arr]; // create a copy of the array 9 | let step = Math.ceil(newArr.length / items.length); 10 | 11 | // Insert the first item at the beginning of the array 12 | newArr.unshift(items[0]); 13 | 14 | for (let i = 1; i < items.length; i++) { 15 | let position = i * step + 1; // +1 to account for the first item 16 | newArr.splice(position, 0, items[i]); 17 | } 18 | 19 | return newArr; 20 | } 21 | 22 | function sortAlphabetical(a, b) { 23 | if (a.data.name < b.data.name) { 24 | return -1; 25 | } 26 | if (a.data.name > b.data.name) { 27 | return 1; 28 | } 29 | return 0; 30 | } 31 | 32 | function sortByPopularity(a, b) { 33 | if (a.data.rank < b.data.rank) { 34 | return -1; 35 | } 36 | if (a.data.rank > b.data.rank) { 37 | return 1; 38 | } 39 | return 0; 40 | } 41 | 42 | const sortMap = { 43 | name: sortAlphabetical, 44 | popular: sortByPopularity, 45 | }; 46 | 47 | export default function HooksList({ hooks }) { 48 | const [sort, setSort] = useState<"name" | "popular">("popular"); 49 | 50 | const list = hooks.sort(sortMap[sort]); 51 | const listWithCallouts = insertAtIntervals(list, [ 52 | { 53 | id: "Callout 1", 54 | image: "d20", 55 | imageWidth: "222", 56 | imageHeight: "206", 57 | imageAlt: "20-sided die", 58 | pitch: 59 | "It’s dangerous to go alone! Master React by learning how to build useHooks yourself.", 60 | }, 61 | { 62 | id: "Callout 2", 63 | image: "spinner", 64 | imageWidth: "284", 65 | imageHeight: "180", 66 | imageAlt: "board game spinner and all options are React", 67 | pitch: 68 | "There’s no better way to learn useHooks than by building it yourself.", 69 | }, 70 | { 71 | id: "Callout 3", 72 | image: "money", 73 | imageWidth: "210", 74 | imageHeight: "210", 75 | imageAlt: "$100 Monopoly-style money", 76 | pitch: 77 | "Please give us your money.", 78 | }, 79 | { 80 | id: "Callout 4", 81 | image: "hot-sauce", 82 | imageWidth: "206", 83 | imageHeight: "224", 84 | imageAlt: "travel-style postcard from React that says “Enjoy the views!", 85 | pitch: 86 | "The all new interactive way to master modern React (for fun and profit).", 87 | } 88 | ]); 89 | 90 | return ( 91 |
    92 |
    93 | 94 |
    95 |
      96 | {listWithCallouts.map( 97 | ({ data, id, image, imageWidth, imageHeight, imageAlt, pitch }) => { 98 | if (!data) { 99 | return ( 100 | 108 | ); 109 | } 110 | return ( 111 | 112 | ); 113 | } 114 | )} 115 |
    116 |
    117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /usehooks.com/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z, reference } from 'astro:content'; 2 | 3 | export const collections = { 4 | hooks: defineCollection({ 5 | schema: z.object({ 6 | experimental: z.boolean().optional(), 7 | draft: z.boolean().default(false), 8 | sandboxId: z.string().optional(), 9 | previewHeight: z.string().optional(), 10 | name: z.string(), 11 | tagline: z.string(), 12 | ogImage: z.string().optional(), 13 | rank: z.number(), 14 | relatedHooks: z.array( 15 | reference('hooks').optional() 16 | ).optional(), 17 | }), 18 | }), 19 | }; -------------------------------------------------------------------------------- /usehooks.com/src/content/hooks/useBattery.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useBattery 3 | rank: 32 4 | tagline: Track the battery status of a user’s device with useBattery. 5 | sandboxId: usebattery-o8js1p 6 | previewHeight: 320px 7 | relatedHooks: 8 | - usenetworkstate 9 | - usepreferredlanguage 10 | --- 11 | 12 | import CodePreview from "../../components/CodePreview.astro"; 13 | import HookDescription from "../../components/HookDescription.astro"; 14 | import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; 15 | 16 | 17 | The useBattery hook is useful for accessing and monitoring the battery status 18 | of the user’s device in a React application. By using this hook, you can 19 | easily retrieve information such as the battery level, charging status, and 20 | estimated charging and discharging times. It provides a state object that 21 | includes properties like supported, loading, level, charging, chargingTime, 22 | and dischargingTime. 23 | 24 | 25 |
    26 | ### Return Values 27 | The hook returns an object containing the following properties: 28 | 29 |
    30 | | Name | Type | Description | 31 | | ---------------- | ------- | ----------- | 32 | | supported | boolean | Indicates whether the Battery Status API is supported in the user’s browser. | 33 | | loading | boolean | Indicates if the battery information is still loading. | 34 | | level | number | Represents the level of the system’s battery. 0.0 means that the system’s battery is completely discharged, and 1.0 means the battery is completely charged. | 35 | | charging | boolean | Represents whether the system’s battery is charging. `true` means the battery is charging, `false` means it’s not. | 36 | | chargingTime | number | Represents the time remaining in seconds until the system’s battery is fully charged. | 37 | | dischargingTime | number | Represents the time remaining in seconds until the system’s battery is completely discharged and the system is about to be suspended. | 38 |
    39 |
    40 | 41 | 45 | 46 | 47 | 48 | ```jsx 49 | import { useBattery } from "@uidotdev/usehooks"; 50 | import Battery from "./Battery"; 51 | 52 | export default function App() { 53 | const { loading, level, charging, chargingTime, dischargingTime } = 54 | useBattery(); 55 | return ( 56 | <> 57 |
    58 |

    useBattery

    59 | {!loading ? ( 60 | 66 | ) : ( 67 |

    Loading...

    68 | )} 69 |
    70 | 71 | ); 72 | } 73 | ``` 74 | 75 |
    76 | -------------------------------------------------------------------------------- /usehooks.com/src/content/hooks/useClickAway.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useClickAway 3 | rank: 47 4 | tagline: Detect clicks outside of specific component with useClickAway. 5 | sandboxId: useclickaway-p4hl4m 6 | previewHeight: 250px 7 | relatedHooks: 8 | - uselongpress 9 | - usehover 10 | --- 11 | 12 | import CodePreview from "../../components/CodePreview.astro"; 13 | import HookDescription from "../../components/HookDescription.astro"; 14 | import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; 15 | 16 | 17 | The useClickAway hook is a useful for detecting clicks outside a specific 18 | component. It allows you to pass a callback function that will be triggered 19 | whenever a click occurs outside the component’s area. This hook is 20 | particularly helpful when implementing dropdown menus, modals, or any other UI 21 | elements that need to be closed when the user clicks outside of them. By 22 | attaching event listeners to the document, the hook checks if the click target 23 | is within the component’s reference, and if not, it invokes the provided 24 | callback function. 25 | 26 | 27 |
    28 | ### Parameters 29 | 30 |
    31 | | Name | Type | Description | 32 | | ---- | -------- | ----------- | 33 | | callback | function | The callback function that is provided as an argument to `useClickAway`. This function is invoked whenever a click event is detected outside of the referenced element. The event object from the click is passed to this callback function. | 34 |
    35 | 36 | ### Return Values 37 | 38 |
    39 | | Name | Type | Description | 40 | | ---- | ------------ | ----------- | 41 | | ref | React ref | This is a ref object returned by the hook. It should be attached to a React element to monitor click events. The ref provides a way to access the properties of the element it is attached to. | 42 |
    43 |
    44 | 45 | 46 | 50 | 51 | 52 | 53 | ```jsx 54 | import * as React from "react"; 55 | import { useClickAway } from "@uidotdev/usehooks"; 56 | import { closeIcon } from "./icons"; 57 | 58 | export default function App() { 59 | const [isOpen, setIsOpen] = React.useState(false); 60 | const ref = useClickAway(() => { 61 | setIsOpen(false); 62 | }); 63 | 64 | const handleOpenModal = () => { 65 | if (isOpen === false) { 66 | setIsOpen(true); 67 | } 68 | }; 69 | 70 | return ( 71 | <> 72 |
    73 |

    useClickAway

    74 | 77 |
    78 | {isOpen && ( 79 | 80 | 81 |

    Modal

    82 |

    83 | Click outside the modal to close (or use the button) whatever you 84 | prefer. 85 |

    86 |
    87 | )} 88 | 89 | ); 90 | } 91 | ``` 92 | 93 |
    94 | -------------------------------------------------------------------------------- /usehooks.com/src/content/hooks/useContinuousRetry.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useContinuousRetry 3 | experimental: true 4 | rank: 12 5 | tagline: Automates retries of a callback function until it succeeds with useContinuousRetry 6 | sandboxId: usecontinuousretry-v0uf1n 7 | previewHeight: 380px 8 | relatedHooks: 9 | - usesessionstorage 10 | --- 11 | 12 | import CodePreview from "../../components/CodePreview.astro"; 13 | import HookDescription from "../../components/HookDescription.astro"; 14 | import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; 15 | 16 | 17 | The useContinuousRetry hook allows you to repeatedly call a specified callback 18 | function at a defined interval until the callback returns a truthy value, 19 | indicating a successful resolution. This hook is particularly handy when 20 | dealing with asynchronous operations or API calls that may fail temporarily 21 | and need to be retried automatically. It encapsulates the logic of retrying 22 | and provides a clean interface to handle retry-related states, such as whether 23 | the retry process has resolved or not. 24 | 25 | 26 |
    27 | ### Parameters 28 | 29 |
    30 | | Name | Type | Description | 31 | | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------| 32 | | callback | function | The callback function to be executed repeatedly until it returns a truthy value. | 33 | | interval | number | (Optional) The interval in milliseconds at which the callback function is executed. Default value is 100 milliseconds. | 34 | | options | object | (Optional) An object containing a `maxRetries` property which tells `useContinuousRetry` the maximum amount of retry attempts it should make before stopping | 35 |
    36 | 37 | ### Return Value 38 | 39 |
    40 | | Type | Description | 41 | | ------- | ------------------------------------------------------------------------------------------ | 42 | | boolean | `true` if the callback function has resolved (returned a truthy value), `false` otherwise. | 43 |
    44 |
    45 | 46 | 50 | 51 | 52 | 53 | ```jsx 54 | import * as React from "react"; 55 | import { useContinuousRetry } from "@uidotdev/usehooks"; 56 | 57 | export default function App() { 58 | const [count, setCount] = React.useState(0); 59 | const hasResolved = useContinuousRetry(() => { 60 | console.log("retrying"); 61 | return count > 10; 62 | }, 1000); 63 | 64 | return ( 65 |
    66 |

    useContinuousRetry

    67 | 70 |
    {JSON.stringify({ hasResolved, count }, null, 2)}
    71 |
    72 | ); 73 | } 74 | ``` 75 | 76 |
    77 | -------------------------------------------------------------------------------- /usehooks.com/src/content/hooks/useCopyToClipboard.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useCopyToClipboard 3 | rank: 31 4 | tagline: Copy text to the clipboard using useCopyToClipboard. 5 | sandboxId: usecopytoclipboard-y22r6w 6 | previewHeight: 300px 7 | relatedHooks: 8 | - usemap 9 | - usequeue 10 | --- 11 | 12 | import CodePreview from "../../components/CodePreview.astro"; 13 | import HookDescription from "../../components/HookDescription.astro"; 14 | import StaticCodeContainer from "../../components/StaticCodeContainer.astro"; 15 | 16 | 17 | The useCopyToClipboard hook is useful because it abstracts the complexity of 18 | copying text to the clipboard in a cross-browser compatible manner. It 19 | utilizes the modern navigator.clipboard.writeText method if available, which 20 | provides a more efficient and secure way to copy text. In case the writeText 21 | method is not supported by the browser, it falls back to a traditional method 22 | using the document.execCommand("copy") approach. 23 | 24 | 25 |
    26 | ### Return Value 27 | 28 | The `useCopyToClipboard` hook returns an array with the following elements: 29 | 30 |
    31 | | Index | Type | Description | 32 | | ----- | -------- | ------------------------------------------------------ | 33 | | 0 | string | The value that was last copied to the clipboard. | 34 | | 1 | function | A function to copy a specified value to the clipboard. | 35 |
    36 |
    37 | 38 | 42 | 43 | 44 | 45 | ```jsx 46 | import * as React from "react"; 47 | import { useCopyToClipboard } from "@uidotdev/usehooks"; 48 | import { copyIcon, checkIcon } from "./icons"; 49 | 50 | const randomHash = crypto.randomUUID(); 51 | 52 | export default function App() { 53 | const [copiedText, copyToClipboard] = useCopyToClipboard(); 54 | const hasCopiedText = Boolean(copiedText); 55 | return ( 56 |
    57 |

    useCopyToClipboard

    58 |
    59 | 60 |
    61 |           {randomHash}
    62 |           
    69 |         
    70 |
    71 | {hasCopiedText && ( 72 | 73 |

    74 | Copied{" "} 75 | 76 | 🎉 77 | 78 |

    79 |