├── .husky ├── .gitignore └── pre-commit ├── website ├── .gitignore ├── public │ ├── logo.png │ ├── logo_text.png │ ├── og_image.jpg │ ├── icons │ │ ├── favicon.ico │ │ ├── maskable_icon.png │ │ ├── apple-touch-icon.png │ │ ├── maskable_icon_x128.png │ │ ├── maskable_icon_x192.png │ │ ├── maskable_icon_x384.png │ │ ├── maskable_icon_x48.png │ │ ├── maskable_icon_x72.png │ │ └── maskable_icon_x96.png │ ├── fonts │ │ ├── Inter-italic.var.woff2 │ │ ├── Inter-roman.var.woff2 │ │ ├── JetBrainsMono-Bold.woff2 │ │ ├── JetBrainsMono-Thin.woff2 │ │ ├── JetBrainsMono-Regular.woff2 │ │ ├── JetBrainsMono-roman-var.ttf │ │ └── JetBrainsMono-italic-var.ttf │ ├── robots.txt │ ├── scripts │ │ └── theme.js │ ├── styles │ │ └── fonts.css │ └── manifest.json ├── postcss.config.js ├── next-sitemap.js ├── next-env.d.ts ├── vercel.json ├── pages │ ├── 404.tsx │ ├── _document.tsx │ ├── getting-started.tsx │ ├── [package] │ │ └── [function].tsx │ ├── functions.tsx │ ├── _app.tsx │ └── index.tsx ├── next.config.js ├── utils │ ├── useSidebar.ts │ └── loadMDX.ts ├── ui │ ├── docs │ │ ├── mdxComponents.tsx │ │ ├── Playground.tsx │ │ ├── Callout.tsx │ │ └── Source.tsx │ ├── Color.tsx │ ├── SEO.tsx │ ├── ThemeProvider.tsx │ ├── ThemeSwitch.tsx │ ├── SideBar.tsx │ └── FunctionList.tsx ├── scripts │ └── buildRoutes.ts ├── tsconfig.json ├── tailwind.config.js ├── docs │ └── getting-started.mdx ├── package.json └── README.md ├── .prettierignore ├── rollup.config.js ├── packages ├── shared │ ├── index.ts │ ├── utils │ │ ├── index.ts │ │ ├── types.ts │ │ ├── functions.ts │ │ ├── is.ts │ │ └── docs.mdx │ └── package.json └── core │ ├── useHasMounted │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useLocation │ ├── demo.tsx │ ├── docs.mdx │ └── index.ts │ ├── _template │ ├── docs.mdx │ ├── demo.tsx │ ├── index.ts │ └── index.test.ts │ ├── useIsSupported │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useHover │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── _ssr.config.ts │ ├── useWindowSize │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useMount │ ├── index.ts │ ├── demo.tsx │ └── docs.mdx │ ├── useUnMount │ ├── index.ts │ └── docs.mdx │ ├── useMouse │ ├── demo.tsx │ ├── docs.mdx │ └── index.ts │ ├── usePreferredColorScheme │ ├── index.ts │ ├── demo.tsx │ └── docs.mdx │ ├── useMountSync │ ├── index.ts │ ├── demo.tsx │ └── docs.mdx │ ├── usePrevious │ ├── index.ts │ ├── demo.tsx │ ├── docs.mdx │ └── index.test.ts │ ├── useOnline │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useToggle │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useFont │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useInterval │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useActiveElement │ ├── index.ts │ ├── demo.tsx │ └── docs.mdx │ ├── useDebounce │ ├── index.ts │ ├── demo.tsx │ ├── docs.mdx │ └── index.test.ts │ ├── useStateCompare │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useLocalStorage │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useSessionStorage │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useTitle │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useEffectAfterMount │ ├── index.ts │ ├── demo.tsx │ └── docs.mdx │ ├── useScreenShare │ ├── demo.tsx │ ├── docs.mdx │ └── index.ts │ ├── useEventListener │ └── demo.tsx │ ├── useAsyncCallback │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useNetwork │ ├── demo.tsx │ ├── docs.mdx │ └── index.ts │ ├── package.json │ ├── useMediaStream │ └── demo.tsx │ ├── useMediaQuery │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useClickOutside │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useScrollIntoView │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useMutationObserver │ ├── demo.tsx │ ├── index.ts │ └── docs.mdx │ ├── useIntersectionObserver │ ├── demo.tsx │ └── index.ts │ ├── BreakPointHooks │ ├── demo.tsx │ ├── breakpoints.ts │ └── index.ts │ ├── useScroll │ ├── index.ts │ ├── demo.tsx │ └── docs.mdx │ ├── useStateHistory │ ├── demo.tsx │ ├── docs.mdx │ └── index.ts │ ├── index.ts │ └── useKeyStroke │ ├── demo.tsx │ └── index.ts ├── .eslintignore ├── .prettierrc ├── types.ts ├── .editorconfig ├── scripts ├── utils │ ├── findFunctions.ts │ └── testUtils.ts ├── publish.ts ├── rollup.config.ts ├── release.ts └── build.ts ├── .github └── workflows │ ├── test_on_pull.yml │ └── publish_release.yml ├── jest.config.ts ├── .gitignore ├── tsconfig.json ├── LICENSE ├── .eslintrc.js ├── README.md └── package.json /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | public/sitemap.xml 2 | /public/*.js 3 | /public/*.js.map -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | yarn.lock 4 | package-lock.json 5 | public 6 | dist -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | require('esbuild-register') 2 | module.exports = require('./scripts/rollup.config.ts') 3 | -------------------------------------------------------------------------------- /website/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/logo.png -------------------------------------------------------------------------------- /packages/shared/index.ts: -------------------------------------------------------------------------------- 1 | // This file is auto generated. Do not edit manually. 2 | 3 | export * from './utils' 4 | -------------------------------------------------------------------------------- /packages/shared/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functions' 2 | export * from './is' 3 | export * from './types' 4 | -------------------------------------------------------------------------------- /website/public/logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/logo_text.png -------------------------------------------------------------------------------- /website/public/og_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/og_image.jpg -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/out/* 3 | **/.next/* 4 | **/dist/* 5 | website/public/*.js 6 | website/public/*.js.map -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /website/public/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/favicon.ico -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "jsxBracketSameLine": true 6 | } 7 | -------------------------------------------------------------------------------- /website/public/icons/maskable_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon.png -------------------------------------------------------------------------------- /website/public/fonts/Inter-italic.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/Inter-italic.var.woff2 -------------------------------------------------------------------------------- /website/public/fonts/Inter-roman.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/Inter-roman.var.woff2 -------------------------------------------------------------------------------- /website/public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /website/public/icons/maskable_icon_x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon_x128.png -------------------------------------------------------------------------------- /website/public/icons/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon_x192.png -------------------------------------------------------------------------------- /website/public/icons/maskable_icon_x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon_x384.png -------------------------------------------------------------------------------- /website/public/icons/maskable_icon_x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon_x48.png -------------------------------------------------------------------------------- /website/public/icons/maskable_icon_x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon_x72.png -------------------------------------------------------------------------------- /website/public/icons/maskable_icon_x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/icons/maskable_icon_x96.png -------------------------------------------------------------------------------- /website/public/fonts/JetBrainsMono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/JetBrainsMono-Bold.woff2 -------------------------------------------------------------------------------- /website/public/fonts/JetBrainsMono-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/JetBrainsMono-Thin.woff2 -------------------------------------------------------------------------------- /website/public/fonts/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /website/public/fonts/JetBrainsMono-roman-var.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/JetBrainsMono-roman-var.ttf -------------------------------------------------------------------------------- /website/next-sitemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteUrl: process.env.VERCEL_URL || 'https://react-hooks-library.vercel.app', 3 | generateRobotsTxt: true 4 | } 5 | -------------------------------------------------------------------------------- /website/public/fonts/JetBrainsMono-italic-var.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyitsarpit/react-hooks-library/HEAD/website/public/fonts/JetBrainsMono-italic-var.ttf -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | export interface FunctionMeta { 2 | name: string 3 | category: string 4 | description: string 5 | 6 | /** Package Name */ 7 | pkg?: string 8 | deprecated?: boolean 9 | } 10 | -------------------------------------------------------------------------------- /website/public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | # Host 6 | Host: https://react-hooks-library.vercel.app 7 | 8 | # Sitemaps 9 | Sitemap: https://react-hooks-library.vercel.app/sitemap.xml 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /scripts/utils/findFunctions.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob' 2 | 3 | export function findFunctions(dir: string, ignore: string[] = []): string[] { 4 | return fg.sync('*', { 5 | onlyDirectories: true, 6 | cwd: dir, 7 | ignore: ['**/dist', '**/node_modules', '_*', ...ignore] 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/useHasMounted/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useHasMounted } from '.' 2 | 3 | export function Demo() { 4 | const hasMounted = useHasMounted() 5 | 6 | return ( 7 |
8 | {hasMounted ? 'Mounted' : 'Not Yet Mounted'} 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/useLocation/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from '.' 2 | 3 | export function Demo() { 4 | const router = useLocation() 5 | 6 | return ( 7 |
8 |
 9 |         {JSON.stringify(router, null, 2)}
10 |       
11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /website/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": [ 3 | { 4 | "source": "/fonts/(.*)", 5 | "headers": [ 6 | { 7 | "key": "Cache-Control", 8 | "value": "public, max-age=31536000, immutable" 9 | } 10 | ] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /packages/shared/utils/types.ts: -------------------------------------------------------------------------------- 1 | import type { MutableRefObject } from 'react' 2 | 3 | /** 4 | * Any function 5 | */ 6 | export type Fn = () => void 7 | 8 | /** 9 | * Maybe it's a react ref, or a dom node. 10 | * 11 | * ```ts 12 | * type MaybeRef = T | MutableRefObject 13 | * ``` 14 | */ 15 | export type MaybeRef = T | MutableRefObject 16 | -------------------------------------------------------------------------------- /packages/core/_template/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: template 3 | name: useCounter 4 | description: A hook for keeping track of a numerical value and modifying it. 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # useCounter 14 | 15 | A hook for keeping track of a numerical value and modifying it. 16 | -------------------------------------------------------------------------------- /website/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | export default function NotFoundPage() { 4 | return ( 5 |
6 |
oops! this page does not exist
7 |
8 | 9 | Go Home 10 | 11 |
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/useIsSupported/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useIsSupported } from '.' 2 | 3 | export function Demo() { 4 | const isSupported = useIsSupported(() => !!navigator?.connection) 5 | 6 | return isSupported ? ( 7 |
`navigator.connection` Is supported
8 | ) : ( 9 |
`navigator.connection` Is not supported
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const withPWA = require('next-pwa') 3 | const runtimeCaching = require('next-pwa/cache') 4 | 5 | module.exports = withPWA({ 6 | pwa: { 7 | disable: process.env.NODE_ENV === 'development', 8 | dest: 'public', 9 | runtimeCaching 10 | }, 11 | experimental: { 12 | externalDir: true 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /packages/core/useHover/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | import { useHover } from '.' 4 | 5 | export function Demo() { 6 | const ref = useRef(null) 7 | const isHovered = useHover(ref) 8 | 9 | return ( 10 |
11 | {isHovered ? 'Hovered' : 'Not Hovered'} 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/_ssr.config.ts: -------------------------------------------------------------------------------- 1 | import { isClient } from '@react-hooks-library/shared' 2 | 3 | export const _window = /* #__PURE__ */ isClient ? window : undefined 4 | export const _document = /* #__PURE__ */ isClient ? window.document : undefined 5 | export const _navigator = /* #__PURE__ */ isClient 6 | ? window.navigator 7 | : undefined 8 | export const _location = /* #__PURE__ */ isClient ? window.location : undefined 9 | -------------------------------------------------------------------------------- /packages/core/useWindowSize/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useWindowSize } from '.' 2 | 3 | export function Demo() { 4 | const { height, width } = useWindowSize() 5 | 6 | return ( 7 |
8 |
9 | Dimensions - {height} x {width} 10 |
11 |
12 | Resize the window to observe changes 13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/useMount/index.ts: -------------------------------------------------------------------------------- 1 | import { Fn } from '@react-hooks-library/shared' 2 | import { useEffect } from 'react' 3 | 4 | /** 5 | * Run a function when a component is mounted. 6 | * 7 | * @deprecated This hook breaks in React 18's strict mode, since it's not idempotent 8 | * 9 | * @param callback function to be executed 10 | */ 11 | export function useMount(callback: Fn) { 12 | useEffect(callback, []) 13 | } 14 | -------------------------------------------------------------------------------- /website/public/scripts/theme.js: -------------------------------------------------------------------------------- 1 | // A minified version is inlined in _document.tsx 2 | 3 | !(function () { 4 | var b = document.body.classList; 5 | b.remove('dark'); // remove default theme 6 | var e = localStorage.getItem('theme'); 7 | if (e) b.add(e.replace(/"/g, '')); 8 | else { 9 | var m = window.matchMedia('(prefers-color-scheme: dark)'); 10 | m.matches ? b.add('dark') : b.add('light'); 11 | } 12 | })(); 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/core/useUnMount/index.ts: -------------------------------------------------------------------------------- 1 | import { Fn } from '@react-hooks-library/shared' 2 | import { useEffect } from 'react' 3 | 4 | /** 5 | * Run a function when component is unmounted. 6 | * 7 | * @deprecated This hook breaks in React 18's strict mode, since it's not idempotent 8 | * 9 | * @param callback function to be executed 10 | */ 11 | export function useUnMount(func: Fn) { 12 | useEffect(() => { 13 | return func 14 | }, [func]) 15 | } 16 | -------------------------------------------------------------------------------- /website/utils/useSidebar.ts: -------------------------------------------------------------------------------- 1 | import create from 'zustand' 2 | 3 | type SideBarStateProps = { 4 | sidebarOpen: boolean 5 | toggleSideBar: () => void 6 | setSideBar: (open: boolean) => void 7 | } 8 | 9 | export const useSidebar = create((set) => ({ 10 | sidebarOpen: true, 11 | toggleSideBar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })), 12 | setSideBar: (sidebarOpen: boolean) => set({ sidebarOpen }) 13 | })) 14 | -------------------------------------------------------------------------------- /.github/workflows/test_on_pull.yml: -------------------------------------------------------------------------------- 1 | name: Run tests on pull requests 2 | 3 | on: 4 | pull_request: 5 | branches: [main, dev] 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: '16.x' 16 | cache: 'yarn' 17 | - run: yarn install --frozen-lockfile 18 | - run: yarn test 19 | -------------------------------------------------------------------------------- /packages/core/useMouse/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useMouse } from '.' 2 | 3 | export function Demo() { 4 | const { x, y } = useMouse() 5 | 6 | return ( 7 |
8 |
9 | 10 | x: {x} 11 | 12 | 13 | y: {y} 14 | 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/usePreferredColorScheme/index.ts: -------------------------------------------------------------------------------- 1 | import { useMediaQuery } from '../useMediaQuery' 2 | 3 | export type ColorSchemeType = 'dark' | 'light' 4 | 5 | /** 6 | * Reactive prefers-color-scheme media query. 7 | * 8 | * @see https://react-hooks-library.vercel.app/core/usePreferredColorScheme 9 | */ 10 | export function usePreferredColorScheme() { 11 | const isDark = useMediaQuery('(prefers-color-scheme: dark)') 12 | 13 | return isDark ? 'dark' : 'light' 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/useMountSync/index.ts: -------------------------------------------------------------------------------- 1 | import { Fn } from '@react-hooks-library/shared' 2 | import { useLayoutEffect } from 'react' 3 | 4 | /** 5 | * Run a function synchronously when a component is mounted and after DOM is painted. 6 | * 7 | * @deprecated This hook breaks in React 18's strict mode, since it's not idempotent 8 | * 9 | * @param callback function to be executed 10 | */ 11 | export function useMountSync(callback: Fn) { 12 | useLayoutEffect(callback, []) 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/useHasMounted/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useMount } from '../useMount' 4 | 5 | /** 6 | * Hook that returns whether or not the component has mounted. 7 | * Useful in SSR frameworks like Next or Gatsby. 8 | * 9 | * @returns hasMounted 10 | */ 11 | export function useHasMounted() { 12 | const [hasMounted, setHasMounted] = useState(false) 13 | 14 | useMount(() => { 15 | setHasMounted(true) 16 | }) 17 | 18 | return hasMounted 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/usePrevious/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | /** 4 | * Returns the value of the argument from the previous render 5 | * @param {T} value 6 | * @returns {T | undefined} previous value 7 | * @see https://react-hooks-library.vercel.app/core/usePrevious 8 | */ 9 | export function usePrevious(value: T): T | undefined { 10 | const ref = useRef() 11 | 12 | useEffect(() => { 13 | ref.current = value 14 | }, [value]) 15 | 16 | return ref.current 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/_template/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useCounter } from '.' 2 | 3 | export function Demo() { 4 | const { dec, get, inc, reset, set } = useCounter(0) 5 | 6 | return ( 7 |
8 |
The value of count is - {get()}
9 | 10 | 11 | 12 | 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/useOnline/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useOnline } from '.' 2 | 3 | export function Demo() { 4 | const isOnline = useOnline() 5 | 6 | return ( 7 |
8 | {isOnline ? ( 9 |
Online
10 | ) : ( 11 |
Offline
12 | )} 13 | 14 | Toggle your network to observe changes 15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/usePreferredColorScheme/demo.tsx: -------------------------------------------------------------------------------- 1 | import { usePreferredColorScheme } from '.' 2 | 3 | export function Demo() { 4 | const colorScheme = usePreferredColorScheme() 5 | 6 | return ( 7 |
8 |
9 | Try switching system color preference to observe change 10 |
11 |
12 | Preferred color scheme is -{' '} 13 | {colorScheme} 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/usePrevious/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { usePrevious } from '.' 4 | 5 | export function Demo() { 6 | const [count, setCount] = useState(0) 7 | const previousCount = usePrevious(count) 8 | 9 | return ( 10 |
11 |
Current Count is - {count}
12 |
Previous Count is - {previousCount}
13 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types' 2 | 3 | const config: Config.InitialOptions = { 4 | clearMocks: true, 5 | testEnvironment: 'jsdom', 6 | coverageDirectory: 'coverage', 7 | roots: ['/packages', '/website'], 8 | testMatch: ['**/?(*.)+(test).+(ts|tsx|js|jsx)'], 9 | // TODO: Move to esbuild 10 | transform: { '^.+\\.(ts|tsx|jsx)$': 'ts-jest' }, 11 | testPathIgnorePatterns: ['/node_modules/', '/dist/'], 12 | modulePathIgnorePatterns: ['/dist/'] 13 | } 14 | 15 | export default config 16 | -------------------------------------------------------------------------------- /packages/shared/utils/functions.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject } from 'react' 2 | 3 | import { isRef } from './is' 4 | import type { MaybeRef } from './types' 5 | 6 | /** 7 | * Accepts either a ref object or a dom node and returns a dom node 8 | * 9 | * @param target - ref or a dom node 10 | * @returns dom noe 11 | */ 12 | export function unRef(target: MaybeRef): T { 13 | const element = isRef(target) 14 | ? (target as MutableRefObject).current 15 | : (target as T) 16 | 17 | return element 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/useMount/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useMount } from '.' 4 | 5 | function Child() { 6 | useMount(() => { 7 | alert('I have been mounted') 8 | }) 9 | 10 | return
11 | } 12 | 13 | export function Demo() { 14 | const [mounted, setMounted] = useState(false) 15 | 16 | return ( 17 |
18 | 21 | 22 | {mounted ? : null} 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | # next.js 11 | .next 12 | out 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | .eslintcache 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | .eslintcache 34 | # vercel 35 | .vercel 36 | dist -------------------------------------------------------------------------------- /website/ui/docs/mdxComponents.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-has-content */ 2 | import Link from 'next/link' 3 | 4 | import { Callout } from './Callout' 5 | import { Playground } from './Playground' 6 | import { Source } from './Source' 7 | 8 | export const mdxComponents = { 9 | Playground, 10 | Source, 11 | Callout, 12 | a({ href = '', ...props }) { 13 | return ( 14 | 15 | 16 | 17 | ) 18 | }, 19 | p(props) { 20 | return

21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/useIsSupported/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useMount } from '../useMount' 4 | 5 | /** 6 | * Is a feature supported in the browser or not 7 | * 8 | * @param predicate - predicate to check if the feature is supported 9 | * 10 | * @see https://react-hooks-library.vercel.app/core/useIsSupported 11 | */ 12 | export function useIsSupported(predicate: () => boolean) { 13 | const [isSupported, setIsSupported] = useState(false) 14 | 15 | useMount(() => { 16 | setIsSupported(predicate()) 17 | }) 18 | 19 | return isSupported 20 | } 21 | -------------------------------------------------------------------------------- /scripts/utils/testUtils.ts: -------------------------------------------------------------------------------- 1 | import matter from 'gray-matter' 2 | 3 | import type { FunctionMeta } from '../../types' 4 | 5 | export function testDocs(name: string, source: string) { 6 | const meta = matter(source).data as FunctionMeta 7 | 8 | expect(meta).toBeDefined() 9 | 10 | expect(meta).toHaveProperty('name') 11 | expect(meta).toHaveProperty('description') 12 | expect(meta).toHaveProperty('category') 13 | 14 | expect(meta.name).toBe(name) 15 | } 16 | 17 | export function sleep(time = 1000) { 18 | return new Promise((resolve) => setTimeout(resolve, time)) 19 | } 20 | -------------------------------------------------------------------------------- /website/ui/docs/Playground.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type Props = { children: React.ReactNode } 4 | 5 | export function Playground({ children }: Props) { 6 | return ( 7 |

8 |
9 | DEMO 10 |
11 |
12 | {children} 13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/useToggle/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useToggle } from '.' 2 | 3 | export function Demo() { 4 | const { bool, setFalse, setTrue, toggle } = useToggle() 5 | 6 | return ( 7 |
8 |
{bool ? '🐵' : '🙈'}
9 |
10 | 11 | 14 | 17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /website/ui/docs/Callout.tsx: -------------------------------------------------------------------------------- 1 | type CalloutProps = { 2 | type: 'info' | 'success' | 'warning' | 'danger' 3 | children: React.ReactNode 4 | } 5 | 6 | export function Callout({ type, children }: CalloutProps) { 7 | const emoji = 8 | type === 'info' 9 | ? '💡' 10 | : type === 'success' 11 | ? '✅' 12 | : type === 'warning' 13 | ? '⚠️' 14 | : '🚨' 15 | 16 | return ( 17 |
19 |
{emoji}
20 | {children} 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/useToggle/index.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | /** 4 | * A state toggle hook 5 | * 6 | * @param defaultValue 7 | * @default false 8 | * 9 | * @see https://react-hooks-library.vercel.app/core/useToggle 10 | */ 11 | export function useToggle(defaultValue = false) { 12 | const [bool, setBool] = useState(defaultValue) 13 | 14 | const toggle = useCallback(() => setBool((s) => !s), []) 15 | 16 | const setTrue = useCallback(() => setBool(true), []) 17 | 18 | const setFalse = useCallback(() => setBool(false), []) 19 | 20 | return { bool, toggle, setTrue, setFalse } 21 | } 22 | -------------------------------------------------------------------------------- /website/scripts/buildRoutes.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { join } from 'path' 3 | 4 | import { getAllFunctionsMeta } from '../utils/loadMDX' 5 | 6 | const indexPath = join(__dirname, '..', 'routes.json') 7 | 8 | async function buildRoutes() { 9 | const functionMeta = (await getAllFunctionsMeta()).map( 10 | ({ name, pkg, category, description }) => ({ 11 | name, 12 | category, 13 | route: `/${pkg}/${name}`, 14 | description 15 | }) 16 | ) 17 | 18 | fs.writeFileSync(indexPath, JSON.stringify(functionMeta, null, 2), { 19 | encoding: 'utf-8' 20 | }) 21 | } 22 | 23 | buildRoutes() 24 | -------------------------------------------------------------------------------- /packages/core/_template/index.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | export function useCounter(initialValue = 0) { 4 | const [count, setCount] = useState(initialValue) 5 | 6 | const inc = useCallback((by = 1) => setCount((c) => c + by), []) 7 | const dec = useCallback((by = 1) => setCount((c) => c - by), []) 8 | const get = () => count 9 | const set = useCallback((val: number) => setCount(val), []) 10 | const reset = useCallback( 11 | (val = initialValue) => { 12 | return set(val) 13 | }, 14 | [initialValue, set] 15 | ) 16 | 17 | return { count, inc, dec, get, set, reset } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/useOnline/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useEventListener } from '../useEventListener' 4 | import { useMount } from '../useMount' 5 | 6 | /** 7 | * Reactive online status 8 | * 9 | * @see https://react-hooks-library.vercel.app/core/useOnline 10 | */ 11 | export function useOnline() { 12 | const [online, setOnline] = useState(false) 13 | 14 | useMount(() => { 15 | setOnline(navigator.onLine) 16 | }) 17 | 18 | useEventListener('offline', () => { 19 | setOnline(false) 20 | }) 21 | 22 | useEventListener('online', () => { 23 | setOnline(true) 24 | }) 25 | 26 | return online 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/useMountSync/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useMountSync } from '.' 4 | 5 | function Child() { 6 | useMountSync(() => { 7 | const el = document.querySelector('#mount-sync') 8 | alert(`I have been mounted. I am a <${el?.tagName}> tag`) 9 | }) 10 | 11 | return 12 | } 13 | 14 | export function Demo() { 15 | const [mounted, setMounted] = useState(false) 16 | 17 | return ( 18 |
19 | 22 | 23 | {mounted ? : null} 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/useFont/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useFont } from '.' 2 | 3 | export function Demo() { 4 | const fontName = 'JetBrains Mono Custom' 5 | const { error, loaded, font } = useFont( 6 | fontName, 7 | '/fonts/JetBrainsMono-Thin.woff2' 8 | ) 9 | 10 | if (error) { 11 | return
Error loading font
12 | } 13 | 14 | if (!loaded) { 15 | return
Loading Font
16 | } 17 | 18 | return ( 19 |
20 |
21 | New Font Loaded!!! 22 |
23 |
Font Family - {font?.family}
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/useInterval/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | import { useInterval } from '.' 4 | 5 | export function Demo() { 6 | const [count, setCount] = useState(0) 7 | const [paused, setPaused] = useState(false) 8 | 9 | const increment = useCallback(() => setCount((c) => c + 1), []) 10 | 11 | useInterval(increment, 1000, { paused }) 12 | 13 | return ( 14 |
15 |
16 | Count - {count} 17 |
18 | 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/useActiveElement/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { _document } from '../_ssr.config' 4 | import { useEventListener } from '../useEventListener' 5 | 6 | /** 7 | * Reactive document.activeElement, returns a reference to current active element 8 | * 9 | * @returns current active element (DOM node) 10 | **/ 11 | export function useActiveElement() { 12 | const [activeElement, setActiveElement] = useState( 13 | () => _document?.activeElement 14 | ) 15 | 16 | useEventListener( 17 | 'focus', 18 | () => setActiveElement(_document?.activeElement), 19 | true 20 | ) 21 | 22 | useEventListener('blur', () => setActiveElement(null), true) 23 | 24 | return { activeElement } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/useDebounce/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | /** 4 | * Used to debounce a quickly changing value. 5 | * Will return the latest value after a specified amount of time. 6 | * 7 | * @param {T} value 8 | * @param timeout 9 | * @returns {Readonly} latest value 10 | * @see https://react-hooks-library.vercel.app/core/useDebounce 11 | */ 12 | export function useDebounce(value: T, timeout: number): Readonly { 13 | const [state, setState] = useState(value) 14 | 15 | useEffect(() => { 16 | const tick = setTimeout(() => setState(value), timeout) 17 | 18 | return () => clearTimeout(tick) 19 | }, [value, timeout]) 20 | 21 | if (timeout <= 0) return value 22 | return state 23 | } 24 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-hooks-library/shared", 3 | "version": "0.6.2", 4 | "description": "A collection of hooks and utilities for React", 5 | "publishConfig": { 6 | "registry": "https://registry.npmjs.org/" 7 | }, 8 | "keywords": [ 9 | "react", 10 | "react hooks", 11 | "@react-hooks-library" 12 | ], 13 | "sideEffects": false, 14 | "exports": { 15 | ".": { 16 | "import": "./index.esm.js", 17 | "require": "./index.cjs.js" 18 | }, 19 | "./*": "./*" 20 | }, 21 | "main": "./index.cjs.js", 22 | "types": "./index.d.ts", 23 | "module": "./index.esm.js", 24 | "license": "MIT", 25 | "author": "Arpit", 26 | "devDependencies": {} 27 | } -------------------------------------------------------------------------------- /website/ui/docs/Source.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | pkg: string 3 | name: string 4 | } 5 | 6 | export function Source({ name, pkg }: Props) { 7 | const baseUrl = 8 | 'https://github.com/react-hooks-library/react-hooks-library/blob/main/packages' 9 | 10 | const props = { 11 | target: '_blank', 12 | rel: 'noopener noreferrer' 13 | } 14 | return ( 15 |
16 | 17 | Source 18 | 19 | {' | '} 20 | 21 | Demo 22 | 23 | {' | '} 24 | 25 | Docs 26 | 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/useStateCompare/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useStateCompare } from '.' 2 | 3 | type State = { 4 | count: number 5 | } 6 | const compare = (oldState: State, newState: State) => 7 | oldState.count === newState.count ? oldState : newState 8 | 9 | export function Demo() { 10 | const [state, setState] = useStateCompare({ 11 | initialValue: () => ({ count: 0 }), 12 | compare 13 | }) 14 | 15 | return ( 16 |
17 |
18 | Count - {state.count} 19 |
20 |
21 | 24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/useUnMount/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: lifecycle 3 | name: useUnMount 4 | description: Run a function when component is unmounted. 5 | --- 6 | 7 | # useUnMount 8 | 9 | Run a function when component is unmounted. 10 | 11 | ## Usage 12 | 13 | ```tsx 14 | import { useUnMount } from '@react-hooks-library/core' 15 | 16 | function Demo() { 17 | useUnMount(() => { 18 | alert('I have been unMounted') 19 | }) 20 | 21 | return
22 | } 23 | ``` 24 | 25 | ## Type Declarations 26 | 27 | ```typescript 28 | /** 29 | * Run a function when component is unmounted. 30 | * 31 | * @param callback function to be executed 32 | */ 33 | declare function useUnMount(func: Fn): void; 34 | ``` 35 | 36 | ## Source 37 | 38 | 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "strictFunctionTypes": true, 11 | "declaration": true, 12 | "resolveJsonModule": true, 13 | "rootDir": ".", 14 | "baseUrl": ".", 15 | "jsx": "preserve", 16 | "skipLibCheck": true, 17 | "noUnusedLocals": true, 18 | "paths": {}, 19 | "allowJs": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "noEmit": true, 22 | "isolatedModules": true 23 | }, 24 | "include": ["packages"], 25 | "exclude": ["node_modules", "**/**/*.test.ts", "**/dist"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/useLocalStorage/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useLocalStorage } from '.' 2 | 3 | export function Demo() { 4 | const [value, setValue] = useLocalStorage( 5 | 'useLocalsStorageKey', 6 | 'Hello World' 7 | ) 8 | 9 | return ( 10 |
11 |
12 | State - {value} 13 |
14 |
15 | 18 | setValue(e.target.value)} 22 | /> 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/useSessionStorage/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useSessionStorage } from '.' 2 | 3 | export function Demo() { 4 | const [value, setValue] = useSessionStorage( 5 | 'useSessionStorageKey', 6 | 'Hello World' 7 | ) 8 | 9 | return ( 10 |
11 |
12 | State - {value} 13 |
14 |
15 | 18 | setValue(e.target.value)} 22 | /> 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import consola from 'consola' 3 | import { join, resolve } from 'path' 4 | 5 | import { version } from '../package.json' 6 | import { findFunctions } from './utils/findFunctions' 7 | 8 | execSync('yarn build:full', { stdio: 'inherit' }) 9 | 10 | let command = 'npm publish --access public' 11 | 12 | if (version.includes('beta')) command += ' --tag beta' 13 | 14 | const rootDir = resolve(__dirname, '..') 15 | const packagesDir = resolve(rootDir, 'packages') 16 | const packages = findFunctions(packagesDir) 17 | 18 | for (const pkg of packages) { 19 | execSync(command, { 20 | stdio: 'inherit', 21 | cwd: join('packages', pkg, 'dist') 22 | }) 23 | 24 | consola.success(`Published @react-hooks-library/${pkg}`) 25 | } 26 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@react-hooks-library/core": ["../packages/core/index.ts"], 20 | "@react-hooks-library/shared": ["../packages/shared/index.ts"] 21 | }, 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/useTitle/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useTitle } from '.' 2 | 3 | export function Demo() { 4 | const { title, setTitle } = useTitle() 5 | 6 | function setRawTitle() { 7 | const titleNode = document.head.querySelector('title') 8 | // This will also re-render the component 9 | if (titleNode) titleNode.innerText = 'Some New Title' 10 | } 11 | 12 | return ( 13 |
14 |
15 | Title - {title} 16 |
17 | setTitle(e.target.value)} 21 | className="mt-4" 22 | /> 23 | 24 | 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/useEffectAfterMount/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | /** 4 | * A useEffect hook does that not run on mount, but only on subsequent updates. 5 | * 6 | * @deprecated This hook breaks in React 18's strict mode, since it's not idempotent 7 | * 8 | * @param effect 9 | * @param deps 10 | * 11 | * @see https://react-hooks-library.vercel.app/core/useEffectAfterMount 12 | */ 13 | export function useEffectAfterMount( 14 | effect: React.EffectCallback, 15 | deps?: React.DependencyList | undefined 16 | ) { 17 | const isMounted = useRef(false) 18 | 19 | useEffect(() => { 20 | let cleanup: void | (() => void) = undefined 21 | 22 | if (isMounted.current) { 23 | cleanup = effect() 24 | } 25 | 26 | isMounted.current = true 27 | 28 | return cleanup 29 | }, deps) 30 | } 31 | -------------------------------------------------------------------------------- /website/ui/Color.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react' 2 | 3 | import { useTheme } from './ThemeProvider' 4 | 5 | export const Color = ({ className = '', varName = '' }) => { 6 | const [color, setColor] = useState('') 7 | const ref = useRef() 8 | const { theme } = useTheme() 9 | 10 | useEffect(() => { 11 | const color = getComputedStyle(ref.current).getPropertyValue( 12 | 'background-color' 13 | ) 14 | setColor(color) 15 | }, [theme]) 16 | 17 | return ( 18 |
19 |
22 |
23 |
{varName}
24 |
{color}
25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/useScreenShare/demo.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/media-has-caption */ 2 | import { useScreenShare } from '.' 3 | 4 | export function Demo() { 5 | const { play, ref, stop, isPlaying } = useScreenShare() 6 | 7 | return ( 8 |
9 | 18 |
19 | 22 | 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/useMount/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: lifecycle 3 | name: useMount 4 | description: Run a function when a component is mounted. 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # useMount 14 | 15 | Run a function when a component is mounted. 16 | 17 | ## Usage 18 | 19 | ```tsx 20 | import { useMount } from '@react-hooks-library/core' 21 | 22 | function Demo() { 23 | useMount(() => { 24 | alert('I have been mounted') 25 | }) 26 | 27 | return
28 | } 29 | 30 | ``` 31 | 32 | ## Type Declarations 33 | 34 | ```typescript 35 | /** 36 | * Run a function when a component is mounted. 37 | * 38 | * @param callback function to be executed 39 | */ 40 | declare function useMount(callback: Fn): void; 41 | ``` 42 | 43 | ## Source 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/core/useActiveElement/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useActiveElement } from '.' 2 | 3 | export function Demo() { 4 | const { activeElement } = useActiveElement() 5 | 6 | return ( 7 |
8 |
9 | Select inputs below to see the active element -{' '} 10 |
11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | Current active element is -{' '} 19 | 20 | {(activeElement as HTMLInputElement)?.placeholder || ''} 21 | 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/useEventListener/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react' 2 | 3 | import { useEventListener } from '.' 4 | 5 | export function Demo() { 6 | const buttonRef = useRef(null) 7 | const [clickCount, setClickCount] = useState(0) 8 | const [lastPressed, setLastPressed] = useState('null') 9 | 10 | useEventListener('keyup', (ev) => { 11 | setLastPressed(ev.key) 12 | }) 13 | 14 | useEventListener(buttonRef, 'click', () => { 15 | setClickCount((c) => c + 1) 16 | }) 17 | 18 | return ( 19 |
20 | 21 |
22 |
press any key -
23 |
24 | last pressed: {lastPressed} 25 |
26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/useAsyncCallback/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | 3 | import { useAsyncCallback } from '.' 4 | 5 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) 6 | 7 | export function Demo() { 8 | const fn = useCallback(async () => { 9 | await sleep(3000) 10 | return 5000 11 | }, []) 12 | 13 | const [fnState, fnCallback] = useAsyncCallback(fn) 14 | 15 | return ( 16 |
17 | {fnState.isLoading ? ( 18 |
Loading...
19 | ) : null} 20 | 21 | {fnState.error ? ( 22 |
An Error Occurred
23 | ) : null} 24 | 25 | {fnState.data ?
{fnState.data}
: null} 26 | 27 | 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/useOnline/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | name: useOnline 4 | description: Reactive online status 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # useOnline 14 | 15 | Reactive online status. 16 | 17 | ## Usage 18 | 19 | ```tsx 20 | import { useOnline } from '@react-hooks-library/core' 21 | 22 | export function Demo() { 23 | const isOnline = useOnline() 24 | 25 | return ( 26 |
27 | {isOnline ?
Online
:
Offline
} 28 | Toggle your network to observe changes 29 |
30 | ) 31 | } 32 | ``` 33 | 34 | ## Type Declarations 35 | 36 | ```typescript 37 | /** 38 | * Reactive online status 39 | * 40 | * @see https://react-hooks-library.vercel.app/core/useOnline 41 | */ 42 | declare function useOnline(): boolean 43 | ``` 44 | 45 | ## Source 46 | 47 | 48 | -------------------------------------------------------------------------------- /packages/core/useHover/index.ts: -------------------------------------------------------------------------------- 1 | import { MaybeRef, unRef } from '@react-hooks-library/shared' 2 | import { useEffect, useState } from 'react' 3 | 4 | /** 5 | * 6 | * Detect if a dom element is hovered 7 | * 8 | * @param target - The element to listen to 9 | * @returns 10 | */ 11 | export function useHover(target: MaybeRef) { 12 | const [isHovered, setIsHovered] = useState(false) 13 | 14 | useEffect(() => { 15 | const el = unRef(target) 16 | 17 | if (!el) return 18 | 19 | const onMouseEnter = () => setIsHovered(true) 20 | const onMouseLeave = () => setIsHovered(false) 21 | 22 | el.addEventListener('mouseenter', onMouseEnter) 23 | el.addEventListener('mouseleave', onMouseLeave) 24 | 25 | return () => { 26 | el.removeEventListener('mouseenter', onMouseEnter) 27 | el.removeEventListener('mouseleave', onMouseLeave) 28 | } 29 | }, [target]) 30 | 31 | return isHovered 32 | } 33 | -------------------------------------------------------------------------------- /website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const colors = require('tailwindcss/colors') 3 | 4 | module.exports = { 5 | mode: 'jit', 6 | purge: [ 7 | './pages/**/*.{jsx,tsx}', 8 | './ui/**/*.{jsx,tsx}', 9 | '../packages/**/demo.{jsx,tsx}' 10 | ], 11 | darkMode: 'class', 12 | theme: { 13 | extend: { 14 | colors: { 15 | // enable all tailwind colors (in jit mode) 16 | ...colors, 17 | brand: 'var(--brand)', 18 | 'bg-1': 'var(--bg-1)', 19 | 'bg-2': 'var(--bg-2)', 20 | 'fg-1': 'var(--fg-1)', 21 | 'txt-1': 'var(--txt-1)', 22 | 'txt-2': 'var(--txt-2)' 23 | }, 24 | fontFamily: { 25 | mono: '"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;' 26 | } 27 | } 28 | }, 29 | variants: { 30 | extend: {} 31 | }, 32 | plugins: [] 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/useNetwork/demo.tsx: -------------------------------------------------------------------------------- 1 | import { isNumber } from '@react-hooks-library/shared' 2 | 3 | import { NetworkEffectiveType, useNetwork } from '.' 4 | 5 | export function Demo() { 6 | const network = useNetwork() 7 | 8 | const getClass = (value: number | boolean | NetworkEffectiveType) => 9 | `pill ${ 10 | value === undefined 11 | ? 'opacity-60' 12 | : value || isNumber(value) 13 | ? 'active' 14 | : 'danger' 15 | }` 16 | 17 | return ( 18 |
19 |
20 | {Object.entries(network).map(([key, value]) => ( 21 |
24 |
25 | {key}: 26 |
27 |
{`${value}` ?? 'unknown'}
28 |
29 | ))} 30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-hooks-library/core", 3 | "version": "0.6.2", 4 | "description": "A collection of hooks and utilities for React", 5 | "publishConfig": { 6 | "registry": "https://registry.npmjs.org/" 7 | }, 8 | "keywords": [ 9 | "react", 10 | "react hooks", 11 | "@react-hooks-library" 12 | ], 13 | "sideEffects": false, 14 | "exports": { 15 | ".": { 16 | "import": "./index.esm.js", 17 | "require": "./index.cjs.js", 18 | "types": "./index.d.ts" 19 | }, 20 | "./*": "./*" 21 | }, 22 | "main": "./index.cjs.js", 23 | "types": "./index.d.ts", 24 | "module": "./index.esm.js", 25 | "license": "MIT", 26 | "author": "Arpit", 27 | "dependencies": { 28 | "@react-hooks-library/shared": "0.6.2" 29 | }, 30 | "devDependencies": { 31 | "@types/css-font-loading-module": "^0.0.6" 32 | }, 33 | "peerDependencies": { 34 | "react": ">=16.9.0" 35 | } 36 | } -------------------------------------------------------------------------------- /packages/core/useTitle/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { _document } from '../_ssr.config' 4 | import { useMount } from '../useMount' 5 | import { useMutationObserver } from '../useMutationObserver' 6 | 7 | /** 8 | * Reactive document title hook 9 | * 10 | * Set title or observe dom mutation reactively 11 | * 12 | * @param newTitle optional 13 | * @see https://react-hooks-library.vercel.app/core/useTitle 14 | */ 15 | export function useTitle(newTitle?: string) { 16 | const [title, setTitle] = useState(newTitle ?? '') 17 | 18 | useMount(() => { 19 | setTitle((newTitle || _document?.title) ?? '') 20 | }) 21 | 22 | useEffect(() => { 23 | document.title = title 24 | }, [title]) 25 | 26 | useMutationObserver( 27 | _document?.head.querySelector('title'), 28 | () => { 29 | if (document.title !== title) setTitle(document.title) 30 | }, 31 | { childList: true } 32 | ) 33 | 34 | return { title, setTitle } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/useFont/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | /** 4 | * React FontFace, a hook to load fonts asynchronously 5 | * 6 | * @param family 7 | * @param source 8 | * @param descriptors 9 | * 10 | * @see https://react-hooks-library.vercel.app/core/useFont 11 | */ 12 | export function useFont( 13 | family: string, 14 | source: string | Blob, 15 | descriptors?: FontFaceDescriptors 16 | ) { 17 | const [loaded, setLoaded] = useState(true) 18 | const [error, setError] = useState(false) 19 | const [font, setFont] = useState(null) 20 | 21 | useEffect(() => { 22 | const font = new FontFace(family, `url(${source})`, descriptors) 23 | 24 | setFont(font) 25 | setLoaded(false) 26 | 27 | font 28 | .load() 29 | .then(() => document.fonts.add(font)) 30 | .catch(() => setError(true)) 31 | .finally(() => setLoaded(true)) 32 | }, [descriptors, family, source]) 33 | 34 | return { loaded, error, font } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/useMediaStream/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useMediaStream } from '.' 2 | 3 | export function Demo() { 4 | const { ref, pause, isPaused, restart, resume, play, stop, isPlaying } = 5 | useMediaStream() 6 | 7 | return ( 8 |
9 | 17 |
18 | 21 | 24 | 25 | 28 | 31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /website/ui/SEO.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultSeo } from 'next-seo' 2 | 3 | export const SEO: React.FC = () => { 4 | return ( 5 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/useActiveElement/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | name: useActiveElement 4 | description: Reactive document.activeElement 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # useActiveElement 14 | 15 | Reactive document.activeElement, returns a reference to current active element, the DOM node. 16 | 17 | ## Usage 18 | 19 | ```tsx 20 | import { useActiveElement } from '@react-hooks-library/core' 21 | 22 | export function Demo() { 23 | const { activeElement } = useActiveElement() 24 | 25 | return
{activeElement?.tagName}
26 | } 27 | ``` 28 | 29 | ## Type Declarations 30 | 31 | ```typescript 32 | /** 33 | * Reactive document.activeElement, returns a reference to current active element 34 | * 35 | * @returns current active element (DOM node) 36 | **/ 37 | declare function useActiveElement(): { 38 | activeElement: Element | null; 39 | }; 40 | ``` 41 | 42 | ## Source 43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/core/usePreferredColorScheme/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: browser 3 | name: usePreferredColorScheme 4 | description: Reactive prefers color scheme. 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # useRouter 14 | 15 | Reactive prefers color scheme. 16 | 17 | ## Usage 18 | 19 | ```tsx 20 | import { usePreferredColorScheme } from '@react-hooks-library/core' 21 | 22 | export function Demo() { 23 | const colorScheme = usePreferredColorScheme() 24 | 25 | return
Color Scheme is - {colorScheme}
26 | } 27 | ``` 28 | 29 | ## Type Declarations 30 | 31 | ```typescript 32 | declare type ColorSchemeType = 'dark' | 'light' 33 | 34 | /** 35 | * Reactive prefers-color-scheme media query. 36 | * 37 | * @see https://react-hooks-library.vercel.app/core/usePreferredColorScheme 38 | */ 39 | declare function usePreferredColorScheme(): "dark" | "light" 40 | 41 | ``` 42 | 43 | ## Source 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/core/usePrevious/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: state 3 | name: usePrevious 4 | description: Find previous value of a state 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # usePrevious 14 | 15 | Returns the value of the argument from the previous render 16 | 17 | ## Usage 18 | 19 | ```tsx 20 | import { useState } from 'react' 21 | import { usePrevious } from '@react-hooks-library/core' 22 | 23 | export function Demo() { 24 | const [count, setCount] = useState(0) 25 | const previousCount = usePrevious(count) 26 | 27 | return ( 28 |
29 |
Current Count is - {count}
30 |
Previous Count is - {previousCount}
31 | 32 |
33 | ) 34 | } 35 | ``` 36 | 37 | ## Type Declarations 38 | 39 | ```typescript 40 | declare function usePrevious(value: T): T | undefined; 41 | ``` 42 | 43 | ## Source 44 | 45 | -------------------------------------------------------------------------------- /packages/core/useMediaQuery/demo.tsx: -------------------------------------------------------------------------------- 1 | import { useMediaQuery } from '.' 2 | 3 | export function Demo() { 4 | const isLargeScreen = useMediaQuery('(min-width: 1024px)') 5 | const prefersDark = useMediaQuery('(prefers-color-scheme: dark)') 6 | 7 | const getClass = (condition: boolean) => 8 | `mx-4 pill ${condition ? 'active' : 'danger'}` 9 | 10 | return ( 11 |
12 |
13 |
14 | Try resizing the window to observe change 15 |
16 | min-width: 1024px 17 | {`${isLargeScreen}`} 18 |
19 |
20 |
21 | Try switching system color preference to observe change 22 |
23 | prefers-color-scheme: dark 24 | {`${prefersDark}`} 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/useWindowSize/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { useEventListener } from '../useEventListener' 4 | import { useMount } from '../useMount' 5 | 6 | export interface WindowSizeOptions { 7 | initialWidth?: number 8 | initialHeight?: number 9 | } 10 | 11 | /** 12 | * Reactive window size. 13 | * 14 | * @param options 15 | * 16 | * @see https://react-hooks-library.vercel.app/core/useWindowSize 17 | */ 18 | export function useWindowSize({ 19 | initialWidth = Infinity, 20 | initialHeight = Infinity 21 | }: WindowSizeOptions = {}) { 22 | const [width, setWidth] = useState(initialWidth) 23 | const [height, setHeight] = useState(initialHeight) 24 | 25 | useMount(() => { 26 | setWidth(window.innerWidth) 27 | setHeight(window.innerHeight) 28 | }) 29 | 30 | useEventListener( 31 | 'resize', 32 | () => { 33 | setWidth(window.innerWidth) 34 | setHeight(window.innerHeight) 35 | }, 36 | { passive: true } 37 | ) 38 | 39 | return { width, height } 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/useHover/docs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | category: sensors 3 | name: useHover 4 | description: Detect if a dom element is hovered 5 | --- 6 | 7 | import { Demo } from './demo.tsx' 8 | 9 | 10 | 11 | 12 | 13 | # useHover 14 | 15 | Detect if a dom element is hovered. 16 | 17 | ## Usage 18 | 19 | ```tsx 20 | import { useRef } from 'react' 21 | import { useHover } from '@react-hooks-library/core' 22 | 23 | export function Demo() { 24 | const ref = useRef(null) 25 | const isHovered = useHover(ref) 26 | 27 | return ( 28 |
29 | {isHovered ? 'Hovered' : 'Not Hovered'} 30 |
31 | ) 32 | } 33 | ``` 34 | 35 | ## Type Declarations 36 | 37 | ```typescript 38 | /** 39 | * 40 | * Detect if a dom element is hovered 41 | * 42 | * @param target - The element to listen to 43 | * @returns 44 | */ 45 | declare function useHover(target: MaybeRef): boolean; 46 | ``` 47 | 48 | ## Source 49 | 50 | 51 | -------------------------------------------------------------------------------- /website/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | import Document, { Head, Html, Main, NextScript } from 'next/document' 3 | 4 | class MyDocument extends Document { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |