├── .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 |
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 |
inc()}>Increment
10 |
dec()}>Decrement
11 |
reset()}>Reset
12 |
set(-1000)}>Set to -1000
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 |
setCount((c) => c + 1)} className="mt-4">
14 | Increment
15 |
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 | setMounted((b) => !b)}>
19 | {mounted ? 'Mounted' : 'UnMounted'}
20 |
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 | Toggle
11 |
12 | On
13 |
14 |
15 | Off
16 |
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 | setMounted((b) => !b)}>
20 | {mounted ? 'Mounted' : 'UnMounted'}
21 |
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 |
setPaused((p) => !p)}>
19 | {paused ? 'Unpause' : 'Pause'}
20 |
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 |
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 | setState({ count: state.count + 1 })}>
22 | Increment
23 |
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 |
16 | Enter some text to update state and localStorage
17 |
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 |
16 | Enter some text to update state and localStorage
17 |
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 |
25 | set raw title
26 |
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 |
20 | Start Screen Sharing
21 |
22 |
23 | Stop Screen Sharing
24 |
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 |
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 |
button clicked {clickCount} times!
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 |
Call Function
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 |
19 | Start
20 |
21 |
22 | Stop
23 |
24 | Restart
25 |
26 | Pause
27 |
28 |
29 | Resume
30 |
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 |
setCount((c) => c + 1)}>Increment
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 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
27 | export default MyDocument
28 |
--------------------------------------------------------------------------------
/packages/core/useClickOutside/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 |
3 | import { useClickOutside } from '.'
4 |
5 | function Dropdown(props: {
6 | isOpen: boolean
7 | setIsOpen: (isOpen: boolean) => void
8 | }) {
9 | const { isOpen, setIsOpen } = props
10 | const ref = useRef(null)
11 |
12 | useClickOutside(ref, () => {
13 | setIsOpen(false)
14 | })
15 |
16 | if (!isOpen) return null
17 |
18 | return (
19 |
22 | This is a dropdown, click outside to close
23 |
24 | )
25 | }
26 |
27 | export function Demo() {
28 | const [isOpen, setIsOpen] = useState(false)
29 |
30 | return (
31 |
32 |
33 | setIsOpen(true)} disabled={isOpen}>
34 | Open Dropdown
35 |
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/website/public/styles/fonts.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Inter var';
3 | font-weight: 100 900;
4 | font-display: swap;
5 | font-style: normal;
6 | font-named-instance: 'Regular';
7 | src: url(/fonts/Inter-roman.var.woff2) format('woff2');
8 | }
9 |
10 | @font-face {
11 | font-family: 'Inter var';
12 | font-weight: 100 900;
13 | font-display: swap;
14 | font-style: italic;
15 | font-named-instance: 'Italic';
16 | src: url(/fonts/Inter-italic.var.woff2) format('woff2');
17 | }
18 |
19 | @font-face {
20 | font-family: 'JetBrains Mono';
21 | font-display: swap;
22 | font-weight: 400 600;
23 | src: url(/fonts/JetBrainsMono-Regular.woff2) format('woff2');
24 | }
25 |
26 | @font-face {
27 | font-family: 'JetBrains Mono';
28 | font-display: swap;
29 | font-weight: 100 300;
30 | src: url(/fonts/JetBrainsMono-Thin.woff2) format('woff2');
31 | }
32 |
33 | @font-face {
34 | font-family: 'JetBrains Mono';
35 | font-display: swap;
36 | font-weight: 700 900;
37 | src: url(/fonts/JetBrainsMono-Bold.woff2) format('woff2');
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/useDebounce/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | import { useDebounce } from '.'
4 |
5 | export function Demo() {
6 | const [text, setText] = useState('Hello')
7 | const [delay, setDelay] = useState(1000)
8 |
9 | const debouncedText = useDebounce(text, delay)
10 |
11 | return (
12 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/useScrollIntoView/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react'
2 |
3 | import routes from '../../../website/routes.json'
4 | import { useScrollIntoView } from '.'
5 |
6 | type ListItemProps = { isActive: boolean; name: string }
7 |
8 | function ListItem({ isActive, name }: ListItemProps) {
9 | const activeEl = useRef(null)
10 |
11 | useScrollIntoView(activeEl, {
12 | scrollMargin: '8rem',
13 | block: 'end',
14 | behavior: 'smooth',
15 | predicate: isActive
16 | })
17 |
18 | return (
19 |
22 | {name}
23 |
24 | )
25 | }
26 |
27 | export function Demo() {
28 | return (
29 |
30 |
31 | {routes.map(({ name }) => (
32 |
37 | ))}
38 |
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/website/docs/getting-started.mdx:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | React Hooks Library is a collection of [hooks](https://reactjs.org/docs/hooks-intro.html) and utilities for React.
4 | It is currently available for use in Node environments and works with any react meta framework like next, vite, astro.
5 | Support for Deno and browsers is coming soon.
6 |
7 |
8 | The library is currently not v1 yet, while it's mostly stable, some aspects of
9 | the API may change.
10 |
11 |
12 | ## Installation
13 |
14 | ```bash
15 | npm i @react-hooks-library/core
16 | ```
17 |
18 | # Usage
19 |
20 | ```tsx
21 | import {
22 | useMediaStream,
23 | useKeyDown,
24 | useOnline
25 | } from '@react-hooks-library/core'
26 |
27 | export function Demo() {
28 | const isOnline = useOnline()
29 |
30 | useKeyDown(['w', 'W', 'ArrowUp'], (e) => {
31 | console.log('w or W or ArrowUp was pressed')
32 | })
33 |
34 | const { ref, pause, resume, play, stop } = useMediaStream()
35 |
36 | return Hello World
37 | }
38 | ```
39 |
40 | Read the list of [all function](/functions) to know more.
41 |
--------------------------------------------------------------------------------
/packages/core/useHasMounted/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useHasMounted
4 | description: Hook that returns whether or not the component has mounted
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useHasMounted
14 |
15 | Hook that returns whether or not the component has mounted.
16 | Useful in SSR frameworks like Next or Gatsby.
17 |
18 | ## Usage
19 |
20 | ```tsx
21 | import { useHasMounted } from '@react-hooks-library/core'
22 |
23 | export function Demo() {
24 | const hasMounted = useHasMounted()
25 |
26 | return (
27 |
28 | {hasMounted ? 'Mounted' : 'Not Yet Mounted'}
29 |
30 | )
31 | }
32 | ```
33 |
34 | ## Type Declarations
35 |
36 | ```typescript
37 | /**
38 | * Hook that returns whether or not the component has mounted.
39 | * Useful in SSR frameworks like Next or Gatsby.
40 | *
41 | * @returns hasMounted
42 | */
43 | declare function useHasMounted(): boolean
44 | ```
45 |
46 | ## Source
47 |
48 |
49 |
--------------------------------------------------------------------------------
/packages/core/useWindowSize/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useWindowSize
4 | description: Reactive window size
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useWindowSize
14 |
15 | Reactive window size
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useWindowSize } from '@react-hooks-library/core'
21 |
22 | function Demo() {
23 | const { height, width } = useWindowSize()
24 |
25 | return (
26 |
27 | Dimensions - {height} x {width}
28 |
29 | )
30 | }
31 | ```
32 |
33 | ## Type Declarations
34 |
35 | ```typescript
36 | interface WindowSizeOptions {
37 | initialWidth?: number
38 | initialHeight?: number
39 | }
40 | /**
41 | * Reactive window size.
42 | *
43 | * @param options
44 | *
45 | * @see https://react-hooks-library.vercel.app/core/useWindowSize
46 | */
47 | declare function useWindowSize({
48 | initialWidth,
49 | initialHeight
50 | }?: WindowSizeOptions): {
51 | width: number
52 | height: number
53 | }
54 | ```
55 |
56 | ## Source
57 |
58 |
59 |
--------------------------------------------------------------------------------
/packages/core/useInterval/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react'
2 |
3 | type UseIntervalOptions = {
4 | immediate: boolean
5 | paused: boolean
6 | }
7 |
8 | /**
9 | * Run a function repeatedly at a specified interval.
10 | *
11 | * @see https://react-hooks-library.vercel.app/core/useInterval
12 | */
13 | export function useInterval void>(
14 | callback: T,
15 | delay: number,
16 | options?: Partial
17 | ) {
18 | const { immediate = false, paused = false } = options || {}
19 | const savedCallback = useRef(callback)
20 | const tickId = useRef()
21 |
22 | useEffect(() => {
23 | savedCallback.current = callback
24 |
25 | if (!paused && immediate) {
26 | callback()
27 | }
28 | }, [callback, immediate, paused])
29 |
30 | useEffect(() => {
31 | if (tickId.current && paused) {
32 | clearInterval(tickId.current)
33 | return
34 | }
35 |
36 | tickId.current = setInterval(() => savedCallback.current(), delay)
37 |
38 | return () => tickId.current && clearInterval(tickId.current)
39 | }, [delay, paused])
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Arpit Bharti
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.
--------------------------------------------------------------------------------
/packages/core/useEffectAfterMount/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | import { useEffectAfterMount } from '.'
4 |
5 | export function Demo() {
6 | const [count, setCount] = useState(0)
7 | const [firstEffect, setFirstEffect] = useState(false)
8 | const [secondEffect, setSecondEffect] = useState(false)
9 |
10 | useEffect(() => {
11 | setFirstEffect(true)
12 | }, [count])
13 |
14 | useEffectAfterMount(() => {
15 | setSecondEffect(true)
16 |
17 | return () => console.log('run cleanup')
18 | }, [count])
19 |
20 | return (
21 |
22 |
23 | First effect -{' '}
24 |
25 | {firstEffect ? 'ran' : 'did not run'}
26 |
27 |
28 |
29 | Second effect -{' '}
30 |
31 | {secondEffect ? 'ran' : 'did not run'}
32 |
33 |
34 |
setCount((c) => c + 1)}>Rerender
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/useStateCompare/index.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction, useCallback, useState } from 'react'
2 |
3 | export type UseStateCompare = {
4 | initialValue: T | (() => T)
5 | compare: (oldValue: T, newValue: T) => T
6 | }
7 |
8 | /**
9 | * useState hook with custom compare function to avoid re-rendering
10 | * when state is the same, compares with previous state
11 | *
12 | *
13 | * Note: create a custom compare function, outside of the hook to keep
14 | * a stable reference, otherwise it will be recreated on every render
15 | *
16 | * @see https://react-hooks-library.vercel.app/core/useStateCompare
17 | */
18 | export function useStateCompare({
19 | initialValue,
20 | compare
21 | }: UseStateCompare): [T, Dispatch>] {
22 | const [state, _setState] = useState(initialValue)
23 |
24 | const setState: Dispatch> = useCallback(
25 | (value: T | ((prevState: T) => T)) => {
26 | typeof value === 'function'
27 | ? _setState(value)
28 | : _setState((oldValue) => compare(oldValue, value))
29 | },
30 | [compare]
31 | )
32 |
33 | return [state, setState]
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/useIsSupported/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useIsSupported
4 | description: Is a feature supported in the browser or not
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useIsSupported
14 |
15 | Is a feature supported in the browser or not.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useIsSupported } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const isSupported = useIsSupported(() => !!navigator?.connection)
24 |
25 | return isSupported ? (
26 | `navigator.connection` Is supported
27 | ) : (
28 | `navigator.connection` Is not supported
29 | )
30 | }
31 | ```
32 |
33 | ## Type Declarations
34 |
35 | ```typescript
36 | /**
37 | * Is a feature supported in the browser or not
38 | *
39 | * @param predicate - predicate to check if the feature is supported
40 | *
41 | * @see https://react-hooks-library.vercel.app/core/useIsSupported
42 | */
43 | declare function useIsSupported(predicate: () => boolean): boolean
44 | ```
45 |
46 | ## Source
47 |
48 |
49 |
--------------------------------------------------------------------------------
/packages/core/useMutationObserver/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 |
3 | import { useMutationObserver } from '.'
4 |
5 | export function Demo() {
6 | const ref = useRef(null)
7 | const [observed, setObserved] = useState(false)
8 |
9 | useMutationObserver(
10 | ref,
11 | (mutations) => {
12 | for (const mutation of mutations) {
13 | if (mutation.type === 'attributes') {
14 | setObserved(true)
15 | }
16 | }
17 | },
18 | { attributes: true }
19 | )
20 |
21 | const addAttribute = () => {
22 | if (!ref.current) return
23 |
24 | ref.current.setAttribute('data-mut', 'hello world')
25 | }
26 |
27 | return (
28 |
29 |
30 | {observed
31 | ? 'Observed attribute change to node'
32 | : 'No changes observed yet'}
33 |
34 |
35 |
36 | {observed ? 'Added Attribute To Node' : 'Add Attribute To Node'}
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/packages/core/useMountSync/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: lifecycle
3 | name: useMountSync
4 | description: Run a function synchronously when a component is mounted but after DOM is painted.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useMountSync
14 |
15 | Run a function synchronously when a component is mounted and after DOM is painted.
16 | Useful when accessing the DOM or creating event listeners.
17 |
18 | ## Usage
19 |
20 | ```tsx
21 | import { useMountSync } from '@react-hooks-library/core'
22 |
23 | function Demo() {
24 | useMountSync(() => {
25 | const el = document.querySelector('#mount-sync')
26 | alert(`I have been mounted. I am a <${el?.tagName}> tag`)
27 | })
28 |
29 | return
30 | }
31 | ```
32 |
33 | ## Type Declarations
34 |
35 | ```typescript
36 | /**
37 | * Run a function synchronously when a component is mounted and after DOM is painted.
38 | *
39 | * @param callback function to be executed
40 | */
41 | declare function useMountSync(callback: Fn): void;
42 | ```
43 |
44 | ## Source
45 |
46 |
47 |
--------------------------------------------------------------------------------
/packages/core/usePrevious/index.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks'
2 | import fs from 'fs/promises'
3 | import { join } from 'path'
4 |
5 | import { testDocs } from '../../../scripts/utils/testUtils'
6 | import { usePrevious } from '.'
7 |
8 | const FunctionName = usePrevious.name
9 |
10 | describe(FunctionName, () => {
11 | test('should be defined', () => {
12 | expect(usePrevious).toBeDefined()
13 | })
14 |
15 | test('should have docs with appropriate meta data', async () => {
16 | const source = await fs.readFile(join(__dirname, '/docs.mdx'), 'utf-8')
17 | testDocs(FunctionName, source)
18 | })
19 |
20 | test('should update the return previous value', () => {
21 | let count = 1
22 | const { result, rerender } = renderHook(() => usePrevious(count))
23 |
24 | expect(result.current).toBe(undefined)
25 |
26 | rerender()
27 | expect(result.current).toBe(1)
28 |
29 | count = 100
30 | rerender()
31 | expect(result.current).toBe(1)
32 |
33 | count = -1000
34 | rerender()
35 | expect(result.current).toBe(100)
36 |
37 | rerender()
38 | expect(result.current).toBe(-1000)
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/packages/core/useIntersectionObserver/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react'
2 |
3 | import { useIntersectionObserver } from '.'
4 |
5 | export function Demo() {
6 | const outer = useRef(null)
7 | const inner = useRef(null)
8 |
9 | const { inView } = useIntersectionObserver(inner, { root: outer })
10 |
11 | return (
12 |
13 |
14 | {inView ? (
15 |
Visible
16 | ) : (
17 |
Not Visible
18 | )}
19 |
20 |
23 |
Scroll Down
24 |
25 |
28 | Inner Box
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/useMediaQuery/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | import { useMount } from '../useMount'
4 |
5 | /**
6 | * Reactive media query hook that returns the truthy value of the media query.
7 | *
8 | * @param {string} query
9 | * @returns {boolean} boolean value of the query
10 | *
11 | * @see https://react-hooks-library.vercel.app/core/useMediaQuery
12 | */
13 | export function useMediaQuery(query: string): boolean {
14 | const [matches, setMatches] = useState(false)
15 |
16 | useMount(() => {
17 | setMatches(window.matchMedia(query).matches)
18 | })
19 |
20 | useEffect(() => {
21 | const mediaQuery = window.matchMedia(query)
22 | const handler = (event: MediaQueryListEvent) => {
23 | setMatches(event.matches)
24 | }
25 |
26 | // Add event listener for old safari browsers
27 | 'addEventListener' in mediaQuery
28 | ? mediaQuery.addEventListener('change', handler)
29 | : mediaQuery.addListener(handler)
30 |
31 | return () => {
32 | 'addEventListener' in mediaQuery
33 | ? mediaQuery.removeEventListener('change', handler)
34 | : mediaQuery.removeListener(handler)
35 | }
36 | }, [query])
37 |
38 | return matches
39 | }
40 |
--------------------------------------------------------------------------------
/packages/core/useLocation/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: browser
3 | name: useLocation
4 | description: Reactive browser location.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useLocation
14 |
15 | Reactive browser location.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useLocation } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const router = useLocation()
24 |
25 | return (
26 |
27 |
28 | {JSON.stringify(router, null, 2)}
29 |
30 |
31 | )
32 | }
33 | ```
34 |
35 | ## Type Declarations
36 |
37 | ```typescript
38 | interface Location {
39 | trigger: string
40 | state?: any
41 | length?: number
42 | hash?: string
43 | host?: string
44 | hostname?: string
45 | href?: string
46 | origin?: string
47 | pathname?: string
48 | port?: string
49 | protocol?: string
50 | search?: string
51 | }
52 | /**
53 | * Reactive browser location.
54 | *
55 | * @see https://react-hooks-library.vercel.app/core/useLocation
56 | *
57 | */
58 | declare function useLocation(): Location | null
59 | ```
60 |
61 | ## Source
62 |
63 |
64 |
--------------------------------------------------------------------------------
/packages/core/useToggle/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useToggle
4 | description: A state toggle hook
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useToggle
14 |
15 | A state toggle hook
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useToggle } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const { bool, setFalse, setTrue, toggle } = useToggle()
24 |
25 | return (
26 |
27 |
{bool ? '🐵' : '🙈'}
28 |
29 | Toggle
30 | On
31 | Off
32 |
33 |
34 | )
35 | }
36 | ```
37 |
38 | ## Type Declarations
39 |
40 | ```typescript
41 | /**
42 | * A state toggle hook
43 | *
44 | * @param defaultValue
45 | * @default false
46 | *
47 | * @see https://react-hooks-library.vercel.app/core/useToggle
48 | */
49 | declare function useToggle(defaultValue?: boolean): {
50 | bool: boolean
51 | toggle: () => void
52 | setTrue: () => void
53 | setFalse: () => void
54 | }
55 | ```
56 |
57 | ## Source
58 |
59 |
60 |
--------------------------------------------------------------------------------
/scripts/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import fg from 'fast-glob'
2 | import { join } from 'path'
3 | import { RollupOptions } from 'rollup'
4 | import dts from 'rollup-plugin-dts'
5 | import typescript from 'rollup-plugin-typescript2'
6 |
7 | const packages = fg.sync('*', {
8 | cwd: join('.', 'packages'),
9 | onlyDirectories: true
10 | })
11 |
12 | const config = packages
13 | .map((pkg): RollupOptions[] => [
14 | {
15 | input: `packages/${pkg}/index.ts`,
16 | output: [
17 | {
18 | file: `packages/${pkg}/dist/index.cjs.js`,
19 | format: 'cjs'
20 | },
21 | {
22 | file: `packages/${pkg}/dist/index.esm.js`,
23 | format: 'esm'
24 | }
25 | ],
26 | plugins: [
27 | typescript({
28 | tsconfigOverride: {
29 | compilerOptions: {
30 | declaration: false
31 | }
32 | }
33 | })
34 | ],
35 | external: ['react']
36 | },
37 | {
38 | input: `packages/${pkg}/index.ts`,
39 | output: [
40 | {
41 | file: `packages/${pkg}/dist/index.d.ts`,
42 | format: 'esm'
43 | }
44 | ],
45 | plugins: [dts()]
46 | }
47 | ])
48 | .flat()
49 |
50 | export default config
51 |
--------------------------------------------------------------------------------
/packages/core/BreakPointHooks/demo.tsx:
--------------------------------------------------------------------------------
1 | import { BreakPointHooks, breakpointsTailwind } from '.'
2 |
3 | const { useGreater, useBetween, isGreater } =
4 | BreakPointHooks(breakpointsTailwind)
5 |
6 | export function Demo() {
7 | const greater = useGreater('md')
8 | const between = useBetween('md', 'lg')
9 |
10 | const greaterNonReactive = isGreater('lg')
11 |
12 | const getClass = (condition: boolean) =>
13 | `mx-4 pill ${condition ? 'active' : 'danger'}`
14 |
15 | return (
16 |
17 |
18 | Try resizing the window to observe change
19 |
20 |
21 | Size greater than "md" -
22 | {`${greater}`}
23 |
24 |
25 | Size greater than "md" (non reactive) -
26 | {`${greaterNonReactive}`}
30 |
31 |
32 | Size between "md" and "lg"-
33 | {`${between}`}
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/BreakPointHooks/breakpoints.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Breakpoints from Tailwind V2
3 | *
4 | * @see https://tailwindcss.com/docs/breakpoints
5 | */
6 | export const breakpointsTailwind = {
7 | sm: 640,
8 | md: 768,
9 | lg: 1024,
10 | xl: 1280,
11 | '2xl': 1536
12 | }
13 |
14 | /**
15 | * Breakpoints from Bootstrap V5
16 | *
17 | * @see https://getbootstrap.com/docs/5.0/layout/breakpoints
18 | */
19 | export const breakpointsBootstrapV5 = {
20 | sm: 576,
21 | md: 768,
22 | lg: 992,
23 | xl: 1200,
24 | xxl: 1400
25 | }
26 |
27 | /**
28 | * Breakpoints from Vuetify V2
29 | *
30 | * @see https://vuetifyjs.com/en/features/breakpoints
31 | */
32 | export const breakpointsVuetify = {
33 | xs: 600,
34 | sm: 960,
35 | md: 1264,
36 | lg: 1904
37 | }
38 |
39 | /**
40 | * Breakpoints from Ant Design
41 | *
42 | * @see https://ant.design/components/layout/#breakpoint-width
43 | */
44 | export const breakpointsAntDesign = {
45 | xs: 480,
46 | sm: 576,
47 | md: 768,
48 | lg: 992,
49 | xl: 1200,
50 | xxl: 1600
51 | }
52 |
53 | /**
54 | * Sematic Breakpoints
55 | */
56 | export const breakpointsSematic = {
57 | mobileS: 320,
58 | mobileM: 375,
59 | mobileL: 425,
60 | tablet: 768,
61 | laptop: 1024,
62 | laptopL: 1440,
63 | desktop4K: 2560
64 | }
65 |
--------------------------------------------------------------------------------
/packages/core/useScroll/index.ts:
--------------------------------------------------------------------------------
1 | import { MaybeRef, round, unRef } from '@react-hooks-library/shared'
2 |
3 | import { _document } from '../_ssr.config'
4 | import { useEventListener } from '../useEventListener'
5 |
6 | /**
7 | * Reactive scroll values for a react ref or a dom node
8 | *
9 | * @param target - dom node or react ref
10 | * @param callback - callback to run on scroll
11 | *
12 | * @see https://react-hooks-library.vercel.app/core/useScroll
13 | */
14 | export function useScroll(
15 | target: MaybeRef = _document?.documentElement,
16 | callback: (coords: { scrollX: number; scrollY: number }) => void
17 | ) {
18 | const getPositions = () => {
19 | const el = unRef(target)
20 | if (!el) return
21 |
22 | return {
23 | x: round(el.scrollLeft / (el.scrollWidth - el.clientWidth)),
24 | y: round(el.scrollTop / (el.scrollHeight - el.clientHeight))
25 | }
26 | }
27 |
28 | useEventListener(
29 | target,
30 | 'scroll',
31 | () => {
32 | const newScrollValues = getPositions()
33 | if (!newScrollValues) return
34 |
35 | const { x, y } = newScrollValues
36 | callback({ scrollX: x, scrollY: y })
37 | },
38 | {
39 | capture: false,
40 | passive: true
41 | }
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/packages/core/useScroll/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 |
3 | import { useScroll } from '.'
4 |
5 | export function Demo() {
6 | const box = useRef(null)
7 | const [scroll, setScroll] = useState({ x: 0, y: 0 })
8 |
9 | useScroll(box, ({ scrollX, scrollY }) =>
10 | setScroll({ x: scrollX, y: scrollY })
11 | )
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | Horizontally scrolled -{' '}
19 | {(scroll.x * 100).toFixed(0)}%
20 |
21 |
22 | Vertically scrolled -{' '}
23 | {(scroll.y * 100).toFixed(0)}%
24 |
25 |
26 |
27 |
30 |
31 | Scroll Vertically and Horizontally
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/useStateHistory/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useStateHistory } from '.'
2 |
3 | const uid = () =>
4 | Date.now().toString(36) + Math.random().toString(36).substring(2)
5 |
6 | export function Demo() {
7 | const { state, undo, redo, push, history, reset, redoAllowed } =
8 | useStateHistory({ num: 0, id: uid() }, { maxHistory: 5 })
9 |
10 | return (
11 |
12 |
13 | Current State: {state.num}
14 |
15 |
16 |
17 | {history.map(({ num, id }) => (
18 |
23 | {num}
24 |
25 | ))}
26 |
27 |
28 |
29 | push({ num: state.num + 2, id: uid() })}>
30 | Push
31 |
32 | Undo
33 |
34 | Redo
35 |
36 | Reset
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/packages/core/useDebounce/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: utilities
3 | name: useDebounce
4 | description: Used to debounce a quickly changing value. Will return the latest value after a specified amount of time.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useDebounce
14 |
15 | Used to debounce a quickly changing value. Will return the latest value after a specified amount of time.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useState } from 'react'
21 | import { useDebounce } from '@react-hooks-library/core'
22 |
23 | export function Demo() {
24 | const [text, setText] = useState('Hello World')
25 | const debouncedText = useDebounce(text, 1000)
26 |
27 | return (
28 |
29 |
setText(e.target.value)}
32 | defaultValue={text}
33 | />
34 |
Debounced Value - {debouncedText}
35 |
36 | )
37 | }
38 | ```
39 |
40 | ## Type Declarations
41 |
42 | ```typescript
43 | declare function useDebounce(value: T, timeout: number): Readonly
44 | ```
45 |
46 | ## Recommended Reading
47 |
48 | - [Debounce Vs Throttle: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
49 |
50 | ## Source
51 |
52 |
53 |
--------------------------------------------------------------------------------
/packages/core/index.ts:
--------------------------------------------------------------------------------
1 | // This file is auto generated. Do not edit manually.
2 |
3 | export * from './BreakPointHooks'
4 | export * from './useActiveElement'
5 | export * from './useAsyncCallback'
6 | export * from './useClickOutside'
7 | export * from './useDebounce'
8 | export * from './useEffectAfterMount'
9 | export * from './useEventListener'
10 | export * from './useFont'
11 | export * from './useHasMounted'
12 | export * from './useHover'
13 | export * from './useIntersectionObserver'
14 | export * from './useInterval'
15 | export * from './useIsSupported'
16 | export * from './useKeyStroke'
17 | export * from './useLocalStorage'
18 | export * from './useLocation'
19 | export * from './useMediaQuery'
20 | export * from './useMediaStream'
21 | export * from './useMount'
22 | export * from './useMountSync'
23 | export * from './useMouse'
24 | export * from './useMutationObserver'
25 | export * from './useNetwork'
26 | export * from './useOnline'
27 | export * from './usePreferredColorScheme'
28 | export * from './usePrevious'
29 | export * from './useScreenShare'
30 | export * from './useScroll'
31 | export * from './useScrollIntoView'
32 | export * from './useSessionStorage'
33 | export * from './useStateCompare'
34 | export * from './useStateHistory'
35 | export * from './useTitle'
36 | export * from './useToggle'
37 | export * from './useUnMount'
38 | export * from './useWindowSize'
39 |
--------------------------------------------------------------------------------
/packages/core/useAsyncCallback/index.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react'
2 |
3 | export type UseAsyncState = {
4 | data: T | undefined
5 | error: boolean
6 | isSuccess: boolean
7 | isLoading: boolean
8 | }
9 |
10 | /**
11 | * Returns a current execution state of an async function.
12 | * Use it to orchestrate async actions in UI.
13 | *
14 | * @see https://react-hooks-library.vercel.app/core/useAsyncCallback
15 | */
16 | export function useAsyncCallback(
17 | callback: (...args: Args) => Promise
18 | ): [UseAsyncState, (...args: Args) => Promise] {
19 | const [isLoading, setIsLoading] = useState(false)
20 | const [isSuccess, setIsSuccess] = useState(false)
21 | const [error, setError] = useState(false)
22 | const [data, setData] = useState()
23 |
24 | const _callback = useCallback(
25 | async (...args: Args) => {
26 | try {
27 | setIsLoading(true)
28 | const results = await callback(...args)
29 | setData(results)
30 | setIsSuccess(true)
31 |
32 | return results
33 | } catch (e) {
34 | setError(true)
35 | throw e
36 | } finally {
37 | setIsLoading(false)
38 | }
39 | },
40 | [callback]
41 | )
42 |
43 | return [{ data, error, isLoading, isSuccess }, _callback]
44 | }
45 |
--------------------------------------------------------------------------------
/packages/core/useKeyStroke/demo.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | import { useKeyDown } from '.'
4 |
5 | export function Demo() {
6 | const [posX, setPosX] = useState(0)
7 | const [posY, setPosY] = useState(0)
8 |
9 | useKeyDown(['w', 'W', 'ArrowUp'], (e) => {
10 | setPosY((y) => y - 20)
11 | e.preventDefault()
12 | })
13 | useKeyDown(['s', 'S', 'ArrowDown'], (e) => {
14 | setPosY((y) => y + 20)
15 | e.preventDefault()
16 | })
17 |
18 | useKeyDown(['a', 'A', 'ArrowLeft'], (e) => {
19 | setPosX((x) => x - 20)
20 | e.preventDefault()
21 | })
22 | useKeyDown(['d', 'D', 'ArrowRight'], (e) => {
23 | setPosX((x) => x + 20)
24 | e.preventDefault()
25 | })
26 |
27 | return (
28 |
29 |
30 | Use
31 | w
32 | a
33 | s
34 | d
35 | or
36 | ↑
37 | ←
38 | ↓
39 | →
40 | to move the orb
41 |
42 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-hooks-library/website",
3 | "version": "0.6.2",
4 | "description": "Documentation website for react",
5 | "author": "Arpit",
6 | "license": "MIT",
7 | "keywords": [
8 | "react",
9 | "reacthooks"
10 | ],
11 | "scripts": {
12 | "dev": "next dev",
13 | "build": "npm run build:routes && next build",
14 | "postbuild": "next-sitemap",
15 | "start": "next start",
16 | "build:routes": "esno ./scripts/buildRoutes.ts"
17 | },
18 | "dependencies": {
19 | "framer-motion": "^5.0.0",
20 | "next": "12.0.1",
21 | "next-seo": "^4.28.1",
22 | "react": "^17.0.2",
23 | "react-dom": "^17.0.2",
24 | "zustand": "^3.6.1"
25 | },
26 | "devDependencies": {
27 | "@types/node": "16.11.6",
28 | "@types/react": "17.0.33",
29 | "@types/react-dom": "17.0.10",
30 | "autoprefixer": "^10.4.0",
31 | "eslint": "^8.1.0",
32 | "eslint-config-next": "^12.0.1",
33 | "esno": "^0.10.1",
34 | "fast-glob": "^3.2.7",
35 | "gray-matter": "^4.0.3",
36 | "mdx-bundler": "^6.0.2",
37 | "next-pwa": "^5.4.0",
38 | "next-sitemap": "^1.6.192",
39 | "postcss": "^8.3.11",
40 | "rehype-autolink-headings": "^6.1.0",
41 | "rehype-slug": "^5.0.0",
42 | "remark-gfm": "^3.0.0",
43 | "remark-prism": "^1.3.6",
44 | "tailwindcss": "^2.2.17",
45 | "typescript": "4.4.4"
46 | }
47 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | parserOptions: {
5 | ecmaVersion: 2020,
6 | sourceType: 'module',
7 | ecmaFeatures: {
8 | jsx: true
9 | }
10 | },
11 | settings: {
12 | react: {
13 | version: 'detect'
14 | }
15 | },
16 | env: {
17 | browser: true,
18 | amd: true,
19 | node: true
20 | },
21 | plugins: ['simple-import-sort'],
22 | extends: [
23 | 'eslint:recommended',
24 | 'plugin:@typescript-eslint/eslint-recommended',
25 | 'plugin:@typescript-eslint/recommended',
26 | 'plugin:jsx-a11y/recommended',
27 | 'next',
28 | 'plugin:prettier/recommended'
29 | ],
30 | rules: {
31 | 'prettier/prettier': ['error', {}, { usePrettierrc: true }],
32 | 'react/react-in-jsx-scope': 'off',
33 | 'react/prop-types': 'off',
34 | '@typescript-eslint/explicit-function-return-type': 'off',
35 | 'simple-import-sort/imports': 'error',
36 | 'simple-import-sort/exports': 'error',
37 | 'react-hooks/rules-of-hooks': 'error',
38 | 'react-hooks/exhaustive-deps': 'warn',
39 | '@typescript-eslint/explicit-module-boundary-types': 'off',
40 | 'jsx-a11y/anchor-is-valid': [
41 | 'error',
42 | {
43 | components: ['Link'],
44 | specialLink: ['hrefLeft', 'hrefRight'],
45 | aspects: ['invalidHref', 'preferButton']
46 | }
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/core/useInterval/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useInterval
4 | description: Run a function repeatedly at a specified interval
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useInterval
14 |
15 | Run a function repeatedly at a specified interval.
16 | Use the `immediate` option to run the function immediately when it is updated.
17 |
18 | ## Usage
19 |
20 | ```tsx
21 | import { useInterval } from '@react-hooks-library/core'
22 |
23 | function Demo() {
24 | const [count, setCount] = useState(0)
25 |
26 | const increment = useCallback(() => setCount((c) => c + 1), [])
27 |
28 | useInterval(increment, 1000)
29 |
30 | return Count - {count}
31 | }
32 | ```
33 |
34 | ### Options
35 |
36 | - `immediate` - when true, the callback will execute on before setting a timer
37 | - `paused` - pause the execution of your callback
38 |
39 | ## Type Declarations
40 |
41 | ```typescript
42 | declare type UseIntervalOptions = {
43 | immediate: boolean
44 | paused: boolean
45 | }
46 | /**
47 | * Run a function repeatedly at a specified interval.
48 | *
49 | * @see https://react-hooks-library.vercel.app/core/useInterval
50 | */
51 | declare function useInterval void>(
52 | callback: T,
53 | delay: number,
54 | options?: Partial
55 | ): void
56 | ```
57 |
58 | ## Source
59 |
60 |
61 |
--------------------------------------------------------------------------------
/packages/core/useDebounce/index.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks'
2 | import fs from 'fs/promises'
3 | import { join } from 'path'
4 |
5 | import { testDocs } from '../../../scripts/utils/testUtils'
6 | import { useDebounce } from '.'
7 |
8 | const FunctionName = useDebounce.name
9 |
10 | describe(FunctionName, () => {
11 | test('should be defined', () => {
12 | expect(useDebounce).toBeDefined()
13 | })
14 |
15 | test('should have docs with appropriate meta data', async () => {
16 | const source = await fs.readFile(join(__dirname, '/docs.mdx'), 'utf-8')
17 | testDocs(FunctionName, source)
18 | })
19 |
20 | test('should update the value', async () => {
21 | let value = 'Hello World'
22 | let delay = 1000
23 | const { result, rerender, waitForValueToChange } = renderHook(() =>
24 | useDebounce(value, delay)
25 | )
26 |
27 | expect(result.current).toBe('Hello World')
28 |
29 | value = 'Goodbye World'
30 | rerender()
31 |
32 | expect(result.current).toBe('Hello World')
33 | await waitForValueToChange(() => result.current, { timeout: delay + 10 })
34 | expect(result.current).toBe('Goodbye World')
35 |
36 | value = 'Hello Again'
37 | delay = 100
38 | rerender()
39 |
40 | expect(result.current).toBe('Goodbye World')
41 | await waitForValueToChange(() => result.current, { timeout: delay + 10 })
42 | expect(result.current).toBe('Hello Again')
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/packages/shared/utils/is.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-types */
2 |
3 | /**
4 | * Check if we're on the server or client side
5 | */
6 | export const isClient = typeof window !== 'undefined'
7 |
8 | /**
9 | * Check if object is a react ref
10 | */
11 | export const isRef = (obj: unknown): boolean =>
12 | obj !== null &&
13 | typeof obj === 'object' &&
14 | Object.prototype.hasOwnProperty.call(obj, 'current')
15 |
16 | const toString = Object.prototype.toString
17 |
18 | export const isBoolean = (val: any): val is boolean => typeof val === 'boolean'
19 |
20 | export const isFunction = (val: any): val is T =>
21 | typeof val === 'function'
22 |
23 | export const isNumber = (val: any): val is number => typeof val === 'number'
24 |
25 | export const isString = (val: unknown): val is string => typeof val === 'string'
26 |
27 | export const isObject = (val: any): val is object =>
28 | toString.call(val) === '[object Object]'
29 |
30 | export const isWindow = (val: any): val is Window =>
31 | typeof window !== 'undefined' && toString.call(val) === '[object Window]'
32 |
33 | // eslint-disable-next-line @typescript-eslint/no-empty-function
34 | export const noop = () => {}
35 |
36 | export const rand = (min: number, max: number) => {
37 | min = Math.ceil(min)
38 | max = Math.floor(max)
39 | return Math.floor(Math.random() * (max - min + 1)) + min
40 | }
41 |
42 | export const round = (num: number) => Math.round(num * 1e2) / 1e2
43 |
--------------------------------------------------------------------------------
/website/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "React Hooks Library",
3 | "short_name": "React Hooks Library",
4 | "theme_color": "#ffffff",
5 | "background_color": "#004740",
6 | "display": "fullscreen",
7 | "orientation": "portrait",
8 | "scope": "/",
9 | "start_url": "/",
10 | "icons": [
11 | {
12 | "src": "icons/maskable_icon_x48.png",
13 | "sizes": "48x48",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "icons/maskable_icon_x72.png",
18 | "sizes": "72x72",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "icons/maskable_icon_x96.png",
23 | "sizes": "96x96",
24 | "type": "image/png"
25 | },
26 | {
27 | "src": "icons/maskable_icon_x128.png",
28 | "sizes": "128x128",
29 | "type": "image/png"
30 | },
31 | {
32 | "src": "icons/maskable_icon_x192.png",
33 | "sizes": "192x192",
34 | "type": "image/png"
35 | },
36 | {
37 | "src": "icons/maskable_icon_x192.png",
38 | "sizes": "384x384",
39 | "type": "image/png"
40 | },
41 | {
42 | "src": "icons/maskable_icon.png",
43 | "sizes": "456x456",
44 | "type": "image/png",
45 | "purpose": "any maskable"
46 | }
47 | ],
48 | "splash_pages": null
49 | }
--------------------------------------------------------------------------------
/website/pages/getting-started.tsx:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import { getMDXComponent } from 'mdx-bundler/client'
3 | import { InferGetStaticPropsType } from 'next'
4 | import { NextSeo } from 'next-seo'
5 | import path from 'path'
6 | import { useMemo } from 'react'
7 | import { mdxComponents } from 'ui/docs/mdxComponents'
8 | import { loadMdx } from 'utils/loadMDX'
9 |
10 | export async function getStaticProps() {
11 | const docsDir = path.resolve(process.cwd(), 'docs')
12 |
13 | const mdxSource = fs.readFileSync(
14 | path.join(docsDir, 'getting-started.mdx'),
15 | 'utf8'
16 | )
17 |
18 | const post = await loadMdx(mdxSource)
19 | return { props: post }
20 | }
21 |
22 | type Props = InferGetStaticPropsType
23 |
24 | export default function GettingStarted({ code }: Props) {
25 | const CodeComponent = useMemo(() => getMDXComponent(code), [code])
26 |
27 | return (
28 | <>
29 |
39 |
40 |
41 |
42 | >
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/packages/core/useClickOutside/index.ts:
--------------------------------------------------------------------------------
1 | import type { MaybeRef } from '@react-hooks-library/shared'
2 | import { unRef } from '@react-hooks-library/shared'
3 | import { useCallback } from 'react'
4 |
5 | import { _window } from '../_ssr.config'
6 | import { useEventListener } from '../useEventListener'
7 |
8 | export type ClickOutsideEvents = Pick<
9 | WindowEventMap,
10 | | 'mousedown'
11 | | 'mouseup'
12 | | 'touchstart'
13 | | 'touchend'
14 | | 'pointerdown'
15 | | 'pointerup'
16 | >
17 | export interface ClickOutsideOptions {
18 | event?: E
19 | }
20 |
21 | /**
22 | * Listen for clicks outside of an element.
23 | *
24 | * @param target
25 | * @param handler
26 | * @param options
27 | *
28 | * @see https://react-hooks-library.vercel.app/core/onClickOutside
29 | */
30 | export function useClickOutside<
31 | E extends keyof ClickOutsideEvents = 'pointerdown'
32 | >(
33 | target: MaybeRef,
34 | handler: (evt: ClickOutsideEvents[E]) => void,
35 | options: ClickOutsideOptions = {}
36 | ) {
37 | const { event = 'pointerdown' } = options
38 |
39 | const listener = useCallback(
40 | (event: ClickOutsideEvents[E]) => {
41 | const el = unRef(target)
42 | if (!el) return
43 |
44 | if (el === event.target || event.composedPath().includes(el)) return
45 |
46 | handler(event)
47 | },
48 | [handler, target]
49 | )
50 |
51 | return useEventListener(_window, event, listener, { passive: true })
52 | }
53 |
--------------------------------------------------------------------------------
/.github/workflows/publish_release.yml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM and Release To Github
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*
7 |
8 | jobs:
9 | test:
10 | name: Test
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v2
15 | with:
16 | node-version: '16.x'
17 | cache: 'yarn'
18 | - run: yarn install --frozen-lockfile
19 | - run: yarn test
20 |
21 | publish-npm:
22 | name: Publish to NPM
23 | needs: test
24 | runs-on: ubuntu-latest
25 |
26 | steps:
27 | - uses: actions/checkout@v2
28 | - uses: actions/setup-node@v2
29 | with:
30 | node-version: '16.x'
31 | registry-url: 'https://registry.npmjs.org'
32 | - run: yarn install --frozen-lockfile
33 | - run: yarn publish:packages
34 | env:
35 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
36 |
37 | release-github:
38 | name: Release To Github
39 | needs: test
40 | runs-on: ubuntu-latest
41 | steps:
42 | - name: Get the version
43 | id: get_version
44 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
45 | - uses: actions/checkout@v2
46 | - uses: "marvinpinto/action-automatic-releases@latest"
47 | with:
48 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
49 | automatic_release_tag: "latest"
50 | prerelease: false
51 | title: "Release ${{ steps.get_version.outputs.VERSION }}"
52 |
--------------------------------------------------------------------------------
/packages/core/useMediaQuery/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: browser
3 | name: useMediaQuery
4 | description: Reactive media query hook that returns the truthy value of the media query.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useMediaQuery
14 |
15 | Reactive media query hook that returns the truthy value of the media query.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useMediaQuery } from '@react-hooks-library/core'
21 |
22 | function Demo() {
23 | const isLargeScreen = useMediaQuery('(min-width: 1024px)')
24 | const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')
25 | }
26 |
27 | return (
28 | <>
29 |
30 | {isLargeScreen ? 'The screen is large' : 'The screen is small'}
31 |
32 |
33 | {prefersDark ? 'User prefers dark mode' : 'User prefers light mode'}
34 |
35 | >
36 | )
37 | ```
38 |
39 | ## Type Declarations
40 |
41 | ```typescript
42 | /**
43 | * Reactive media query hook that returns the truthy value of the media query.
44 | *
45 | * @param {string} query
46 | * @returns {boolean} boolean value of the query
47 | *
48 | * @see https://react-hooks-library.vercel.app/core/useMediaQuery
49 | */
50 | declare function useMediaQuery(query: string): boolean
51 | ```
52 |
53 | ## Recommended Reading
54 |
55 | - [Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries)
56 |
57 | ## Source
58 |
59 |
60 |
--------------------------------------------------------------------------------
/packages/core/useFont/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: browser
3 | name: useFont
4 | description: React FontFace, a hook to load fonts asynchronously
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useFont
14 |
15 | React [FontFace](https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace), a hook to load fonts asynchronously.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useFont } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const fontName = 'JetBrains Mono Custom'
24 | const { error, loaded, font } = useFont(
25 | fontName,
26 | '/fonts/JetBrainsMono-Thin.woff2'
27 | )
28 |
29 | if (error) {
30 | return Error loading font
31 | }
32 |
33 | if (!loaded) {
34 | return Loading Font
35 | }
36 |
37 | return (
38 |
39 |
New Font Loaded!!!
40 |
Font Family - {font?.family}
41 |
42 | )
43 | }
44 | ```
45 |
46 | ## Type Declarations
47 |
48 | ```typescript
49 | /**
50 | * React FontFace, a hook to load fonts asynchronously
51 | *
52 | * @param family
53 | * @param source
54 | * @param descriptors
55 | *
56 | * @see https://react-hooks-library.vercel.app/core/useFont
57 | */
58 | declare function useFont(
59 | family: string,
60 | source: string | Blob,
61 | descriptors?: FontFaceDescriptors
62 | ): {
63 | loaded: boolean
64 | error: boolean
65 | font: FontFace | null
66 | }
67 | ```
68 |
69 | ## Source
70 |
71 |
72 |
--------------------------------------------------------------------------------
/scripts/release.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process'
2 | import consola from 'consola'
3 | import { readFileSync, writeFileSync } from 'fs'
4 | import { basename, resolve } from 'path'
5 |
6 | import { findFunctions } from './utils/findFunctions'
7 |
8 | const { version: oldVersion } = JSON.parse(
9 | readFileSync('package.json', 'utf-8')
10 | )
11 |
12 | execSync('npx bumpp', { stdio: 'inherit' })
13 |
14 | const { version } = JSON.parse(readFileSync('package.json', 'utf-8'))
15 |
16 | if (oldVersion === version) {
17 | console.log('canceled')
18 | process.exit()
19 | }
20 |
21 | const rootDir = resolve(__dirname, '..')
22 | const packagesDir = resolve(rootDir, 'packages')
23 | let packages = findFunctions(packagesDir)
24 |
25 | packages = [
26 | ...packages.map((p) => resolve(packagesDir, p)),
27 | resolve(rootDir, 'website')
28 | ]
29 |
30 | for (const pkg of packages) {
31 | const packageJson = JSON.parse(readFileSync(`${pkg}/package.json`, 'utf-8'))
32 |
33 | packageJson.version = version
34 |
35 | if (packageJson.dependencies?.['@react-hooks-library/shared']) {
36 | packageJson.dependencies['@react-hooks-library/shared'] = version
37 | }
38 |
39 | writeFileSync(`${pkg}/package.json`, JSON.stringify(packageJson, null, 2))
40 |
41 | consola.success(
42 | `Bumped package version for @react-hooks-library/${basename(pkg)}`
43 | )
44 | }
45 |
46 | execSync('git add .', { stdio: 'inherit' })
47 | execSync(`git commit -m "v${version}"`, { stdio: 'inherit' })
48 | execSync(`git tag -a v${version} -m "v${version}"`, { stdio: 'inherit' })
49 | execSync(`git push --follow-tags`, { stdio: 'inherit' })
50 |
--------------------------------------------------------------------------------
/packages/core/useLocalStorage/index.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react'
2 | import { useState } from 'react'
3 |
4 | import { useMount } from '../useMount'
5 |
6 | export type UseLocalStorageOptions = {
7 | /**
8 | * Function for converting to string.
9 | *
10 | * @default JSON.stringify
11 | */
12 | serialize: (value: unknown) => string
13 |
14 | /**
15 | * Function to convert stored string to object value.
16 | *
17 | * @default JSON.parse
18 | */
19 | deserialize: (value: string) => unknown
20 | }
21 |
22 | /**
23 | * Modified `useState` hook that syncs with localStorage.
24 | *
25 | * @param key
26 | * @param initialValue
27 | * @param options
28 | *
29 | * @see https://react-hooks-library.vercel.app/core/useLocalStorage
30 | */
31 | export function useLocalStorage(
32 | key: string,
33 | initialValue: T,
34 | options?: UseLocalStorageOptions
35 | ): [T, (value: T) => void] {
36 | const [storedValue, setStoredValue] = useState(initialValue)
37 | const { deserialize = JSON.parse, serialize = JSON.stringify } = options || {}
38 |
39 | useMount(() => {
40 | try {
41 | const item = localStorage.getItem(key)
42 | item && setStoredValue(deserialize(item))
43 | } catch (error) {
44 | console.error(error)
45 | }
46 | })
47 |
48 | const setValue = useCallback(
49 | (value: T) => {
50 | try {
51 | localStorage.setItem(key, serialize(value))
52 | setStoredValue(value)
53 | } catch (error) {
54 | console.error(error)
55 | }
56 | },
57 | [key, serialize]
58 | )
59 |
60 | return [storedValue, setValue]
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/useLocation/index.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | import { _window } from '../_ssr.config'
4 | import { useEventListener } from '../useEventListener'
5 | import { useMount } from '../useMount'
6 |
7 | export interface Location {
8 | trigger: string
9 | state?: any
10 | length?: number
11 | hash?: string
12 | host?: string
13 | hostname?: string
14 | href?: string
15 | origin?: string
16 | pathname?: string
17 | port?: string
18 | protocol?: string
19 | search?: string
20 | }
21 |
22 | const buildState = (trigger: string): Location => {
23 | const { state, length } = _window?.history || {}
24 | const {
25 | hash,
26 | host,
27 | hostname,
28 | href,
29 | origin,
30 | pathname,
31 | port,
32 | protocol,
33 | search
34 | } = _window?.location || {}
35 |
36 | return {
37 | trigger,
38 | state,
39 | length,
40 | hash,
41 | host,
42 | hostname,
43 | href,
44 | origin,
45 | pathname,
46 | port,
47 | protocol,
48 | search
49 | }
50 | }
51 |
52 | /**
53 | * Reactive browser location.
54 | *
55 | * @see https://react-hooks-library.vercel.app/core/useLocation
56 | *
57 | */
58 | export function useLocation() {
59 | const [state, setState] = useState(null)
60 |
61 | useMount(() => {
62 | setState(buildState('load'))
63 | })
64 |
65 | useEventListener('popstate', () => setState(buildState('popstate')), {
66 | passive: true
67 | })
68 |
69 | useEventListener('hashchange', () => setState(buildState('hashchange')), {
70 | passive: true
71 | })
72 |
73 | return state
74 | }
75 |
--------------------------------------------------------------------------------
/packages/core/useMutationObserver/index.ts:
--------------------------------------------------------------------------------
1 | import { MaybeRef, unRef } from '@react-hooks-library/shared'
2 | import { useCallback, useEffect, useRef } from 'react'
3 |
4 | import { _window } from '../_ssr.config'
5 | import { useIsSupported } from '../useIsSupported'
6 | import { useUnMount } from '../useUnMount'
7 |
8 | /**
9 | * Watch for changes being made to the DOM tree.
10 | *
11 | * @param target - React ref or DOM node
12 | * @param callback - callback to execute when mutations are observed
13 | * @param options - Options passed to mutation observer
14 | *
15 | * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver MutationObserver MDN
16 | * @see https://react-hooks-library.vercel.app/core/useMutationObserver
17 | */
18 | export function useMutationObserver(
19 | target: MaybeRef,
20 | callback: MutationCallback,
21 | options: MutationObserverInit = {}
22 | ) {
23 | const observer = useRef(null)
24 | const isSupported = useIsSupported(() => !!_window?.IntersectionObserver)
25 |
26 | const stop = useCallback(() => {
27 | if (!observer.current) return
28 |
29 | observer.current.disconnect()
30 | observer.current = null
31 | }, [])
32 |
33 | useUnMount(stop)
34 |
35 | useEffect(() => {
36 | const el = unRef(target)
37 |
38 | if (!(isSupported && el && _window)) return
39 |
40 | observer.current = new _window.MutationObserver(callback)
41 | observer.current?.observe(el, options)
42 |
43 | return stop
44 | }, [callback, stop, options, target, isSupported])
45 |
46 | return {
47 | isSupported,
48 | stop
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/core/useSessionStorage/index.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react'
2 | import { useState } from 'react'
3 |
4 | import { useMount } from '../useMount'
5 |
6 | export type UseSessionStorageOptions = {
7 | /**
8 | * Function for converting to string.
9 | *
10 | * @default JSON.stringify
11 | */
12 | serialize: (value: unknown) => string
13 |
14 | /**
15 | * Function to convert stored string to object value.
16 | *
17 | * @default JSON.parse
18 | */
19 | deserialize: (value: string) => unknown
20 | }
21 |
22 | /**
23 | * Modified `useState` hook that syncs with useSessionStorage.
24 | *
25 | * @param key
26 | * @param initialValue
27 | * @param options
28 | *
29 | * @see https://react-hooks-library.vercel.app/core/useSessionStorage
30 | */
31 | export function useSessionStorage(
32 | key: string,
33 | initialValue: T,
34 | options?: UseSessionStorageOptions
35 | ): [T, (value: T) => void] {
36 | const [storedValue, setStoredValue] = useState(initialValue)
37 | const { deserialize = JSON.parse, serialize = JSON.stringify } = options || {}
38 |
39 | useMount(() => {
40 | try {
41 | const item = sessionStorage.getItem(key)
42 | item && setStoredValue(deserialize(item))
43 | } catch (error) {
44 | console.error(error)
45 | }
46 | })
47 |
48 | const setValue = useCallback(
49 | (value: T) => {
50 | try {
51 | setStoredValue(value)
52 | sessionStorage.setItem(key, serialize(value))
53 | } catch (error) {
54 | console.error(error)
55 | }
56 | },
57 | [key, serialize]
58 | )
59 |
60 | return [storedValue, setValue]
61 | }
62 |
--------------------------------------------------------------------------------
/website/pages/[package]/[function].tsx:
--------------------------------------------------------------------------------
1 | import { getMDXComponent } from 'mdx-bundler/client'
2 | import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
3 | import { NextSeo } from 'next-seo'
4 | import { useMemo } from 'react'
5 | import { mdxComponents } from 'ui/docs/mdxComponents'
6 | import { getAllFunctionsMeta, getFunction } from 'utils/loadMDX'
7 |
8 | export async function getStaticPaths() {
9 | const posts = await getAllFunctionsMeta()
10 | const paths = posts.map(({ pkg, name }) => ({
11 | params: { package: pkg, function: name }
12 | }))
13 |
14 | return {
15 | paths,
16 | fallback: false // -> /404
17 | }
18 | }
19 |
20 | export async function getStaticProps(context: GetStaticPropsContext) {
21 | const pkg = context.params?.package as string
22 | const name = context.params?.function as string
23 |
24 | const posts = await getFunction(pkg, name)
25 |
26 | return { props: posts }
27 | }
28 |
29 | type Props = InferGetStaticPropsType
30 |
31 | export default function Functions({ meta, code }: Props) {
32 | const Component = useMemo(() => getMDXComponent(code), [code])
33 |
34 | return (
35 | <>
36 |
46 |
47 |
48 |
49 | >
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/packages/core/useNetwork/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useNetwork
4 | description: Reactive Network status
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useNetwork
14 |
15 | Reactive Network status.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useNetwork } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const network = useNetwork()
24 |
25 | return (
26 |
27 |
28 | {Object.entries(network).map(([key, value]) => (
29 |
30 |
{key}:
31 | {`${value}` ?? 'unknown'}
32 |
33 | ))}
34 |
35 |
36 | )
37 | }
38 | ```
39 |
40 | ## Type Declarations
41 |
42 | ```typescript
43 | declare type NetworkEffectiveType = 'slow-2g' | '2g' | '3g' | '4g' | undefined;
44 | interface NetworkInformation extends EventTarget {
45 | readonly effectiveType?: NetworkEffectiveType;
46 | readonly downlinkMax?: number;
47 | readonly downlink?: number;
48 | readonly rtt?: number;
49 | readonly saveData?: boolean;
50 | onchange?: EventListener;
51 | }
52 | /**
53 | * Reactive Network status.
54 | *
55 | * @see https://react-hooks-library.vercel.app/core/useNetwork
56 | */
57 | declare function useNetwork(): {
58 | isSupported: boolean;
59 | isOnline: boolean;
60 | offlineAt: number | undefined;
61 | saveData: boolean | undefined;
62 | rtt: number | undefined;
63 | downlink: number | undefined;
64 | downlinkMax: number | undefined;
65 | effectiveType: NetworkEffectiveType;
66 | };
67 | ```
68 |
69 | ## Source
70 |
71 |
72 |
--------------------------------------------------------------------------------
/packages/core/_template/index.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react-hooks'
2 | import fs from 'fs/promises'
3 | import { join } from 'path'
4 |
5 | import { testDocs } from '../../../scripts/utils/testUtils'
6 | import { useCounter } from '.'
7 |
8 | const FunctionName = useCounter.name
9 |
10 | describe(FunctionName, () => {
11 | test('should be defined', () => {
12 | expect(useCounter).toBeDefined()
13 | })
14 |
15 | test('should have docs with appropriate meta data', async () => {
16 | const source = await fs.readFile(join(__dirname, '/docs.mdx'), 'utf-8')
17 | testDocs(FunctionName, source)
18 | })
19 |
20 | test('should update the counter', () => {
21 | const { result } = renderHook(() => useCounter(0))
22 |
23 | expect(result.current.count).toBe(0)
24 | act(() => expect(result.current.get()).toBe(0))
25 |
26 | act(() => result.current.inc())
27 | expect(result.current.count).toBe(1)
28 | act(() => expect(result.current.get()).toBe(1))
29 |
30 | act(() => result.current.dec())
31 | expect(result.current.count).toBe(0)
32 | act(() => expect(result.current.get()).toBe(0))
33 |
34 | act(() => result.current.set(9))
35 | expect(result.current.count).toBe(9)
36 | act(() => expect(result.current.get()).toBe(9))
37 |
38 | act(() => result.current.reset(25))
39 | expect(result.current.count).toBe(25)
40 | act(() => expect(result.current.get()).toBe(25))
41 |
42 | act(() => result.current.reset())
43 | expect(result.current.count).toBe(0)
44 | act(() => expect(result.current.get()).toBe(0))
45 |
46 | act(() => result.current.reset(-100))
47 | expect(result.current.count).toBe(-100)
48 | act(() => expect(result.current.get()).toBe(-100))
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This is the documentation and demos website for `@react-hooks-library`. The library documentation is not written here, but it is imported and built here.
4 |
5 | It is built using:
6 |
7 | - [next js](https://nextjs.org/)
8 | - [tailwind css](https://tailwindcss.com/)
9 | - [react](https://reactjs.org/)
10 | - [typescript](https://www.typescriptlang.org/)
11 | - [framer-motion](https://framer.com/motion/)
12 | - [zustand](https://github.com/pmndrs/zustand)
13 | - [mdx-bundler](https://github.com/kentcdodds/mdx-bundler)
14 |
15 | ## Code Guide
16 |
17 | Most of the code is written in TypeScript, but most pages are written in Markdown, we build our website by importing the Markdown files and compiling them into react using `mdx-bundler`. These `.mdx` files embed interactive react components(with state, effects etc.).
18 |
19 | The loader functions are defined at [loadMDX.ts](/website/utils/loadMDX.ts). All hooks docs are built by importing all the `.mdx` files.
20 | This implementation can be seen [here](/website/pages/[package]/[function].tsx).
21 |
22 | The rest of the UI component are at [ui](/website/ui) and the rest is normal next js.
23 |
24 | There are special components that available to the mdx file that don't need to be imported, these are defined at [ui/docs](/website/ui/docs), they are imported and in [mdxComponents.tsx](/website/ui/docs/mdxComponents.tsx) which also modifies the incoming html to add special nextjs features.
25 |
26 | A simpler example of this workflow is seen at [functions](/website/pages/functions.tsx) or the [getting-started](/website/pages/getting-started.tsx) pages, which is a list of all the hooks.
27 |
28 | That's most of it, we also use framer-motion for animation, primary state transitions and zustand is used for global state management.
29 |
--------------------------------------------------------------------------------
/website/pages/functions.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { NextSeo } from 'next-seo'
3 | import { Fragment } from 'react'
4 |
5 | import routes from '../routes.json'
6 |
7 | const groupedRoutes: {
8 | [key: string]: typeof routes
9 | } = {}
10 |
11 | routes.forEach((route) => {
12 | const category = route.category.toLowerCase()
13 |
14 | groupedRoutes[category]
15 | ? groupedRoutes[category].push(route)
16 | : (groupedRoutes[category] = [route])
17 | })
18 |
19 | export default function Functions() {
20 | return (
21 | <>
22 |
32 |
33 |
Functions
34 | {Object.keys(groupedRoutes).map((category) => (
35 |
36 |
37 | {category}
38 |
39 |
40 | {groupedRoutes[category].map(({ name, route, description }) => (
41 |
42 |
43 | {name}
44 |
45 | {description}
46 |
47 | ))}
48 |
49 |
50 | ))}
51 |
52 | >
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/packages/core/useAsyncCallback/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useAsyncCallback
4 | description: Returns a current execution state of an async function.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useAsyncCallback
14 |
15 | Returns a current execution state of an async function, Use it to orchestrate async actions in UI.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useCallback } from 'react'
21 | import { useAsyncCallback } from '@react-hooks-library/core'
22 |
23 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
24 |
25 | function Demo() {
26 | const fn = useCallback(async () => {
27 | await sleep(3000)
28 | return 5000
29 | }, [])
30 |
31 | const [fnState, fnCallback] = useAsyncCallback(fn)
32 |
33 | return (
34 |
35 | {fnState.isLoading ?
Loading...
: null}
36 |
37 | {fnState.error ?
An Error Occurred
: null}
38 |
39 | {fnState.data ?
{fnState.data}
: null}
40 |
41 |
Call Function
42 |
43 | )
44 | }
45 | ```
46 |
47 | ## Type Declarations
48 |
49 | ```typescript
50 | declare type UseAsyncState = {
51 | data: T | undefined
52 | error: boolean
53 | isSuccess: boolean
54 | isLoading: boolean
55 | }
56 | /**
57 | * Returns a current execution state of an async function.
58 | * Use it to orchestrate async actions in UI.
59 | *
60 | * @see https://react-hooks-library.vercel.app/core/useAsyncCallback
61 | */
62 | declare function useAsyncCallback(
63 | callback: (...args: Args) => Promise
64 | ): [UseAsyncState, (...args: Args) => Promise]
65 | ```
66 |
67 | ## Source
68 |
69 |
70 |
--------------------------------------------------------------------------------
/packages/core/useTitle/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: browser
3 | name: useTitle
4 | description: Reactive document title hook, set title or observe dom mutation reactively
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useTitle
14 |
15 | Reactive document title hook, set title or observe dom mutation reactively.
16 | Uses [useMutationObserver](/core/useMutationObserver) to observe DOM mutations.
17 |
18 | ## Usage
19 |
20 | ```tsx
21 | import { useTitle } from '@react-hooks-library/core'
22 |
23 | function Demo() {
24 | const { title, setTitle } = useTitle()
25 |
26 | function setRawTitle() {
27 | const titleNode = document.head.querySelector('title')
28 | // This will also re-render the component
29 | titleNode.innerText = 'Some New Title'
30 | }
31 |
32 | return (
33 |
34 |
Title - {title}
35 |
setTitle(e.target.value)}
39 | />
40 |
41 |
set raw title
42 |
43 | )
44 | }
45 | ```
46 |
47 | ```tsx
48 | function Demo() {
49 | const { title } = useTitle('Default Title')
50 |
51 | return (
52 |
53 |
Title - {title}
54 |
55 | )
56 | }
57 | ```
58 |
59 | ## Type Declarations
60 |
61 | ```typescript
62 | /**
63 | * Reactive document title hook
64 | *
65 | * Set title or observe dom mutation reactively
66 | *
67 | * @param newTitle optional
68 | * @see https://react-hooks-library.vercel.app/core/useTitle
69 | */
70 | declare function useTitle(newTitle?: string): {
71 | title: string
72 | setTitle: react.Dispatch>
73 | }
74 | ```
75 |
76 | ## Source
77 |
78 |
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | [](https://github.com/heyitsarpit/react-hooks-library/actions/workflows/publish_release.yml?query=event%3Apush)
4 | [](https://www.npmjs.com/package/@react-hooks-library/core)
5 | [](/LICENSE)
6 |
7 | A collection of hooks and utilities for React. List of [all function](https://react-hooks-library.vercel.app/functions). For more information, read the [official documentation](https://react-hooks-library.vercel.app).
8 |
9 | ## Features
10 |
11 | - 🔮 Typescript
12 |
13 | Written in typescript so you get the advantage of strong type safety
14 |
15 | - 🧠 Server Side Ready
16 |
17 | All hooks handle SSR rendering and work well with frameworks like Next/Gatsby
18 |
19 | - 🌿 Tree Shakable
20 |
21 | Exported as es modules, import cost for individual function is tiny
22 |
23 | - 🎡 Interactive Demos
24 |
25 | All hooks have a demo example to demonstrate their use
26 |
27 | ## Installation
28 |
29 | ```bash
30 | npm i @react-hooks-library/core
31 | # or
32 | yarn add @react-hooks-library/core
33 | # or
34 | pnpm add @react-hooks-library/core
35 | ```
36 |
37 | ## Contribution
38 |
39 | Contribution guide coming soon... 🤞🏽
40 |
41 | ## Credits
42 |
43 | This library is heavily inspired by [VueUse](https://vueuse.org/), from the vue ecosystem.
44 |
45 | ## License
46 |
47 | [MIT](/LICENSE) © 2021 | [Arpit](https://github.com/heyitsarpit)
48 |
--------------------------------------------------------------------------------
/packages/core/useEffectAfterMount/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: lifecycle
3 | name: useEffectAfterMount
4 | description: A useEffect hook that does not run on mount, but only on subsequent updates
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useEffectAfterMount
14 |
15 | A useEffect hook that does not run on mount, but only on subsequent updates, useful for purely reactive effects.
16 |
17 | The effect can return a cleanup function just like `useEffect`, that has the same semantics to the native `useEffect`.
18 |
19 | ## Usage
20 |
21 | ```tsx
22 | import { useEffect, useState } from 'react'
23 |
24 | import { useEffectAfterMount } from '@react-hooks-library/core'
25 |
26 | export function Demo() {
27 | const [count, setCount] = useState(0)
28 | const [firstEffect, setFirstEffect] = useState(false)
29 | const [secondEffect, setSecondEffect] = useState(false)
30 |
31 | useEffect(() => {
32 | setFirstEffect(true)
33 | }, [count])
34 |
35 | useEffectAfterMount(() => {
36 | setSecondEffect(true)
37 |
38 | return () => console.log('run cleanup')
39 | }, [count])
40 |
41 | return (
42 |
43 |
First effect - {firstEffect ? 'ran' : 'did not run'}
44 |
Second effect - {secondEffect ? 'ran' : 'did not run'}
45 |
setCount((c) => c + 1)}>Rerender
46 |
47 | )
48 | }
49 | ```
50 |
51 | ## Type Declarations
52 |
53 | ```typescript
54 | /**
55 | * A useEffect hook does that not run on mount, but only on subsequent updates.
56 | *
57 | * @param effect
58 | * @param deps
59 | *
60 | * @see https://react-hooks-library.vercel.app/core/useEffectAfterMount
61 | */
62 | declare function useEffectAfterMount(
63 | effect: React.EffectCallback,
64 | deps?: React.DependencyList | undefined
65 | ): void
66 | ```
67 |
68 | ## Source
69 |
70 |
71 |
--------------------------------------------------------------------------------
/website/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import 'public/styles/global.css'
2 | import 'public/styles/prism.css'
3 | import 'public/styles/fonts.css'
4 |
5 | import { AnimatePresence, motion } from 'framer-motion'
6 | import { AppProps } from 'next/app'
7 | import { Router } from 'next/router'
8 | import { Header } from 'ui/Header'
9 | import { SEO } from 'ui/SEO'
10 | import { SideBar } from 'ui/SideBar'
11 | import { ThemeProvider } from 'ui/ThemeProvider'
12 | import { useSidebar } from 'utils/useSidebar'
13 |
14 | Router.events.on('routeChangeComplete', () => {
15 | document.querySelector('#__next').scroll({
16 | top: 0,
17 | behavior: 'smooth'
18 | })
19 | })
20 |
21 | const App: React.FC = ({ Component, pageProps, router }) => {
22 | const { sidebarOpen } = useSidebar()
23 |
24 | return (
25 | <>
26 |
27 |
28 |
29 | {['/', '/design'].includes(router.asPath) ? (
30 |
31 |
32 |
33 | ) : (
34 | <>
35 |
36 |
37 |
44 |
45 |
46 |
47 |
48 |
49 | >
50 | )}
51 |
52 | >
53 | )
54 | }
55 |
56 | export default App
57 |
--------------------------------------------------------------------------------
/website/ui/ThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | import { usePreferredColorScheme } from '@react-hooks-library/core'
2 | import { noop } from '@react-hooks-library/shared'
3 | import { createContext, useContext, useEffect, useRef, useState } from 'react'
4 |
5 | type Theme = 'dark' | 'light'
6 |
7 | type ThemeContextProps = {
8 | theme: Theme
9 | switchTheme: () => void
10 | }
11 | const ThemeContext = createContext({
12 | theme: 'dark',
13 | switchTheme: noop
14 | })
15 |
16 | type Props = { children: React.ReactNode }
17 |
18 | export function ThemeProvider({ children }: Props) {
19 | const [theme, setTheme] = useState('dark')
20 | const preferredTheme = usePreferredColorScheme()
21 |
22 | const renderCount = useRef(0)
23 |
24 | useEffect(() => {
25 | renderCount.current += 1
26 | })
27 |
28 | useEffect(() => {
29 | const theme = (document.body.getAttribute('class') as Theme) || 'dark'
30 | setTheme(theme)
31 | }, [])
32 |
33 | useEffect(() => {
34 | const bodyClass = document.body.classList
35 |
36 | if (theme === 'dark') {
37 | bodyClass.remove('light')
38 | bodyClass.add('dark')
39 | } else {
40 | bodyClass.remove('dark')
41 | bodyClass.add('light')
42 | }
43 |
44 | localStorage.setItem('theme', theme)
45 | }, [theme])
46 |
47 | useEffect(() => {
48 | /**
49 | * We don't want to switch theme on first few renders
50 | * only when user switches system theme
51 | */
52 | if (renderCount.current <= 2) return
53 | preferredTheme === 'dark' ? setTheme('dark') : setTheme('light')
54 | }, [preferredTheme])
55 |
56 | const switchTheme = () =>
57 | theme === 'light' ? setTheme('dark') : setTheme('light')
58 |
59 | return (
60 |
61 | {children}
62 |
63 | )
64 | }
65 |
66 | export const useTheme = () => {
67 | return useContext(ThemeContext)
68 | }
69 |
--------------------------------------------------------------------------------
/packages/shared/utils/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: shared
3 | name: utils
4 | description: Common utils and helpers.
5 | ---
6 |
7 | # Utils
8 |
9 | Common utils and helpers, as functions and types.
10 |
11 | ## Functions
12 |
13 | - `isClient` - Check if we're on the server or client side
14 | - `unRef` - Accepts either a ref object or a dom node and returns a dom node
15 | - `isRef` - Check if object is a react ref
16 | - `isWindow` - is the object window
17 | - `noop` - no operation
18 | - `rand` - random number between a min and max number
19 | - `isBoolean`
20 | - `isFunction`
21 | - `isNumber`
22 | - `isString`
23 | - `isObject`
24 |
25 | ## Types
26 |
27 | - `Fn` - A function type
28 | - `MaybeRef` - Union type of react ref and dom node.
29 |
30 |
31 | # Type Declarations
32 |
33 | ```typescript
34 | /**
35 | * Any function
36 | */
37 | declare type Fn = () => void
38 |
39 | /**
40 | * Maybe it's a react ref, or a dom node.
41 | */
42 | declare type MaybeRef = T | MutableRefObject
43 |
44 | /**
45 | * Accepts either a ref object or a dom node and returns a dom node
46 | *
47 | * @param target - ref or a dom node
48 | * @returns dom node
49 | */
50 | declare function unRef(target: MaybeRef): T
51 |
52 | /**
53 | * Check if we're on the server or client side
54 | */
55 | declare const isClient: boolean
56 |
57 | /**
58 | * Check if object is a react ref
59 | */
60 | declare const isRef: (obj: unknown) => boolean
61 |
62 | declare const isBoolean: (val: any) => val is boolean
63 |
64 | declare const isFunction: (val: any) => val is T
65 |
66 | declare const isNumber: (val: any) => val is number
67 |
68 | declare const isString: (val: unknown) => val is string
69 |
70 | declare const isObject: (val: any) => val is object
71 |
72 | declare const isWindow: (val: any) => val is Window
73 |
74 | declare const noop: () => void
75 |
76 | declare const rand: (min: number, max: number) => number
77 | ```
--------------------------------------------------------------------------------
/scripts/build.ts:
--------------------------------------------------------------------------------
1 | import { execSync as exec } from 'child_process'
2 | import consola from 'consola'
3 | import fs from 'fs/promises'
4 | import { join, resolve } from 'path'
5 |
6 | import { findFunctions } from './utils/findFunctions'
7 |
8 | const rootDir = resolve(__dirname, '..')
9 | const packagesDir = resolve(rootDir, 'packages')
10 |
11 | async function buildImports() {
12 | const packages = findFunctions(packagesDir)
13 |
14 | for (const pkg of packages) {
15 | const functions = findFunctions(join(packagesDir, pkg)).sort()
16 | const contents: string[] = [
17 | `// This file is auto generated. Do not edit manually.\n\n`
18 | ]
19 |
20 | for (const funcName of functions) {
21 | contents.push(`export * from './${funcName}'\n`)
22 | }
23 |
24 | await fs.writeFile(`${join(packagesDir, pkg)}/index.ts`, contents, 'utf-8')
25 | }
26 | }
27 |
28 | async function buildMetaFiles() {
29 | const packages = findFunctions(packagesDir)
30 |
31 | for (const pkg of packages) {
32 | const basePath = join(packagesDir, pkg)
33 | await fs.copyFile(
34 | join(basePath, 'package.json'),
35 | join(basePath, 'dist', 'package.json')
36 | )
37 |
38 | await fs.copyFile(
39 | join(rootDir, 'LICENSE'),
40 | join(basePath, 'dist', 'LICENSE')
41 | )
42 |
43 | // TODO - Make read me dynamic and more informative for each package
44 | await fs.copyFile(
45 | join(rootDir, 'README.md'),
46 | join(basePath, 'dist', 'README.md')
47 | )
48 | }
49 | }
50 |
51 | async function build() {
52 | consola.info('Running build')
53 |
54 | consola.info('Cleaning dist folders')
55 | exec('npm run clean', { stdio: 'inherit' })
56 |
57 | consola.info('Building imports')
58 | await buildImports()
59 |
60 | consola.info('Running rollup')
61 | exec('npm run build:rollup', { stdio: 'inherit' })
62 |
63 | consola.info('Building package meta files')
64 | await buildMetaFiles()
65 | }
66 |
67 | build()
68 |
--------------------------------------------------------------------------------
/packages/core/useScrollIntoView/index.ts:
--------------------------------------------------------------------------------
1 | import { isFunction, MaybeRef, unRef } from '@react-hooks-library/shared'
2 |
3 | import { useMount } from '../useMount'
4 |
5 | export interface UseScrollIntoViewOptions extends ScrollIntoViewOptions {
6 | /**
7 | * Defines vertical alignment.
8 | * One of `'start'`, `'center'`, `'end'`, or `'nearest'`
9 | *
10 | * @default 'start'
11 | */
12 | block?: ScrollLogicalPosition
13 |
14 | /**
15 | * Defines horizontal alignment.
16 | * One of `start`, `center`, `end`, or `nearest`. Defaults to nearest.
17 | *
18 | * @default 'nearest'
19 | */
20 | inline?: ScrollLogicalPosition
21 |
22 | /**
23 | * Defines the transition animation.
24 | * One of 'auto' or 'smooth'.
25 | *
26 | * @default 'auto'
27 | */
28 | behavior?: ScrollBehavior
29 |
30 | /**
31 | * The margin around the element to make sure it's visible.
32 | *
33 | * @default '0px'
34 | */
35 | scrollMargin?: string
36 |
37 | /**
38 | * The condition to decide if the element should be scrolled into view.
39 | *
40 | * @default true
41 | */
42 | predicate?: boolean | (() => boolean)
43 | }
44 |
45 | /**
46 | *
47 | * A hook to scroll an element into view on mounting.
48 | *
49 | * @param options {UseScrollIntoViewOptions}
50 | *
51 | * @see https://react-hooks-library.vercel.app/core/useScrollIntoView
52 | */
53 | export function useScrollIntoView(
54 | target: MaybeRef,
55 | options: UseScrollIntoViewOptions = {}
56 | ) {
57 | const {
58 | behavior = 'auto',
59 | block = 'start',
60 | inline = 'nearest',
61 | scrollMargin = '0px',
62 | predicate = true
63 | } = options
64 |
65 | useMount(() => {
66 | const el = unRef(target)
67 |
68 | if (!(el && (isFunction(predicate) ? predicate() : predicate))) {
69 | return
70 | }
71 |
72 | el.style.scrollMargin = scrollMargin
73 | el.scrollIntoView({
74 | behavior,
75 | block,
76 | inline
77 | })
78 | })
79 | }
80 |
--------------------------------------------------------------------------------
/packages/core/useNetwork/index.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 |
3 | import { _navigator } from '../_ssr.config'
4 | import { useEventListener } from '../useEventListener'
5 | import { useIsSupported } from '../useIsSupported'
6 | import { useMount } from '../useMount'
7 |
8 | export type NetworkEffectiveType = 'slow-2g' | '2g' | '3g' | '4g' | undefined
9 |
10 | export interface NetworkInformation extends EventTarget {
11 | readonly effectiveType?: NetworkEffectiveType
12 | readonly downlinkMax?: number
13 | readonly downlink?: number
14 | readonly rtt?: number
15 | readonly saveData?: boolean
16 | onchange?: EventListener
17 | }
18 |
19 | /**
20 | * Reactive Network status.
21 | *
22 | * @see https://react-hooks-library.vercel.app/core/useNetwork
23 | */
24 | export function useNetwork() {
25 | const isSupported = useIsSupported(() => !!_navigator?.connection)
26 | const [isOnline, setIsOnline] = useState(true)
27 | const [offlineAt, setOfflineAt] = useState(undefined)
28 |
29 | const connection = useRef(undefined)
30 | const rerender = useState({})[1]
31 |
32 | useMount(() => {
33 | if (!_navigator) return
34 |
35 | setIsOnline(_navigator.onLine)
36 | setOfflineAt(isOnline ? undefined : Date.now())
37 |
38 | const _connection = _navigator?.connection as NetworkInformation
39 | if (!_connection) return
40 |
41 | connection.current = _connection
42 | connection.current.onchange = () => rerender({})
43 | })
44 |
45 | useEventListener('offline', () => {
46 | setIsOnline(false)
47 | setOfflineAt(Date.now())
48 | })
49 |
50 | useEventListener('online', () => {
51 | setIsOnline(true)
52 | })
53 |
54 | return {
55 | isSupported,
56 | isOnline,
57 | offlineAt,
58 | saveData: connection.current?.saveData,
59 | rtt: connection.current?.rtt,
60 | downlink: connection.current?.downlink,
61 | downlinkMax: connection.current?.downlinkMax,
62 | effectiveType: connection.current?.effectiveType
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/website/ui/ThemeSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { useHasMounted } from '@react-hooks-library/core'
2 | import { AnimatePresence, motion } from 'framer-motion'
3 |
4 | import { useTheme } from './ThemeProvider'
5 |
6 | export function ThemeSwitch() {
7 | const { switchTheme, theme } = useTheme()
8 | const hasMounted = useHasMounted()
9 |
10 | // Avoid initial transition when switching themes
11 | if (!hasMounted) {
12 | return
13 | }
14 |
15 | return (
16 |
19 |
20 |
29 |
40 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/packages/core/useScreenShare/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useScreenShare
4 | description: Reactive screen sharing
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useScreenShare
14 |
15 | Reactive `mediaDevices.getDisplayMedia`, allows screen sharing. It also handles the native stop stream button automatically.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useScreenShare } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const { play, ref, stop, isPlaying } = useScreenShare()
24 |
25 | return (
26 |
27 |
34 |
35 |
36 | Start Screen Sharing
37 |
38 |
39 | Stop Screen Sharing
40 |
41 |
42 |
43 | )
44 | }
45 | ```
46 |
47 | ## Type Declarations
48 |
49 | ```typescript
50 | interface UseScreenShareOptions {
51 | /**
52 | * If the stream video media constraints
53 | *
54 | * @default true
55 | */
56 | video?: boolean | MediaTrackConstraints | undefined
57 | /**
58 | * If the stream audio media constraints
59 | *
60 | * @default true
61 | */
62 | audio?: boolean | MediaTrackConstraints | undefined
63 | }
64 |
65 | /**
66 | * Reactive screen sharing
67 | *
68 | * @param options
69 | *
70 | * @see https://react-hooks-library.vercel.app/core/useScreenShare
71 | */
72 | declare function useScreenShare(options?: UseScreenShareOptions): {
73 | isSupported: boolean
74 | isPlaying: boolean
75 | ref: react.MutableRefObject
76 | stream: react.MutableRefObject
77 | play: () => Promise
78 | stop: () => Promise
79 | }
80 | ```
81 |
82 | ## Source
83 |
84 |
85 |
--------------------------------------------------------------------------------
/packages/core/useScreenShare/index.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef, useState } from 'react'
2 |
3 | import { _navigator } from '../_ssr.config'
4 | import { useIsSupported } from '../useIsSupported'
5 |
6 | export interface UseScreenShareOptions {
7 | /**
8 | * If the stream video media constraints
9 | *
10 | * @default true
11 | */
12 | video?: boolean | MediaTrackConstraints | undefined
13 | /**
14 | * If the stream audio media constraints
15 | *
16 | * @default true
17 | */
18 | audio?: boolean | MediaTrackConstraints | undefined
19 | }
20 |
21 | /**
22 | * Reactive screen sharing
23 | *
24 | * @param options
25 | *
26 | * @see https://react-hooks-library.vercel.app/core/useScreenShare
27 | */
28 | export function useScreenShare(options: UseScreenShareOptions = {}) {
29 | const { audio = true, video = true } = options
30 | const isSupported = useIsSupported(
31 | () => !!_navigator?.mediaDevices?.getDisplayMedia
32 | )
33 |
34 | const stream = useRef(null)
35 | const ref = useRef(null)
36 |
37 | const [isPlaying, setPlaying] = useState(false)
38 |
39 | const play = useCallback(async () => {
40 | if (!isSupported || !ref.current) return
41 |
42 | stream.current =
43 | (await _navigator?.mediaDevices.getDisplayMedia({
44 | audio,
45 | video
46 | })) ?? null
47 |
48 | setPlaying(true)
49 |
50 | return stream.current
51 | }, [audio, isSupported, video])
52 |
53 | const stop = useCallback(() => {
54 | stream.current?.getTracks().forEach((t) => t.stop())
55 | stream.current = null
56 | setPlaying(false)
57 | }, [])
58 |
59 | useEffect(() => {
60 | if (!ref.current) return
61 |
62 | ref.current.srcObject = stream.current
63 |
64 | // Handle os native stop screen sharing buttons
65 | stream.current?.getVideoTracks()[0].addEventListener('ended', stop)
66 | }, [isPlaying, stop])
67 |
68 | return {
69 | isSupported,
70 | isPlaying,
71 | ref,
72 | stream,
73 | play,
74 | stop
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/packages/core/useLocalStorage/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useLocalStorage
4 | description: Modified `useState` hook that syncs with localStorage.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useLocalStorage
14 |
15 | Modified `useState` hook that syncs with [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useLocalStorage } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const [value, setValue] = useLocalStorage(
24 | 'useLocalsStorageKey',
25 | 'Hello World'
26 | )
27 |
28 | return (
29 |
30 |
State - {value}
31 |
32 | Enter some text to update state and localStorage
33 |
34 |
setValue(e.target.value)} type="text" id="demo" />
35 |
36 | )
37 | }
38 | ```
39 |
40 | ### Using custom serialize and deserialize options
41 |
42 | The default for `serialize` is `JSON.stringify` and the default for `deserialize` is `JSON.parse`.
43 |
44 | ```tsx
45 | const [value, setValue] = useLocalStorage('key', 'value', {
46 | serialize: JSON.stringify,
47 | deserialize: JSON.parse
48 | })
49 | ```
50 |
51 | ## Type Declarations
52 |
53 | ```typescript
54 | declare type UseLocalStorageOptions = {
55 | /**
56 | * Function for converting to string.
57 | *
58 | * @default JSON.stringify
59 | */
60 | serialize: (value: unknown) => string
61 | /**
62 | * Function to convert stored string to object value.
63 | *
64 | * @default JSON.parse
65 | */
66 | deserialize: (value: string) => unknown
67 | }
68 | /**
69 | * Modified `useState` hook that syncs with localStorage.
70 | *
71 | * @param key
72 | * @param initialValue
73 | * @param options
74 | *
75 | * @see https://react-hooks-library.vercel.app/core/useLocalStorage
76 | */
77 | declare function useLocalStorage(
78 | key: string,
79 | initialValue: T,
80 | options?: UseLocalStorageOptions
81 | ): [T, (value: T) => void]
82 | ```
83 |
84 | ## Source
85 |
86 |
87 |
--------------------------------------------------------------------------------
/website/ui/SideBar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BreakPointHooks,
3 | breakpointsTailwind,
4 | useClickOutside
5 | } from '@react-hooks-library/core'
6 | import { useRouter } from 'next/router'
7 | import { useEffect, useRef } from 'react'
8 | import { useSidebar } from 'utils/useSidebar'
9 |
10 | import { FunctionList } from './FunctionList'
11 | import { NavLinks } from './Header'
12 |
13 | export const { useGreater } = BreakPointHooks(breakpointsTailwind)
14 |
15 | export function SideBar() {
16 | const { sidebarOpen, setSideBar } = useSidebar()
17 | const isGreater = useGreater('md')
18 | const ref = useRef(null)
19 |
20 | const { asPath } = useRouter()
21 |
22 | useClickOutside(ref, () => {
23 | if (sidebarOpen && !isGreater) {
24 | setSideBar(false)
25 | }
26 | })
27 |
28 | useEffect(() => {
29 | if (!ref.current) return
30 |
31 | const classes = ref.current.classList
32 | const open = 'translate-x-0'
33 | const close = 'translate-x-[calc(-1*var(--sidebar-width))]'
34 |
35 | if (sidebarOpen) {
36 | classes.remove(close)
37 | classes.add(open)
38 | } else {
39 | classes.remove(open)
40 | classes.add(close)
41 | }
42 | }, [sidebarOpen])
43 |
44 | useEffect(() => {
45 | setSideBar(isGreater)
46 | }, [isGreater, setSideBar])
47 |
48 | if (asPath === '/') {
49 | return null
50 | }
51 |
52 | return (
53 |
69 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/packages/core/useSessionStorage/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useSessionStorage
4 | description: Modified `useState` hook that syncs with sessionStorage.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useSessionStorage
14 |
15 | Modified `useState` hook that syncs with [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage).
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useSessionStorage } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const [value, setValue] = useSessionStorage(
24 | 'useSessionStorageKey',
25 | 'Hello World'
26 | )
27 |
28 | return (
29 |
30 |
State - {value}
31 |
32 | Enter some text to update state and sessionStorage
33 |
34 |
setValue(e.target.value)} type="text" id="demo" />
35 |
36 | )
37 | }
38 | ```
39 |
40 | ### Using custom serialize and deserialize options
41 |
42 | The default for `serialize` is `JSON.stringify` and the default for `deserialize` is `JSON.parse`.
43 |
44 | ```tsx
45 | const [value, setValue] = useSessionStorage('key', 'value', {
46 | serialize: JSON.stringify,
47 | deserialize: JSON.parse
48 | })
49 | ```
50 |
51 | ## Type Declarations
52 |
53 | ```typescript
54 | declare type UseSessionStorageOptions = {
55 | /**
56 | * Function for converting to string.
57 | *
58 | * @default JSON.stringify
59 | */
60 | serialize: (value: unknown) => string
61 | /**
62 | * Function to convert stored string to object value.
63 | *
64 | * @default JSON.parse
65 | */
66 | deserialize: (value: string) => unknown
67 | }
68 | /**
69 | * Modified `useState` hook that syncs with useSessionStorage.
70 | *
71 | * @param key
72 | * @param initialValue
73 | * @param options
74 | *
75 | * @see https://react-hooks-library.vercel.app/core/useSessionStorage
76 | */
77 | declare function useSessionStorage(
78 | key: string,
79 | initialValue: T,
80 | options?: UseSessionStorageOptions
81 | ): [T, (value: T) => void]
82 | ```
83 |
84 | ## Source
85 |
86 |
87 |
--------------------------------------------------------------------------------
/packages/core/useClickOutside/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useClickOutside
4 | description: Listen for click outside an element, useful for modals and tooltips.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useClickOutside
14 |
15 | Listen for click outside an element, useful for modals and tooltips.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useRef, useState } from 'react'
21 | import { useClickOutside } from '@react-hooks-library/core'
22 |
23 | function Dropdown(props: {
24 | isOpen: boolean
25 | setIsOpen: (isOpen: boolean) => void
26 | }) {
27 | const { isOpen, setIsOpen } = props
28 | const ref = useRef(null)
29 |
30 | useClickOutside(ref, () => {
31 | setIsOpen(false)
32 | })
33 |
34 | if (!isOpen) return null
35 |
36 | return This is a dropdown, click outside to close
37 | }
38 |
39 | export function Demo() {
40 | const [isOpen, setIsOpen] = useState(false)
41 |
42 | return (
43 |
44 |
45 | setIsOpen(true)} disabled={isOpen}>
46 | Open Dropdown
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 | ```
56 |
57 | ## Type Declarations
58 |
59 | ```typescript
60 | declare type ClickOutsideEvents = Pick<
61 | WindowEventMap,
62 | | 'mousedown'
63 | | 'mouseup'
64 | | 'touchstart'
65 | | 'touchend'
66 | | 'pointerdown'
67 | | 'pointerup'
68 | >
69 | interface ClickOutsideOptions {
70 | event?: E
71 | }
72 | /**
73 | * Listen for clicks outside of an element.
74 | *
75 | * @param target
76 | * @param handler
77 | * @param options
78 | *
79 | * @see https://react-hooks-library.vercel.app/core/onClickOutside
80 | */
81 | declare function useClickOutside<
82 | E extends keyof ClickOutsideEvents = 'pointerdown'
83 | >(
84 | target: MaybeRef,
85 | handler: (evt: ClickOutsideEvents[E]) => void,
86 | options?: ClickOutsideOptions
87 | ): _react_hooks_library_shared.Fn
88 | ```
89 |
90 | ## Source
91 |
92 |
93 |
--------------------------------------------------------------------------------
/packages/core/useStateCompare/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useStateCompare
4 | description: useState hook with custom compare function to avoid re-rendering
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useStateCompare
14 |
15 | `useState` hook with custom compare function to avoid re-rendering when state is the same, compares with previous state.
16 |
17 |
18 | create a custom `compare` function, outside of the hook to keep a stable
19 | reference, otherwise it will be recreated on every render
20 |
21 |
22 |
23 | `compare` is only used when in `setState(value)`, `value` is not a function, in that case the default react behavior is used.
24 |
25 |
26 | ## Usage
27 |
28 | ```tsx
29 | import { useStateCompare } from '@react-hooks-library/core'
30 |
31 | type State = {
32 | count: number
33 | }
34 |
35 | const compare = (oldState: State, newState: State) =>
36 | oldState.count === newState.count ? oldState : newState
37 |
38 | export function Demo() {
39 | const [state, setState] = useStateCompare({
40 | initialValue: () => ({ count: 0 }),
41 | compare
42 | })
43 |
44 | return (
45 |
46 |
Count - {state.count}
47 |
setState({ count: state.count + 1 })}>
48 | Increment
49 |
50 |
51 | )
52 | }
53 | ```
54 |
55 | ## Type Declarations
56 |
57 | ```typescript
58 | declare type UseStateCompare = {
59 | initialValue: T | (() => T)
60 | compare: (oldValue: T, newValue: T) => T
61 | }
62 | /**
63 | * useState hook with custom compare function to avoid re-rendering
64 | * when state is the same, compares with previous state
65 | *
66 | *
67 | * Note: create a custom compare function, outside of the hook to a keep
68 | * stable reference, otherwise it will be recreated on every render
69 | *
70 | * @see https://react-hooks-library.vercel.app/core/useStateCompare
71 | */
72 | declare function useStateCompare({
73 | initialValue,
74 | compare
75 | }: UseStateCompare): [T, Dispatch>]
76 | ```
77 |
78 | ## Source
79 |
80 |
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-hooks-library/packages",
3 | "version": "0.6.2",
4 | "private": true,
5 | "description": "A collection of hooks and utilities for React",
6 | "main": "index.js",
7 | "author": "Arpit",
8 | "license": "MIT",
9 | "keywords": [
10 | "react",
11 | "react hooks",
12 | "@react-hooks-library"
13 | ],
14 | "workspaces": [
15 | "packages/*",
16 | "website"
17 | ],
18 | "scripts": {
19 | "lint": "eslint .",
20 | "lint:fix": "eslint --fix .",
21 | "build:rollup": "rollup -c",
22 | "clean": "rimraf dist packages/*/dist",
23 | "build:full": "esno scripts/build.ts",
24 | "publish:packages": "esno scripts/publish.ts",
25 | "release": "esno scripts/release.ts",
26 | "test": "jest",
27 | "website": "yarn workspace @react-hooks-library/website",
28 | "website:build": "yarn website build",
29 | "website:dev": "yarn website dev",
30 | "website:start": "yarn website start",
31 | "website:build:routes": "yarn website build:routes",
32 | "prepare": "husky install"
33 | },
34 | "devDependencies": {
35 | "@testing-library/react-hooks": "^7.0.2",
36 | "@types/jest": "^27.0.2",
37 | "@types/node": "16.11.6",
38 | "@typescript-eslint/eslint-plugin": "5.2.0",
39 | "@typescript-eslint/parser": "5.2.0",
40 | "bumpp": "^7.1.1",
41 | "consola": "^2.15.3",
42 | "esbuild-register": "^3.0.0",
43 | "eslint": "7.32.0",
44 | "eslint-config-next": "^12.0.1",
45 | "eslint-config-prettier": "^8.3.0",
46 | "eslint-plugin-jest": "^25.2.2",
47 | "eslint-plugin-jsx-a11y": "^6.4.1",
48 | "eslint-plugin-prettier": "^4.0.0",
49 | "eslint-plugin-simple-import-sort": "7.0.0",
50 | "esno": "^0.10.1",
51 | "fast-glob": "^3.2.7",
52 | "husky": ">=7",
53 | "jest": "^27.3.1",
54 | "lint-staged": ">=11",
55 | "prettier": "2.4.1",
56 | "react": "^17.0.2",
57 | "react-dom": "^17.0.2",
58 | "rimraf": "^3.0.2",
59 | "rollup": "^2.58.3",
60 | "rollup-plugin-dts": "^4.0.0",
61 | "rollup-plugin-terser": "^7.0.2",
62 | "rollup-plugin-typescript2": "^0.30.0",
63 | "ts-jest": "^27.0.7",
64 | "ts-node": "^10.4.0",
65 | "typescript": "4.4.4"
66 | },
67 | "lint-staged": {
68 | "*.{ts,tsx}": "eslint --cache --fix ."
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/core/useMouse/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useMouse
4 | description: Reactive mouse position based by page or client
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useMouse
14 |
15 | Reactive mouse position based by page or client.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useMouse } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const { x, y } = useMouse()
24 |
25 | return (
26 |
27 | x: {x}
28 |
29 | y: {y}
30 |
31 | )
32 | }
33 | ```
34 |
35 | ### Position Types
36 |
37 | ```tsx
38 | // use clientX and clientY
39 | const { x, y } = useMouse({ type: 'client' })
40 |
41 | // use pageX and pageY
42 | const { x, y } = useMouse({ type: 'page' })
43 | ```
44 |
45 | ## Options
46 |
47 | - `type` - Mouse position type, `'page'` or `'client'`. Default is `'client'`.
48 |
49 | - `touch` - Enable or disable touch events, default is `true`.
50 |
51 | - `resetOnTouchEnds` - Reset to initial value when `touchend` event fired
52 |
53 | - `initialValue` - Initial x and y position, default is `{ x: 0, y: 0 }`
54 |
55 | ## Type Declarations
56 |
57 | ```typescript
58 | declare type Position = {
59 | x: number
60 | y: number
61 | }
62 | interface MouseOptions {
63 | /**
64 | * Mouse position based by page or client
65 | *
66 | * @default 'client'
67 | */
68 | type?: 'page' | 'client'
69 | /**
70 | * Listen to `touchmove` events
71 | *
72 | * @default true
73 | */
74 | touch?: boolean
75 | /**
76 | * Reset to initial value when `touchend` event fired
77 | *
78 | * @default false
79 | */
80 | resetOnTouchEnds?: boolean
81 | /**
82 | * Initial values
83 | */
84 | initialValue?: Position
85 | }
86 |
87 | declare type MouseSource = 'mouse' | 'touch' | null
88 | /**
89 | *
90 | * Reactive mouse position based by page or client
91 | *
92 | * @param options
93 | *
94 | * @see https://react-hooks-library.vercel.app/core/useMouse
95 | */
96 | declare function useMouse(options?: MouseOptions): {
97 | x: number
98 | y: number
99 | source: MouseSource
100 | }
101 | ```
102 |
103 | ## Source
104 |
105 |
106 |
--------------------------------------------------------------------------------
/packages/core/useStateHistory/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: state
3 | name: useStateHistory
4 | description: useState with built in undo and redo history control
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useStateHistory
14 |
15 | useState with built in undo and redo history control
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useStateHistory } from '@react-hooks-library/core'
21 |
22 | export function Demo() {
23 | const { state, undo, redo, push, history, reset, redoAllowed } =
24 | useStateHistory(0, { maxHistory: 5 })
25 |
26 | return (
27 |
28 |
State: {state}
29 |
30 | push(state + 2)}>Push
31 | Undo
32 |
33 | Redo
34 |
35 | Reset
36 |
37 |
38 | )
39 | }
40 | ```
41 |
42 | ### Functions
43 |
44 | - `push`
45 |
46 | Push new state to history
47 |
48 | - `undo`
49 |
50 | Undo action and go back to previous state
51 |
52 | - `redo`
53 |
54 | Redo actions that were just undid with the undo function.
55 | Only allowed after an undo or a another redo action.
56 | Disabled right after a push action.
57 |
58 | - `redoAllowed`
59 |
60 | Returns true if redo is allowed, false otherwise.
61 |
62 | - `reset`
63 |
64 | Reset to last saved state
65 |
66 | - `history`
67 |
68 | The entire history of saved states.
69 |
70 | ## Type Declarations
71 |
72 | ```typescript
73 | declare type UseStateHistoryOptions = {
74 | /**
75 | * Max number of history states to be stores
76 | *
77 | * @default 10
78 | */
79 | maxHistory?: number
80 | }
81 |
82 | /**
83 | *
84 | * useState with built in undo and redo history control
85 | *
86 | * @param defaultValue
87 | * @param options
88 | * @returns
89 | */
90 | declare function useStateHistory(
91 | defaultValue: T | (() => T),
92 | options?: UseStateHistoryOptions
93 | ): {
94 | state: T
95 | push: (value: T) => void
96 | undo: () => void
97 | redo: () => void
98 | reset: () => void
99 | history: T[]
100 | redoAllowed: boolean
101 | }
102 | ```
103 |
104 | ## Source
105 |
106 |
107 |
--------------------------------------------------------------------------------
/packages/core/useStateHistory/index.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from '@react-hooks-library/shared'
2 | import { useCallback, useRef, useState } from 'react'
3 |
4 | type UseStateHistoryOptions = {
5 | /**
6 | * Max number of history states to be stores
7 | *
8 | * @default 10
9 | */
10 | maxHistory?: number
11 | }
12 |
13 | /**
14 | *
15 | * useState with built in undo and redo history control
16 | *
17 | * @param defaultValue
18 | * @param options
19 | * @returns
20 | */
21 | export function useStateHistory(
22 | defaultValue: T | (() => T),
23 | options: UseStateHistoryOptions = {}
24 | ) {
25 | const { maxHistory = 10 } = options
26 | const [state, setState] = useState(defaultValue)
27 |
28 | const lastSaved = useRef(
29 | isFunction(defaultValue) ? defaultValue() : defaultValue
30 | )
31 | const rerender = useState({})[1]
32 |
33 | const actionHistory = useRef([])
34 | const redoHistory = useRef([])
35 | const redoAllowed = useRef(false)
36 |
37 | const push = useCallback(
38 | (value: T) => {
39 | if (actionHistory.current.length < maxHistory) {
40 | actionHistory.current.push(value)
41 | } else {
42 | actionHistory.current = [...actionHistory.current.slice(1), value]
43 | lastSaved.current = actionHistory.current[0]
44 | }
45 |
46 | redoAllowed.current = false
47 | setState(value)
48 | },
49 | [maxHistory]
50 | )
51 |
52 | const redo = useCallback(() => {
53 | if (!(redoHistory.current.length && redoAllowed.current)) return
54 |
55 | const lastUndoState = redoHistory.current.pop()
56 | lastUndoState && push(lastUndoState)
57 | redoAllowed.current = true
58 | }, [push])
59 |
60 | const undo = useCallback(() => {
61 | if (actionHistory.current.length < 1) return
62 |
63 | const lastState = actionHistory.current.pop()
64 | lastState && redoHistory.current.push(lastState)
65 |
66 | const prev = actionHistory.current[actionHistory.current.length - 1]
67 | prev ? setState(prev) : setState(lastSaved.current)
68 | rerender({})
69 | redoAllowed.current = true
70 | }, [rerender])
71 |
72 | const reset = useCallback(() => {
73 | if (!actionHistory.current?.length) return
74 |
75 | setState(actionHistory.current[0])
76 | actionHistory.current = [actionHistory.current[0]]
77 | }, [])
78 |
79 | return {
80 | state,
81 | push,
82 | undo,
83 | redo,
84 | reset,
85 | history: actionHistory.current,
86 | redoAllowed: redoAllowed.current
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/website/ui/FunctionList.tsx:
--------------------------------------------------------------------------------
1 | import { useScrollIntoView } from '@react-hooks-library/core'
2 | import { AnimateSharedLayout, motion } from 'framer-motion'
3 | import Link from 'next/link'
4 | import { useRouter } from 'next/router'
5 | import { Fragment, useRef } from 'react'
6 |
7 | import routes from '../routes.json'
8 |
9 | const groupedRoutes: {
10 | [key: string]: typeof routes
11 | } = {}
12 |
13 | routes.forEach((route) => {
14 | const category = route.category.toLowerCase()
15 |
16 | groupedRoutes[category]
17 | ? groupedRoutes[category].push(route)
18 | : (groupedRoutes[category] = [route])
19 | })
20 |
21 | type ItemProps = {
22 | name: string
23 | route: string
24 | isActive: boolean
25 | }
26 |
27 | function ListItem({ name, route, isActive }: ItemProps) {
28 | const activeEl = useRef(null)
29 |
30 | useScrollIntoView(activeEl, {
31 | behavior: 'smooth',
32 | scrollMargin: 'calc(50vh - 5rem)',
33 | predicate: isActive
34 | })
35 |
36 | return (
37 |
40 | {isActive ? (
41 |
45 | ) : null}
46 |
47 |
51 | {name}
52 |
53 |
54 |
55 | )
56 | }
57 |
58 | export function FunctionList() {
59 | const router = useRouter()
60 |
61 | const isActive = (name: string) => {
62 | return router.query.function === name
63 | }
64 |
65 | return (
66 |
67 |
68 | {Object.keys(groupedRoutes).map((category) => (
69 |
70 |
71 | {category}
72 |
73 |
74 | {groupedRoutes[category].map(({ name, route }) => (
75 |
81 | ))}
82 |
83 |
84 | ))}
85 |
86 |
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/packages/core/useScroll/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: sensors
3 | name: useScroll
4 | description: Reactive scroll values for a react ref or a dom node
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useScroll
14 |
15 | Reactive scroll values for a react ref or a dom node.
16 | Takes a callback that takes parameters with number between 0 and 1, indicating the progress of the scroll.
17 |
18 | ## Usage
19 |
20 | ```tsx
21 | import { useRef } from 'react'
22 | import { useScroll } from '@react-hooks-library/core'
23 |
24 | export function Demo() {
25 | const box = useRef(null)
26 | const [scroll, setScroll] = useState({ x: 0, y: 0 })
27 |
28 | useScroll(box, ({ scrollX, scrollY }) =>
29 | setScroll({ x: scrollX, y: scrollY })
30 | )
31 |
32 | return (
33 |
34 |
Scroll Vertically and Horizontally
35 |
36 |
37 | )
38 | }
39 | ```
40 |
41 | ### A Scroll Progress Bar Example
42 |
43 | ```tsx
44 | import { useRef } from 'react'
45 | import { useScroll } from '@react-hooks-library/core'
46 |
47 | export function Demo() {
48 | const box = useRef(null)
49 | const scrollProgress = useRef(null)
50 |
51 | useScroll(box, ({ scrollX }) => {
52 | if(!scrollProgress.current) return
53 |
54 | scrollProgress.current.style.width = `${scrollX * 100}%`
55 | })
56 |
57 | return (
58 |
61 | )
62 | }
63 | ```
64 |
65 | ## A Note on Performance
66 |
67 | You should avoid updating state inside the hook's callback if possible, updating state on every scroll action can be expensive and cause performance issues.
68 |
69 | The first example above update updates state for demonstration only.
70 |
71 | The second example updates the progress bar's width on every scroll action by mutating the DOM and hence bypassing any react related code.
72 |
73 | ## Type Declarations
74 |
75 | ```typescript
76 | /**
77 | * Reactive scroll values for a react ref or a dom node
78 | *
79 | * @param target - dom node or react ref
80 | * @param callback - callback to run on scroll
81 | *
82 | * @see https://react-hooks-library.vercel.app/core/useScroll
83 | */
84 | declare function useScroll(target: MaybeRef, callback: (coords: {
85 | scrollX: number;
86 | scrollY: number;
87 | }) => void): void;
88 |
89 | ```
90 |
91 | ## Source
92 |
93 |
94 |
--------------------------------------------------------------------------------
/packages/core/useScrollIntoView/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: browser
3 | name: useScrollIntoView
4 | description: A hook to scroll an element into view on mounting
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useScrollIntoView
14 |
15 | A hook to scroll an element into view on mounting.
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { useRef } from 'react'
21 | import { useScrollIntoView } from '@react-hooks-library/core'
22 |
23 | const routes = [
24 | 'BreakPointHooks',
25 | 'useActiveElement',
26 | 'useClickOutside',
27 | 'useScrollIntoView'
28 | ]
29 |
30 | function ListItem({ isActive, name }) {
31 | const activeEl = useRef(null)
32 |
33 | useScrollIntoView(activeEl, {
34 | scrollMargin: '8rem',
35 | block: 'end',
36 | predicate: isActive
37 | })
38 |
39 | return (
40 |
41 | {name}
42 |
43 | )
44 | }
45 |
46 | export function Demo() {
47 | return (
48 |
49 |
50 | {routes.map((name) => (
51 |
56 | ))}
57 |
58 |
59 | )
60 | }
61 | ```
62 |
63 | ## Type Declarations
64 |
65 | ```typescript
66 | interface UseScrollIntoViewOptions extends ScrollIntoViewOptions {
67 | /**
68 | * Defines vertical alignment.
69 | * One of `'start'`, `'center'`, `'end'`, or `'nearest'`
70 | *
71 | * @default 'start'
72 | */
73 | block?: ScrollLogicalPosition
74 | /**
75 | * Defines horizontal alignment.
76 | * One of `start`, `center`, `end`, or `nearest`. Defaults to nearest.
77 | *
78 | * @default 'nearest'
79 | */
80 | inline?: ScrollLogicalPosition
81 | /**
82 | * Defines the transition animation.
83 | * One of 'auto' or 'smooth'.
84 | *
85 | * @default 'auto'
86 | */
87 | behavior?: ScrollBehavior
88 | /**
89 | * The margin around the element to make sure it's visible.
90 | *
91 | * @default '0px'
92 | */
93 | scrollMargin?: string
94 | /**
95 | * The condition to decide if the element should be scrolled into view.
96 | *
97 | * @default true
98 | */
99 | predicate?: boolean | (() => boolean)
100 | }
101 | /**
102 | *
103 | * A hook to scroll an element into view on mounting.
104 | *
105 | * @param options {UseScrollIntoViewOptions}
106 | *
107 | * @see https://react-hooks-library.vercel.app/core/useScrollIntoView
108 | */
109 | declare function useScrollIntoView(
110 | target: MaybeRef,
111 | options?: UseScrollIntoViewOptions
112 | ): void
113 | ```
114 | ## Recommended Reading
115 | - [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView)
116 |
117 | ## Source
118 |
119 |
120 |
--------------------------------------------------------------------------------
/packages/core/useMutationObserver/docs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | category: browser
3 | name: useMutationObserver
4 | description: Watch for changes being made to the DOM tree.
5 | ---
6 |
7 | import { Demo } from './demo.tsx'
8 |
9 |
10 |
11 |
12 |
13 | # useMutationObserver
14 |
15 | Watch for changes being made to the DOM tree. Reactive [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
16 |
17 | You can observer react refs or DOM nodes directtly.
18 | Automatically stops observering when unmounted but returns a manual stop obsering method.
19 |
20 | ## Usage
21 |
22 | ```tsx
23 | import { useRef, useState } from 'react'
24 | import { useMutationObserver } from '@react-hooks-library/core'
25 |
26 | export function Demo() {
27 | const ref = useRef(null)
28 | const [observed, setObserved] = useState(false)
29 |
30 | useMutationObserver(
31 | ref,
32 | (mutations) => {
33 | for (const mutation of mutations) {
34 | if (mutation.type === 'attributes') {
35 | setObserved(true)
36 | }
37 | }
38 | },
39 | { attributes: true }
40 | )
41 |
42 | const addAttribute = () => {
43 | if (!ref.current) return
44 |
45 | ref.current.setAttribute('data-mut', 'hello world')
46 | }
47 |
48 | return (
49 |
50 |
51 | {observed
52 | ? 'Observed attribute change to node'
53 | : 'No changes observed yet'}
54 |
55 |
56 |
57 | {observed ? 'Added Attribute To Node' : 'Add Attribute To Node'}
58 |
59 |
60 | )
61 | }
62 |
63 | ```
64 |
65 | ```tsx
66 | import { useMutationObserver } from '@react-hooks-library/core'
67 |
68 | export function Demo() {
69 |
70 | const { isSupported, stop } = useMutationObserver(
71 | document.querySelector('#my-element'),
72 | (mutations) => {
73 | for (const mutation of mutations) {
74 | if (mutation.type === 'childList') {
75 | console.log("Observer change to child nodes")
76 | }
77 | }
78 | },
79 | { childList: true }
80 | )
81 | }
82 | ```
83 |
84 | ## Type Declarations
85 |
86 | ```typescript
87 | /**
88 | * Watch for changes being made to the DOM tree.
89 | *
90 | * @param target - React ref or DOM node
91 | * @param callback - callback to execute when mutations are observed
92 | * @param options - Options passed to mutation observer
93 | *
94 | * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver MutationObserver MDN
95 | * @see https://react-hooks-library.vercel.app/core/useMutationObserver
96 | */
97 | declare function useMutationObserver(
98 | target: MaybeRef,
99 | callback: MutationCallback,
100 | options?: MutationObserverInit): { isSupported: boolean, stop: () => void }
101 | )
102 | ```
103 |
104 | ## Source
105 |
106 |
107 |
--------------------------------------------------------------------------------
/website/utils/loadMDX.ts:
--------------------------------------------------------------------------------
1 | import fg from 'fast-glob'
2 | import fs from 'fs'
3 | import matter from 'gray-matter'
4 | import { bundleMDX } from 'mdx-bundler'
5 | import { join } from 'path'
6 | import rehypeAutolink from 'rehype-autolink-headings'
7 | import rehypeSlug from 'rehype-slug'
8 | import remarkGfm from 'remark-gfm'
9 | import remarkPrism from 'remark-prism'
10 |
11 | import { FunctionMeta } from '../../types'
12 |
13 | function findFunctions(dir: string, ignore: string[] = []): string[] {
14 | return fg.sync('*', {
15 | onlyDirectories: true,
16 | cwd: dir,
17 | ignore: ['**/dist', '**/node_modules', '_*', ...ignore]
18 | })
19 | }
20 |
21 | function findRootDir(base = __dirname) {
22 | const exists = fs.existsSync(join(base, 'rollup.config.js'))
23 |
24 | return exists ? base : findRootDir(join(base, '..'))
25 | }
26 |
27 | const ROOT_DIR = findRootDir()
28 | const PACKAGES_DIR = join(ROOT_DIR, 'packages')
29 |
30 | /**
31 | * Get meta data of all posts
32 | */
33 | export async function getAllFunctionsMeta() {
34 | const packages = findFunctions(PACKAGES_DIR)
35 |
36 | const allFunctions: FunctionMeta[] = []
37 |
38 | for (const pkg of packages) {
39 | const functions = findFunctions(join(PACKAGES_DIR, pkg)).sort()
40 |
41 | for (const name of functions) {
42 | const post = fs.readFileSync(
43 | join(PACKAGES_DIR, pkg, name, 'docs.mdx'),
44 | 'utf-8'
45 | )
46 | const meta = matter(post).data
47 | allFunctions.push({
48 | ...meta,
49 | pkg,
50 | name
51 | } as FunctionMeta)
52 | }
53 | }
54 |
55 | return allFunctions
56 | }
57 |
58 | /**
59 | *
60 | * @param source source mdx file path
61 | * @param cwd source mdx file path
62 | * @returns bundled react component
63 | */
64 | export async function loadMdx(source: string, cwd?: string) {
65 | const { code, frontmatter } = await bundleMDX(source, {
66 | cwd,
67 | xdmOptions(options) {
68 | options.remarkPlugins = [
69 | ...(options?.remarkPlugins ?? []),
70 | remarkGfm,
71 | remarkPrism
72 | ]
73 | options.rehypePlugins = [
74 | ...(options?.rehypePlugins ?? []),
75 | rehypeSlug,
76 | [
77 | rehypeAutolink,
78 | { content: { type: 'text', value: '#' }, behaviour: 'append' }
79 | ]
80 | ]
81 | return options
82 | }
83 | })
84 |
85 | const meta = { ...frontmatter } as FunctionMeta
86 | return { meta, code }
87 | }
88 |
89 | /**
90 | * Get a single post content by slug
91 | *
92 | * @param pkg package name
93 | * @param name function name
94 | * @returns bundled react component and meta data
95 | */
96 | export async function getFunction(pkg: string, name: string) {
97 | const folderPath = join(PACKAGES_DIR, pkg, name)
98 | const source = fs.readFileSync(join(folderPath, 'docs.mdx'), 'utf-8')
99 |
100 | const { code, meta } = await loadMdx(source, folderPath)
101 |
102 | const _meta = { ...meta, pkg, name } as FunctionMeta
103 | return { meta: _meta, code }
104 | }
105 |
--------------------------------------------------------------------------------
/packages/core/useMouse/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export type Position = { x: number; y: number }
4 |
5 | export interface MouseOptions {
6 | /**
7 | * Mouse position based by page or client
8 | *
9 | * @default 'client'
10 | */
11 | type?: 'page' | 'client'
12 |
13 | /**
14 | * Listen to `touchmove` events
15 | *
16 | * @default true
17 | */
18 | touch?: boolean
19 |
20 | /**
21 | * Reset to initial value when `touchend` event fired
22 | *
23 | * @default false
24 | */
25 | resetOnTouchEnds?: boolean
26 |
27 | /**
28 | * Initial values
29 | */
30 | initialValue?: Position
31 | }
32 |
33 | export type MouseSource = 'mouse' | 'touch' | null
34 |
35 | /**
36 | *
37 | * Reactive mouse position based by page or client
38 | *
39 | * @param options
40 | *
41 | * @see https://react-hooks-library.vercel.app/core/useMouse
42 | */
43 | export function useMouse(options: MouseOptions = {}) {
44 | const {
45 | touch = true,
46 | type = 'client',
47 | resetOnTouchEnds = false,
48 | initialValue = { x: 0, y: 0 }
49 | } = options
50 |
51 | const [x, setX] = useState(initialValue.x)
52 | const [y, setY] = useState(initialValue.y)
53 |
54 | const [source, setSource] = useState(null)
55 |
56 | useEffect(() => {
57 | const mouseHandler = (event: MouseEvent) => {
58 | setSource('mouse')
59 |
60 | if (type === 'page') {
61 | setX(event.pageX)
62 | setY(event.pageY)
63 | } else if (type === 'client') {
64 | setX(event.clientX)
65 | setY(event.clientY)
66 | }
67 | }
68 |
69 | const reset = () => {
70 | setX(initialValue.x)
71 | setY(initialValue.y)
72 | }
73 |
74 | const touchHandler = (event: TouchEvent) => {
75 | if (event.touches.length > 0) {
76 | setSource('touch')
77 | if (type === 'page') {
78 | setX(event.touches[0].pageX)
79 | setY(event.touches[0].pageY)
80 | } else if (type === 'client') {
81 | setX(event.touches[0].clientX)
82 | setY(event.touches[0].clientY)
83 | }
84 | }
85 | }
86 |
87 | window.addEventListener('mousemove', mouseHandler, { passive: true })
88 | window.addEventListener('dragover', mouseHandler, { passive: true })
89 |
90 | if (touch) {
91 | window.addEventListener('touchstart', touchHandler, { passive: true })
92 | window.addEventListener('touchmove', touchHandler, { passive: true })
93 |
94 | if (resetOnTouchEnds)
95 | window.addEventListener('touchend', reset, { passive: true })
96 | }
97 |
98 | return () => {
99 | window.removeEventListener('mousemove', mouseHandler)
100 | window.removeEventListener('dragover', mouseHandler)
101 |
102 | if (touch) {
103 | window.removeEventListener('touchstart', touchHandler)
104 | window.removeEventListener('touchmove', touchHandler)
105 |
106 | if (resetOnTouchEnds) window.removeEventListener('touchend', reset)
107 | }
108 | }
109 | }, [initialValue.x, initialValue.y, resetOnTouchEnds, touch, type])
110 |
111 | return { x, y, source }
112 | }
113 |
--------------------------------------------------------------------------------
/packages/core/useKeyStroke/index.ts:
--------------------------------------------------------------------------------
1 | import { MaybeRef } from '@react-hooks-library/shared'
2 | import { useCallback } from 'react'
3 |
4 | import { _window } from '../_ssr.config'
5 | import { useEventListener } from '../useEventListener'
6 |
7 | export type Keys = string | string[]
8 | export type KeyStrokeEventName = 'keydown' | 'keypress' | 'keyup'
9 | export type KeyStrokeOptions = {
10 | /** Key Stroke Event Name
11 | *
12 | * Can only be one of the following:
13 | * - keydown
14 | * - keypress
15 | * - keyup
16 | */
17 | eventName?: KeyStrokeEventName
18 |
19 | /** The DOM node to attach the event listener to
20 | * @default window
21 | */
22 | target?: MaybeRef
23 |
24 | /**
25 | * when `true` will use `event.code`
26 | *
27 | * when `false` will use `event.key`
28 | *
29 | * @default false
30 | */
31 | code?: boolean
32 |
33 | /** TA boolean value that, if true,
34 | * indicates that the function specified by listener
35 | * will never call `preventDefault()`.
36 | *
37 | * @default window
38 | */
39 | passive?: false
40 | }
41 |
42 | /**
43 | * Listen for keyboard keys being stroked.
44 | *
45 | * @param keys
46 | * @param handler
47 | * @param options
48 | *
49 | * @see https://react-hooks-library.vercel.app/core/useKeyStroke
50 | */
51 | export function useKeyStroke(
52 | keys: Keys,
53 | handler: (event: KeyboardEvent) => void,
54 | options: KeyStrokeOptions = {}
55 | ) {
56 | const {
57 | target = _window,
58 | eventName = 'keydown',
59 | passive = false,
60 | code = false
61 | } = options
62 |
63 | const listener = useCallback(
64 | (e: KeyboardEvent) => {
65 | const eventKey = code ? e.code : e.key
66 |
67 | keys.includes(eventKey) && handler(e)
68 | },
69 | [code, handler, keys]
70 | )
71 |
72 | return useEventListener(target, eventName, listener, { passive })
73 | }
74 |
75 | /**
76 | * Listen for keyboard keys on keydown.
77 | *
78 | * @param keys
79 | * @param handler
80 | * @param options
81 | *
82 | * @see https://react-hooks-library.vercel.app/core/useKeyStroke
83 | */
84 | export function useKeyDown(
85 | keys: Keys,
86 | handler: (event: KeyboardEvent) => void,
87 | options: KeyStrokeOptions = {}
88 | ) {
89 | return useKeyStroke(keys, handler, { ...options, eventName: 'keydown' })
90 | }
91 |
92 | /**
93 | * Listen for keyboard keys on keypress.
94 | *
95 | * @param keys
96 | * @param handler
97 | * @param options
98 | *
99 | * @see https://react-hooks-library.vercel.app/core/onKeyStroke
100 | */
101 | export function useKeyPressed(
102 | keys: Keys,
103 | handler: (event: KeyboardEvent) => void,
104 | options: KeyStrokeOptions = {}
105 | ) {
106 | return useKeyStroke(keys, handler, { ...options, eventName: 'keypress' })
107 | }
108 |
109 | /**
110 | * Listen for keyboard keys on keyup.
111 | *
112 | * @param keys
113 | * @param handler
114 | * @param options
115 | *
116 | * @see https://react-hooks-library.vercel.app/core/onKeyStroke
117 | */
118 | export function useKeyUp(
119 | keys: Keys,
120 | handler: (event: KeyboardEvent) => void,
121 | options: KeyStrokeOptions = {}
122 | ) {
123 | return useKeyStroke(keys, handler, { ...options, eventName: 'keyup' })
124 | }
125 |
--------------------------------------------------------------------------------
/packages/core/useIntersectionObserver/index.ts:
--------------------------------------------------------------------------------
1 | import { MaybeRef, noop, unRef } from '@react-hooks-library/shared'
2 | import { useCallback, useEffect, useRef, useState } from 'react'
3 |
4 | import { _document } from '../_ssr.config'
5 | import { useIsSupported } from '../useIsSupported'
6 | import { useUnMount } from '../useUnMount'
7 |
8 | export interface IntersectionObserverOptions {
9 | /**
10 | * The Element or Document whose bounds are used as the bounding box when testing for intersection.
11 | *
12 | * @default document
13 | */
14 | root?: MaybeRef
15 |
16 | /**
17 | * A string which specifies a set of offsets to add to the root's bounding_box when calculating intersections.
18 | *
19 | * @default '0px'
20 | */
21 | rootMargin?: string
22 |
23 | /**
24 | * Either a single number or an array of numbers between 0.0 and 1.
25 | *
26 | * @default 0
27 | */
28 | threshold?: number | number[]
29 | }
30 |
31 | /**
32 | * Reactive intersection observer.
33 | *
34 | * @param target - React ref or DOM node
35 | * @param options - Options passed to mutation observer
36 | * @param callback - callback to execute when mutations are observed
37 | *
38 | * @see https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver IntersectionObserver MDN
39 | * @see https://react-hooks-library.vercel.app/core/useIntersectionObserver
40 | */
41 | export function useIntersectionObserver(
42 | target: MaybeRef,
43 | options: IntersectionObserverOptions = {},
44 | callback: IntersectionObserverCallback = noop
45 | ) {
46 | const { root = _document, rootMargin = '0px', threshold = 0 } = options
47 |
48 | const [inView, setInView] = useState(false)
49 | const [entry, setEntry] = useState(null)
50 | const isSupported = useIsSupported(() => 'IntersectionObserver' in window)
51 |
52 | const observer = useRef(null)
53 |
54 | const stop = useCallback(() => {
55 | if (!observer.current) return
56 |
57 | observer.current.disconnect()
58 | observer.current = null
59 | }, [])
60 |
61 | useUnMount(stop)
62 |
63 | useEffect(() => {
64 | const el = unRef(target)
65 | const rootEl = unRef(root)
66 |
67 | if (!(isSupported && el && rootEl)) return
68 |
69 | observer.current = new window.IntersectionObserver(
70 | (
71 | entries: IntersectionObserverEntry[],
72 | observer: IntersectionObserver
73 | ) => {
74 | const thresholds = Array.isArray(threshold) ? threshold : [threshold]
75 |
76 | entries.forEach((entry) => {
77 | const inView =
78 | entry.isIntersecting &&
79 | thresholds.some((threshold) => entry.intersectionRatio >= threshold)
80 |
81 | setInView(inView)
82 | setEntry(entry)
83 | })
84 | callback(entries, observer)
85 | },
86 | {
87 | root: rootEl,
88 | rootMargin,
89 | threshold
90 | }
91 | )
92 |
93 | observer.current?.observe(el)
94 |
95 | return stop
96 | }, [callback, isSupported, root, rootMargin, stop, target, threshold])
97 |
98 | return {
99 | isSupported,
100 | stop,
101 | inView,
102 | entry
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/packages/core/BreakPointHooks/index.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery'
2 | export * from './breakpoints'
3 | import { _window } from '../_ssr.config'
4 |
5 | function match(query: string): boolean {
6 | if (!_window) return false
7 |
8 | return _window.matchMedia(query).matches
9 | }
10 |
11 | /**
12 | * Reactive hooks and utilities to be used with user provided breakpoints.
13 | *
14 | * @param {string} breakpoints
15 | * @returns functions to be used as hooks
16 | *
17 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
18 | */
19 | export function BreakPointHooks>(
20 | breakpoints: BreakPoints
21 | ) {
22 | type BreakPointsKey = keyof BreakPoints
23 |
24 | return {
25 | /**
26 | * Hook that returns a boolean if screen width is greater than given breakpoint.
27 | *
28 | * @param k {string} breakpoint
29 | * @returns boolean
30 | *
31 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
32 | **/
33 | useGreater: (k: BreakPointsKey) => {
34 | return useMediaQuery(`(min-width: ${breakpoints[k]}px)`)
35 | },
36 |
37 | /**
38 | * Hook that returns a boolean if screen width is smaller than given breakpoint.
39 | *
40 | * @param k {string} breakpoint
41 | * @param k {string} breakpoint
42 | *
43 | * @returns boolean
44 | *
45 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
46 | **/
47 | useSmaller: (k: BreakPointsKey) => {
48 | return useMediaQuery(`(max-width: ${breakpoints[k]}px)`)
49 | },
50 |
51 | /**
52 | * Hook that returns a boolean if screen width is between two given breakpoint.
53 | *
54 | * @param a {string} breakpoint
55 | * @param b {string} breakpoint
56 | *
57 | * @returns boolean
58 | *
59 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
60 | **/
61 | useBetween: (a: BreakPointsKey, b: BreakPointsKey) => {
62 | return useMediaQuery(
63 | `(min-width: ${breakpoints[a]}px) and (max-width: ${breakpoints[b]}px)`
64 | )
65 | },
66 |
67 | /**
68 | * Utility function that returns a boolean if screen width is greater than given breakpoint.
69 | *
70 | * @param k {string} breakpoint
71 | *
72 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
73 | **/
74 | isGreater(k: BreakPointsKey) {
75 | return match(`(min-width: ${breakpoints[k]}px)`)
76 | },
77 |
78 | /**
79 | * Utility function that returns a boolean if screen width is smaller than given breakpoint.
80 | *
81 | * @param k {string} breakpoint
82 | *
83 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
84 | **/
85 | isSmaller(k: BreakPointsKey) {
86 | return match(`(max-width: ${breakpoints[k]}px)`)
87 | },
88 |
89 | /**
90 | * Utility function that returns a boolean if screen width is between two given breakpoint.
91 | *
92 | * @param k {string} breakpoint
93 | *
94 | * @see https://react-hooks-library.vercel.app/core/BreakPointHooks
95 | **/
96 | isInBetween(a: BreakPointsKey, b: BreakPointsKey) {
97 | return match(
98 | `(min-width: ${breakpoints[a]}px) and (max-width: ${breakpoints[b]}px)`
99 | )
100 | }
101 | }
102 | }
103 |
104 | export type BreakPointHooksReturn = ReturnType
105 |
--------------------------------------------------------------------------------
/website/pages/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import Link from 'next/link'
3 |
4 | const features = [
5 | {
6 | img: '🔮',
7 | title: 'Typescript',
8 | description:
9 | 'Written in typescript so you get the advantage of strong type safety'
10 | },
11 | {
12 | img: '🧠',
13 | title: 'Server Side Ready',
14 | description:
15 | 'All hooks handle SSR rendering and work well with frameworks like Next/Gatsby'
16 | },
17 | {
18 | img: '🌿',
19 | title: 'Tree Shakable',
20 | description:
21 | 'Exported as es modules, import cost for individual function is tiny'
22 | },
23 |
24 | {
25 | img: '🎡',
26 | title: 'Interactive Demos',
27 | description: 'All hooks have a demo example to demonstrate their use'
28 | }
29 | ]
30 |
31 | const Logo = () => (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )
43 |
44 | const Features = () => (
45 |
46 | {features.map(({ img, description, title }) => (
47 |
48 |
49 |
50 |
{img}
51 |
52 | {img}
53 |
54 |
55 |
56 |
57 |
{title}
58 |
{description}
59 |
60 |
61 | ))}
62 |
63 | )
64 |
65 | const CallToAction = () => (
66 | <>
67 |
68 |
A collection of hooks and utilities for React
69 |
70 | npm i @react-hooks-library/core
71 |
72 |
73 |
85 | >
86 | )
87 |
88 | export default function Home() {
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
96 |
106 |
107 | )
108 | }
109 |
--------------------------------------------------------------------------------