@gmana/react-hooks
Version:
1,665 lines (1,646 loc) • 46.6 kB
TypeScript
import * as react0 from "react";
import React$1, { CSSProperties, DependencyList, Dispatch, EffectCallback, RefObject, SetStateAction, useEffect } from "react";
//#region src/lib/use-array.d.ts
/**
*
* @param defaultValue
* @returns
* @example
* ```tsx
* import { useArray } from '@gmana/hook';
*
* export default function ArrayComponent() {
* const { array, set, push, remove, filter, update, clear } = useArray([1, 2, 3, 4, 5, 6]);
*
* return (
* <div className="space-x-3 text-white divide-x-2 divide-gray-400">
* <div>{array.join(', ')}</div>
* <button onClick={() => push(7)}>Add 7</button>
* <button onClick={() => update(1, 9)}>Change Second Element To 9</button>
* <button onClick={() => remove(1)}>Remove Second Element</button>
* <button onClick={() => filter((n) => n < 4)}>Keep Numbers Less Than 4</button>
* <button onClick={() => set([1, 2])}>Set To 1, 2</button>
* <button onClick={clear}>Clear</button>
* </div>
* );
* }
* ```
*/
declare function useArray<T>(defaultValue: T[]): {
array: T[];
set: react0.Dispatch<react0.SetStateAction<T[]>>;
push: (element: T) => void;
filter: (callback: (n: T) => boolean) => void;
update: (index: number, newElement: T) => void;
remove: (index: number) => void;
clear: () => void;
};
//#endregion
//#region src/lib/use-async-effect.d.ts
/**
* A hook that allows you to use async functions in useEffect.
* Automatically handles cleanup to prevent memory leaks.
*
* @param asyncFunction - The async function to execute
* @param deps - Dependency array for the effect
*
* @example
* ```tsx
* import { useAsyncEffect } from '@gmana/react-hooks'
* import { useState } from 'react'
*
* function UserProfile({ userId }: { userId: string }) {
* const [user, setUser] = useState(null)
* const [loading, setLoading] = useState(true)
*
* useAsyncEffect(async (isCancelled) => {
* setLoading(true)
*
* try {
* const response = await fetch(`/api/users/${userId}`)
* const userData = await response.json()
*
* // Check if component is still mounted before updating state
* if (!isCancelled()) {
* setUser(userData)
* setLoading(false)
* }
* } catch (error) {
* if (!isCancelled()) {
* console.error('Failed to fetch user:', error)
* setLoading(false)
* }
* }
* }, [userId])
*
* if (loading) return <div>Loading...</div>
* if (!user) return <div>User not found</div>
*
* return <div>Hello, {user.name}!</div>
* }
* ```
*/
declare function useAsyncEffect(asyncFunction: (isCancelled: () => boolean) => Promise<void>, deps?: React.DependencyList): void;
//#endregion
//#region src/lib/use-boolean.d.ts
interface ReturnType$2 {
value: boolean;
setValue: Dispatch<SetStateAction<boolean>>;
setTrue: () => void;
setFalse: () => void;
toggle: () => void;
}
/**
*
* @param defaultValue
* @returns
* @example
* import React from 'react'
* import { useBoolean } from '@gmana/hook'
*
* export default function Component() {
* const { value, setValue, setTrue, setFalse, toggle } = useBoolean(false)
*
* // Just an example to use "setValue"
* const customToggle = () => setValue(x => !x)
*
* return (
* <>
* <p>
* Value is <code>{value.toString()}</code>
* </p>
* <button onClick={setTrue}>set true</button>
* <button onClick={setFalse}>set false</button>
* <button onClick={toggle}>toggle</button>
* <button onClick={customToggle}>custom toggle</button>
* </>
* )
* }
*/
declare function useBoolean(defaultValue?: boolean): ReturnType$2;
//#endregion
//#region src/lib/use-clipboard.d.ts
/**
* Hook to copy text to clipboard.
*/
declare function useClipboard(): {
isCopied: boolean;
copyToClipboard: (text: string) => Promise<void>;
};
//#endregion
//#region src/lib/use-cookies.d.ts
interface CookieOptions {
days?: number;
path?: string;
[key: string]: string | number | boolean | undefined;
}
interface EnhancedCookieOptions extends CookieOptions {
sameSite?: "strict" | "lax" | "none";
}
declare const setCookie: (name: string, value: string, options?: EnhancedCookieOptions) => void;
declare const getCookie: (name: string, initialValue?: string) => string;
declare const removeCookie: (name: string) => void;
//#endregion
//#region src/lib/use-copy-to-clipboard.d.ts
declare function useCopyToClipboard({
timeout,
onCopy
}?: {
timeout?: number;
onCopy?: () => void;
}): {
isCopied: boolean;
copyToClipboard: (value: string) => void;
};
//#endregion
//#region src/lib/use-countdown.d.ts
interface UseCountdownOptions {
/** Initial countdown time in seconds */
initialTime?: number;
/** Callback fired when countdown completes */
onComplete?: () => void;
/** Callback fired on each tick */
onTick?: (timeLeft: number) => void;
}
interface UseCountdownReturn {
/** Remaining time in seconds */
timeLeft: number;
/** Whether the countdown is currently active */
isActive: boolean;
/** Start/restart the countdown */
startTimer: () => void;
/** Stop and reset the countdown */
resetTimer: () => void;
/** Pause the countdown without resetting */
pauseTimer: () => void;
/** Resume a paused countdown */
resumeTimer: () => void;
/** Whether user can trigger a new countdown */
canResend: boolean;
}
/**
*
* @param options
* @returns
* @example
* ```tsx
* import { useCountdown } from '@gmana/react-hooks'
*
* // Simple usage (backward compatible)
const { timeLeft, startTimer, canResend } = useCountdown(60)
// Advanced usage with callbacks
const timer = useCountdown({
initialTime: 60,
onComplete: () => console.log("Timer finished!"),
onTick: (time) => console.log(`${time}s remaining`),
})
// New pause/resume functionality
<button onClick={timer.pauseTimer}>Pause</button>
<button onClick={timer.resumeTimer}>Resume</button>
* ```
*/
declare function useCountdown(options?: UseCountdownOptions | number): UseCountdownReturn;
//#endregion
//#region src/lib/use-counter.d.ts
interface ReturnType$1 {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
setCount: Dispatch<SetStateAction<number>>;
}
/**
*
* @param initialValue
* @returns
* @example
* import React from 'react'
* import { useCounter } from '@gmana/hook'
*
* export default function Component() {
* const { count, setCount, increment, decrement, reset } = useCounter(0)
*
* const multiplyBy2 = () => setCount(x => x * 2)
*
* return (
* <>
* <p>Count is {count}</p>
* <button onClick={increment}>Increment</button>
* <button onClick={decrement}>Decrement</button>
* <button onClick={reset}>Reset</button>
* <button onClick={multiplyBy2}>Multiply by 2</button>
* </>
* )
* }
*/
declare function useCounter(initialValue?: number): ReturnType$1;
//#endregion
//#region src/lib/use-dark-mode.d.ts
interface UseDarkModeOutput {
isDarkMode: boolean;
toggle: () => void;
enable: () => void;
disable: () => void;
}
/**
*
* @param defaultValue
* @returns
* @example
* import React from 'react'
* import { useDarkMode } from '@gmana/hook'
*
* export default function Component() {
* const { isDarkMode, toggle, enable, disable } = useDarkMode()
*
* return (
* <div>
* <p>Current theme: {isDarkMode ? 'dark' : 'light'}</p>
* <button onClick={toggle}>Toggle</button>
* <button onClick={enable}>Enable</button>
* <button onClick={disable}>Disable</button>
* </div>
* )
* }
*/
declare function useDarkMode(defaultValue?: boolean): UseDarkModeOutput;
//#endregion
//#region src/lib/use-debounce.d.ts
interface UseDebounceOptions {
/** The number of milliseconds to delay (default: 500) */
delay?: number;
/** Update immediately on first call, then debounce (default: false) */
leading?: boolean;
/** Update on trailing edge after delay (default: true) */
trailing?: boolean;
/** Maximum time to wait before forcing an update (optional) */
maxWait?: number;
}
interface UseDebounceReturn<T> {
/** The debounced value */
value: T;
/** Immediately update to the latest value, canceling pending updates */
flush: () => void;
/** Cancel any pending updates */
cancel: () => void;
/** Whether there's a pending update */
isPending: boolean;
}
/**
* Debounces a value, delaying updates until after wait milliseconds have elapsed
* since the last time the debounced value was invoked.
*
* @param value - The value to debounce
* @param options - Debounce options (or delay number for backward compatibility)
* @returns The debounced value (or object with value and controls if using options)
*
* @example
* ```tsx
* // Simple usage (backward compatible)
* const debouncedSearch = useDebounce(searchTerm, 500)
*
* // Advanced usage with options
* const { value, flush, cancel, isPending } = useDebounce(searchTerm, {
* delay: 500,
* leading: true,
* maxWait: 2000
* })
* ```
*/
declare function useDebounce<T>(value: T, delay?: number): T;
declare function useDebounce<T>(value: T, options: UseDebounceOptions): UseDebounceReturn<T>;
//#endregion
//#region src/lib/use-debounce-callback.d.ts
interface UseDebouncedCallbackOptions {
/** The number of milliseconds to delay */
delay: number;
/** Execute immediately on first call, then debounce (default: false) */
leading?: boolean;
/** Execute on trailing edge after delay (default: true) */
trailing?: boolean;
/** Maximum time to wait before forcing execution (optional) */
maxWait?: number;
}
interface DebouncedFunction<T extends (...args: any[]) => any> {
/** The debounced function */
(...args: Parameters<T>): void;
/** Immediately execute with the last provided arguments */
flush: () => void;
/** Cancel any pending execution */
cancel: () => void;
/** Whether there's a pending execution */
isPending: () => boolean;
}
/**
* Debounces a callback function, delaying its execution until after `delay` milliseconds
* have elapsed since the last time the debounced function was invoked.
*
* @param callback - The function to debounce
* @param options - Debounce options (or delay number for backward compatibility)
* @returns A debounced version of the callback with control methods
*
* @example
* ```tsx
* // Simple usage (backward compatible)
* const debouncedSave = useDebouncedCallback(saveData, 500)
*
* // Advanced usage with options
* const debouncedSearch = useDebouncedCallback(
* async (query: string) => {
* const results = await searchAPI(query)
* setResults(results)
* },
* {
* delay: 300,
* leading: true,
* maxWait: 1000
* }
* )
*
* // Manual control
* <button onClick={debouncedSearch.flush}>Search Now</button>
* <button onClick={debouncedSearch.cancel}>Cancel</button>
* // ✅ Form with manual controls
function SearchForm() {
const [query, setQuery] = useState('')
const search = useDebouncedCallback(
(searchQuery: string) => {
console.log('Searching for:', searchQuery)
},
{ delay: 500 }
)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
search.flush() // Execute immediately on submit
}
const handleCancel = () => {
search.cancel() // Cancel pending search
setQuery('')
}
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={(e) => {
setQuery(e.target.value)
search(e.target.value)
}}
/>
<button type="submit">Search Now</button>
<button type="button" onClick={handleCancel}>
Cancel
</button>
{search.isPending() && <span>Searching...</span>}
</form>
)
}
// ✅ Auto-save with maxWait
const autoSave = useDebouncedCallback(
(content: string) => saveDocument(content),
{
delay: 2000, // Wait 2s after typing stops
maxWait: 10000, // Force save every 10s regardless
}
)
// ✅ Scroll handler with leading edge
const handleScroll = useDebouncedCallback(
() => {
const scrollTop = window.scrollY
updateScrollPosition(scrollTop)
},
{
delay: 150,
leading: true, // Update immediately on first scroll
trailing: true // Update again when scrolling stops
}
)
useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
handleScroll.cancel() // Cleanup pending calls
}
}, [handleScroll])
* ```
*/
declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, delay: number): DebouncedFunction<T>;
declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, options: UseDebouncedCallbackOptions): DebouncedFunction<T>;
//#endregion
//#region src/lib/use-dimensions.d.ts
declare function useDimensions(containerRef: React$1.RefObject<HTMLElement | null>): {
width: number;
height: number;
};
//#endregion
//#region src/lib/use-effect-once.d.ts
/**
*
* @param effect
* @example
* import React, { useEffect, useState } from 'react'
* import { useEffectOnce } from '@gmana/hook'
*
* export default function Component() {
* const [data, setData] = useState<number>(0)
* useEffect(() => {
* console.log('Normal useEffect', { data })
* }, [data])
*
* useEffectOnce(() => {
* console.log('Triggered only once, on mount', { data })
* })
*
* return (
* <div>
* <p>Open your console</p>
* <button onClick={() => setData(Date.now())}>Update data</button>
* </div>
* )
* }
*/
declare function useEffectOnce(effect: EffectCallback): void;
//#endregion
//#region src/lib/use-element-size.d.ts
interface Size {
width: number;
height: number;
}
/**
* @description
* Use EventListener with simplicity by React Hook. It takes as parameters a `eventName`,
* a call-back functions (handler) and optionally a reference `element`. You can see above two examples using `useRef` and `window` based event.
* @returns
* @example
* import React, { useState } from 'react'
* import { useElementSize } from '@gmana/hook'
*
* export default function Component() {
* const [isVisible, setVisible] = useState(true)
* const [squareRef, { width, height }] = useElementSize()
*
* const toggleVisibility = () => setVisible(x => !x)
*
* return (
* <>
* <p>{`The square width is ${width}px and height ${height}px`}</p>
* <p>Try, resize your window and-or click on the button.</p>
*
* <button onClick={toggleVisibility}>
* {isVisible ? 'Hide' : 'Show'} square
* </button>
*
* {isVisible && (
* <div
* ref={squareRef}
* style={{
* width: '50%',
* paddingTop: '50%',
* backgroundColor: 'aquamarine',
* margin: 'auto',
* }}
* />
* )}
* </>
* )
* }
*/
declare function useElementSize<T extends HTMLElement = HTMLDivElement>(): [(node: T | null) => void, Size];
//#endregion
//#region src/lib/use-estimate-storage.d.ts
/**
* Estimates the available storage quota and usage.
* Returns the estimated remaining storage space in MB.
* @example
'use client';
import { useEstimateStorage } from '@gmana/hook';
export function Quota() {
const quota = useEstimateStorage();
return (
<div>
<h1>Quota: {(quota / 1024).toFixed(2)} GB</h1>
</div>
);
}
*/
declare function useEstimateStorage(): number;
//#endregion
//#region src/lib/use-event-listener.d.ts
/**
*
* @param eventName
* @param handler
* @param element
* @example
* import React, { useRef } from 'react'
* import { useEventListener } from '@gmana/hook'
*
* export default function Component() {
* // Define button ref
* const buttonRef = useRef<HTMLButtonElement>(null)
*
* const onScroll = (event: Event) => {
* console.log('window scrolled!', event)
* }
*
* const onClick = (event: Event) => {
* console.log('button clicked!', event)
* }
*
* // example with window based event
* useEventListener('scroll', onScroll)
*
* // example with element based event
* useEventListener('click', onClick, buttonRef)
*
* return (
* <div style={{ minHeight: '200vh' }}>
* <button ref={buttonRef}>Click me</button>
* </div>
* )
* }
*/
declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (event: WindowEventMap[K]) => void): void;
/**
*
* @param eventName
* @param handler
* @param element
* @example
* import React, { useRef } from 'react'
* import { useEventListener } from '@gmana/hook'
*
* export default function Component() {
* // Define button ref
* const buttonRef = useRef<HTMLButtonElement>(null)
*
* const onScroll = (event: Event) => {
* console.log('window scrolled!', event)
* }
*
* const onClick = (event: Event) => {
* console.log('button clicked!', event)
* }
*
* // example with window based event
* useEventListener('scroll', onScroll)
*
* // example with element based event
* useEventListener('click', onClick, buttonRef)
*
* return (
* <div style={{ minHeight: '200vh' }}>
* <button ref={buttonRef}>Click me</button>
* </div>
* )
* }
*/
declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLDivElement>(eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: RefObject<T>): void;
//#endregion
//#region src/lib/use-fetch.d.ts
interface State<T> {
data?: T;
error?: Error;
isLoading?: boolean;
}
/**
* @description
* Here is a React Hook which aims to retrieve data on an API using the native Fetch API.
*
* For advanced usages and optimizations,
* see these other hooks more powerful like
* - `useSWR`,
* - `useQuery`
* - `Redux Toolkit` (`RTK Query`).
* @param url
* @param options
* @returns
* @example
* import React from 'react'
* import { useFetch } from '@gmana/hook'
*
* const url = `http://jsonplaceholder.typicode.com/posts`
*
* interface Post {
* userId: number
* id: number
* title: string
* body: string
* }
*
* export default function Component() {
* const { data, error } = useFetch<Post[]>(url)
*
* if (error) return <p>There is an error.</p>
* if (!data) return <p>Loading...</p>
* return <p>{data[0].title}</p>
* }
*/
declare function useFetch<T = unknown>(url?: string, options?: RequestInit): State<T>;
//#endregion
//#region src/lib/use-focus-trap.d.ts
declare function useFocusTrap(isActive?: boolean): react0.RefObject<HTMLElement | null>;
//#endregion
//#region src/lib/use-hover.d.ts
/**
* @example
* import React, { useRef } from 'react'
* import { useHover } from '@gmana/hook';
*
* export default function Component() {
* const hoverRef = useRef(null)
* const isHover = useHover(hoverRef)
*
* return (
* <div ref={hoverRef}>
* {`The current div is ${isHover ? `hovered` : `un-hovered`}`}
* </div>
* )
* }
*/
declare function useHover<T extends HTMLElement = HTMLElement>(ComponentRef: RefObject<T>): boolean;
//#endregion
//#region src/lib/use-hydrated.d.ts
/**
* A hook that returns whether the component has been hydrated on the client side.
* This is essential for preventing hydration mismatches in Next.js and other SSR frameworks.
*
* Unlike useIsClient, this hook is specifically designed to handle the hydration phase
* and provides a safer way to conditionally render client-only content.
*
* @returns boolean indicating if the component is hydrated
* @example
* import React from 'react'
* import { useHydrated } from '@gmana/react-hooks'
*
* export default function Component() {
* const isHydrated = useHydrated()
*
* return (
* <div>
* <h1>My App</h1>
* {isHydrated ? (
* <ClientOnlyComponent />
* ) : (
* <div>Loading client content...</div>
* )}
* </div>
* )
* }
*
* @example
* // Using with media queries to prevent hydration mismatches
* function ResponsiveComponent() {
* const isHydrated = useHydrated()
* const isMobile = useMediaQuery('(max-width: 768px)')
*
* if (!isHydrated) {
* // Return a safe default that matches SSR
* return <div>Loading...</div>
* }
*
* return (
* <div>
* {isMobile ? <MobileLayout /> : <DesktopLayout />}
* </div>
* )
* }
*/
declare function useHydrated(): boolean;
//#endregion
//#region src/lib/use-image-on-load.d.ts
interface ImageStyle {
thumbnail: CSSProperties;
fullSize: CSSProperties;
}
interface ImageOnLoadType {
handleImageOnLoad: () => void;
css: ImageStyle;
}
/**
*
* @returns
* @example
* import React, { CSSProperties } from 'react'
* import { useImageOnLoad } from '@gmana/hook'
*
* export default function Component() {
* const { handleImageOnLoad, css } = useImageOnLoad()
*
* const style: { [key: string]: CSSProperties } = {
* wrap: {
* position: 'relative',
* width: 400,
* height: 400,
* margin: 'auto',
* },
* image: {
* position: 'absolute',
* top: 0,
* left: 0,
* width: `100%`,
* height: `100%`,
* },
* }
*
* return (
* <div style={style.wrap}>
*
* <img
* style={{ ...style.image, ...css.thumbnail }}
* src="https://via.placeholder.com/150"
* alt="thumbnail"
* />
*
* <img
* onLoad={handleImageOnLoad}
* style={{ ...style.image, ...css.fullSize }}
* src="https://via.placeholder.com/600"
* alt="fullImage"
* />
* </div>
* )
* }
*/
declare function useImageOnLoad(): ImageOnLoadType;
//#endregion
//#region src/lib/use-in-view.d.ts
declare const useIntersection: (element: RefObject<HTMLDivElement>, rootMargin: string) => boolean;
//#endregion
//#region src/lib/use-intersection-observer.d.ts
interface Args extends IntersectionObserverInit {
freezeOnceVisible?: boolean;
}
/**
*
* @param ComponentRef
* @param param1
* @returns
* @example
* import React, { useRef } from 'react'
* import { useIntersectionObserver } from '@gmana/hook'
*
* const Section = (props: { title: string }) => {
* const ref = useRef<HTMLDivElement | null>(null)
* const entry = useIntersectionObserver(ref, {})
* const isVisible = !!entry?.isIntersecting
*
* console.log(`Render Section ${props.title}`, { isVisible })
*
* return (
* <div
* ref={ref}
* style={{
* minHeight: '100vh',
* display: 'flex',
* border: '1px dashed #000',
* fontSize: '2rem',
* }}
* >
* <div style={{ margin: 'auto' }}>{props.title}</div>
* </div>
* )
* }
*
* export default function Component() {
* return (
* <>
* {Array.from({ length: 5 }).map((_, index) => (
* <Section key={index + 1} title={`${index + 1}`} />
* ))}
* </>
* )
* }
*/
declare function useIntersectionObserver(ComponentRef: RefObject<Element>, {
threshold,
root,
rootMargin,
freezeOnceVisible
}: Args): IntersectionObserverEntry | undefined;
//#endregion
//#region src/lib/use-interval.d.ts
/**
*
* @param callback
* @param delay
* @example
* import React, { ChangeEvent, useState } from 'react'
* import { useInterval } from '@gmana/hook'
*
* export default function Component() {
* // The counter
* const [count, setCount] = useState<number>(0)
* // Dynamic delay
* const [delay, setDelay] = useState<number>(1000)
* // ON/OFF
* const [isPlaying, setPlaying] = useState<boolean>(false)
*
* useInterval(
* () => {
* // Your custom logic here
* setCount(count + 1)
* },
* // Delay in milliseconds or null to stop it
* isPlaying ? delay : null,
* )
*
* const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
* setDelay(Number(event.target.value))
* }
*
* return (
* <>
* <h1>{count}</h1>
* <button onClick={() => setPlaying(!isPlaying)}>
* {isPlaying ? 'pause' : 'play'}
* </button>
* <p>
* <label htmlFor="delay">Delay: </label>
* <input
* type="number"
* name="delay"
* onChange={handleChange}
* value={delay}
* />
* </p>
* </>
* )
* }
*/
declare function useInterval(callback: () => void, delay: number | null): void;
//#endregion
//#region src/lib/use-is-client.d.ts
/**
*
* @returns
* @example
* import React from 'react'
* import { useIsClient } from '@gmana/hook'
*
* export default function Component() {
* const isClient = useIsClient()
*
* return <div>{isClient ? 'Client' : 'server'}</div>
* }
*/
declare function useIsClient(): boolean;
//#endregion
//#region src/lib/use-is-first-render.d.ts
/**
*
* @returns
* @example
* import React, { useEffect, useState } from 'react'
* import { useIsFirstRender } from '@gmana/hook'
*
* export default function Component() {
* const isFirst = useIsFirstRender()
* const [data, setData] = useState<number>(0)
*
* useEffect(() => {
* console.log('Normal useEffect', { data })
* }, [data])
*
* return (
* <div>
* <p>Open your console</p>
* <p>Is first render: {isFirst ? 'yes' : 'no'}</p>
* <button onClick={() => setData(Date.now())}>Update data</button>
* </div>
* )
* }
*/
declare function useIsFirstRender(): boolean;
//#endregion
//#region src/lib/use-is-mounted.d.ts
/**
*
* @returns
* @example
* import React, { useEffect, useState } from 'react'
* import { useIsMounted } from '@gmana/hook'
*
* const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
*
* function Child() {
* const [data, setData] = useState('loading')
* const isMounted = useIsMounted()
*
* // simulate an api call and update state
* useEffect(() => {
* void delay(3000).then(() => {
* if (isMounted()) setData('OK')
* })
* }, [isMounted])
*
* return <p>{data}</p>
* }
*
* export default function Component() {
* const [isVisible, setVisible] = useState<boolean>(false)
*
* const toggleVisibility = () => setVisible(state => !state)
*
* return (
* <>
* <button onClick={toggleVisibility}>{isVisible ? 'Hide' : 'Show'}</button>
*
* {isVisible && <Child />}
* </>
* )
* }
*/
declare function useIsMounted(): () => boolean;
//#endregion
//#region src/lib/use-isomorphic-layout-effect.d.ts
/**
*
* @example
* import React from 'react'
* import { useIsomorphicLayoutEffect } from '@gmana/hook'
*
* export default function Component() {
* useIsomorphicLayoutEffect(() => {
* console.log(
* "In the browser, I'm an `useLayoutEffect`, but in SSR, I'm an `useEffect`.",
* )
* }, [])
*
* return <p>Hello, world</p>
* }
*/
declare const useIsomorphicLayoutEffect: typeof useEffect;
//#endregion
//#region src/lib/use-local-storage.d.ts
declare global {
interface WindowEventMap {
"local-storage": CustomEvent;
}
}
type SetValue<T> = Dispatch<SetStateAction<T>>;
/**
*
* @param key
* @param initialValue
* @returns
* @example
* import React from 'react'
* import { useLocalStorage } from '@gmana/hook'
*
* export default function Component() {
* const [isDarkTheme, setDarkTheme] = useLocalStorage('darkTheme', true)
*
* const toggleTheme = () => {
* setDarkTheme(prevValue => !prevValue)
* }
*
* return (
* <button onClick={toggleTheme}>
* {`The current theme is ${isDarkTheme ? `dark` : `light`}`}
* </button>
* )
* }
*/
declare function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>];
//#endregion
//#region src/lib/use-locked-body.d.ts
type ReturnType = [boolean, (locked: boolean) => void];
/**
*
* @param initialLocked
* @returns
* @example
* import React, { CSSProperties, useState } from 'react'
* import { useLockedBody } from '@gmana/hook'
*
* const fixedCenterStyle: CSSProperties = {
* position: 'fixed',
* top: '50%',
* left: '50%',
* transform: 'translate(-50%, -50%)',
* }
*
* const fakeScrollableStyle: CSSProperties = {
* minHeight: '150vh',
* background: 'linear-gradient(palegreen, palegoldenrod, palevioletred)',
* }
*
* // Example 1: useLockedBody as useState()
* export default function App() {
* const [locked, setLocked] = useLockedBody()
*
* const toggleLocked = () => {
* setLocked(!locked)
* }
*
* return (
* <div style={fakeScrollableStyle}>
* <button style={fixedCenterStyle} onClick={toggleLocked}>
* {locked ? 'unlock scroll' : 'lock scroll'}
* </button>
* </div>
* )
* }
*
* // Example 2: useLockedBody with our custom state
* export function App2() {
* const [locked, setLocked] = useState(false)
*
* const toggleLocked = () => {
* setLocked(!locked)
* }
*
* useLockedBody(locked)
*
* return (
* <div style={fakeScrollableStyle}>
* <button style={fixedCenterStyle} onClick={toggleLocked}>
* {locked ? 'unlock scroll' : 'lock scroll'}
* </button>
* </div>
* )
* }
*/
declare function useLockedBody(initialLocked?: boolean): ReturnType;
//#endregion
//#region src/lib/use-map.d.ts
type MapOrEntries<K, V> = Map<K, V> | [K, V][];
interface Actions<K, V> {
set: (key: K, value: V) => void;
setAll: (entries: MapOrEntries<K, V>) => void;
remove: (key: K) => void;
reset: Map<K, V>["clear"];
}
type Return<K, V> = [Omit<Map<K, V>, "set" | "clear" | "delete">, Actions<K, V>];
/**
*
* @param initialState
* @returns
* @example
* import React, { Fragment } from 'react'
* import { MapOrEntries, useMap } from '@gmana/hook'
*
* const initialValues: MapOrEntries<string, string> = [['key', '🆕']]
* const otherValues: MapOrEntries<string, string> = [
* ['hello', '👋'],
* ['data', '📦'],
* ]
*
* export default function Component() {
* const [map, actions] = useMap<string, string>(initialValues)
*
* const set = () => actions.set(String(Date.now()), '📦')
* const setAll = () => actions.setAll(otherValues)
* const reset = () => actions.reset()
* const remove = () => actions.remove('hello')
*
* return (
* <div>
* <button onClick={set}>Add</button>
* <button onClick={reset}>Reset</button>
* <button onClick={setAll}>Set new data</button>
* <button onClick={remove} disabled={!map.get('hello')}>
* {'Remove "hello"'}
* </button>
*
* <pre>
* Map (
* {Array.from(map.entries()).map(([key, value]) => (
* <Fragment key={key}>{`\n ${key}: ${value}`}</Fragment>
* ))}
* <br />)
* </pre>
* </div>
* )
* }
*/
declare function useMap<K, V>(initialState?: MapOrEntries<K, V>): Return<K, V>;
//#endregion
//#region src/lib/use-media-query.d.ts
/**
*
* @param query
* @returns
* @example
const isDesktop = useMediaQuery("(min-width: 768px)")
*/
declare function useMediaQuery(query: string): boolean;
//#endregion
//#region src/lib/use-mobile.d.ts
declare function useIsMobile(): boolean;
//#endregion
//#region src/lib/use-network-information.d.ts
interface INetworkInformation {
isOnline: boolean;
effectiveType: string;
downlink: number;
rtt: number;
}
/**
* Hook that returns network information for the browser,
* including online status, effective connection type,
* downlink speed, and round trip latency.
* @example
import { useNetworkInformation } from "@gmana/hook";
export default function Network() {
const networkState = useNetworkInformation();
return <pre>{JSON.stringify(networkState, null, 2)}</pre>;
}
*/
declare const useNetworkInformation: () => INetworkInformation | null;
//#endregion
//#region src/lib/use-offline-detector.d.ts
/**
* Hook to detect offline status.
* Returns a boolean indicating if offline.
* @example
'use client';
import { useOfflineDetector } from '@gmana/hook';
import { useEffect, useState } from 'react';
export function OfflineBanner() {
const isOffline = useOfflineDetector();
const [showBanner, setShowBanner] = useState(() => isOffline);
useEffect(() => {
setShowBanner(isOffline);
}, [isOffline]);
return showBanner ? (
<div className="bg-gray-950 text-white px-3 py-1 flex justify-between items-center">
{isOffline ? <p>You are currently offline.</p> : <p>You are currently online.</p>}
<button onClick={() => setShowBanner(false)}>Close</button>
</div>
) : null;
}
*/
declare const useOfflineDetector: () => boolean;
//#endregion
//#region src/lib/use-on-click-outside.d.ts
type Handler = (event: MouseEvent) => void;
/**
*
* @param ref
* @param handler
* @param mouseEvent
* @example
* import React, { useRef } from 'react'
* import { useOnClickOutside } from '@gmana/tw'
*
* export default function Component() {
* const ref = useRef(null)
*
* const handleClickOutside = () => {
* // Your custom logic here
* console.log('clicked outside')
* }
*
* const handleClickInside = () => {
* // Your custom logic here
* console.log('clicked inside')
* }
*
* useOnClickOutside(ref, handleClickOutside)
*
* return (
* <button
* ref={ref}
* onClick={handleClickInside}
* style={{ width: 200, height: 200, background: 'cyan' }}
* />
* )
* }
// OR
const ref = useRef(null);
useOnClickOutside(ref, () => {
setIsClick(false);
});
*/
declare function useOnClickOutside<T extends HTMLElement = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent?: "mousedown" | "mouseup"): void;
//#endregion
//#region src/lib/use-previous.d.ts
/**
* Returns the previous value of a state or prop.
* Useful for comparing current and previous values in effects.
*
* @param value - The value to track
* @returns The previous value
*
* @example
* ```tsx
* import { usePrevious } from '@gmana/react-hooks'
* import { useState, useEffect } from 'react'
*
* function Counter() {
* const [count, setCount] = useState(0)
* const prevCount = usePrevious(count)
*
* useEffect(() => {
* if (prevCount !== undefined) {
* console.log(`Count changed from ${prevCount} to ${count}`)
* }
* }, [count, prevCount])
*
* return (
* <div>
* <p>Current count: {count}</p>
* <p>Previous count: {prevCount}</p>
* <button onClick={() => setCount(count + 1)}>Increment</button>
* </div>
* )
* }
* ```
*/
declare function usePrevious<T>(value: T): T | undefined;
//#endregion
//#region src/lib/use-read-local-storage.d.ts
type Value<T> = T | null;
/**
*
* @param key
* @returns
* @example
* import React from 'react'
* import { useReadLocalStorage } from '@gmana/hook'
*
* export default function Component() {
* // Assuming a value was set in localStorage with this key
* const darkMode = useReadLocalStorage('darkMode')
*
* return <p>DarkMode is {darkMode ? 'enabled' : 'disabled'}</p>
* }
*/
declare function useReadLocalStorage<T>(key: string): Value<T>;
//#endregion
//#region src/lib/use-screen.d.ts
/**
*
* @returns
* @example
* import React from 'react'
* import { useScreen } from '@gmana/hook'
*
* export default function Component() {
* const screen = useScreen()
*
* return (
* <div>
* The current window dimensions are:{' '}
* <code>
* {JSON.stringify({ width: screen?.width, height: screen?.height })}
* </code>
* </div>
* )
* }
*/
declare function useScreen(): Screen | undefined;
//#endregion
//#region src/lib/use-script.d.ts
type Status = "idle" | "loading" | "ready" | "error";
type ScriptElt = HTMLScriptElement | null;
/**
*
* @param src
* @returns
* @example
* import React, { useEffect } from 'react'
* import { useScript } from '@gmana/hook'
*
* // it's an example, use your types instead
* // eslint-disable-next-line @typescript-eslint/no-explicit-any
* declare const jQuery: any
*
* export default function Component() {
* // Load the script asynchronously
* const status = useScript(`https://code.jquery.com/jquery-3.5.1.min.js`)
*
* useEffect(() => {
* if (typeof jQuery !== 'undefined') {
* // jQuery is loaded => print the version
* // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
* alert(jQuery.fn.jquery)
* }
* }, [status])
*
* return (
* <div>
* <p>{`Current status: ${status}`}</p>
*
* {status === 'ready' && <p>You can use the script here.</p>}
* </div>
* )
* }
*/
declare function useScript(src: string): Status;
//#endregion
//#region src/lib/use-scroll.d.ts
/**
* Hook to track scroll position and determine if scrolled beyond a threshold.
* SSR-safe implementation that prevents hydration mismatches.
*
* @param threshold - The scroll threshold in pixels (default: 20)
* @returns boolean indicating if scrolled beyond threshold
* @example
* import React from 'react'
* import { useScroll } from '@gmana/react-hooks'
*
* export default function ScrollToTop() {
* const isScrolled = useScroll(100)
*
* return (
* <div>
* {isScrolled && (
* <button onClick={() => window.scrollTo(0, 0)}>
* Back to top
* </button>
* )}
* </div>
* )
* }
*/
declare function useScroll(threshold?: number): boolean;
//#endregion
//#region src/lib/use-sidebar.d.ts
declare function useSidebar(): {
open: boolean;
onOpenChange: (open: boolean) => void;
};
//#endregion
//#region src/lib/use-ssr.d.ts
/**
* A hook that provides information about the current environment (server vs client)
* and hydration status. Useful for preventing hydration mismatches in SSR applications.
*
* @returns Object containing environment and hydration status
* @example
* import React from 'react'
* import { useSsr } from '@gmana/hook'
*
* export default function Component() {
* const { isBrowser, isServer, isHydrated } = useSsr()
*
* // Prevents hydration mismatch by only showing client-specific content after hydration
* if (!isHydrated) {
* return <p>Loading...</p>
* }
*
* return <p>{isBrowser ? 'Browser' : 'Server'}!</p>
* }
*/
declare function useSsr(): {
isBrowser: boolean;
isServer: boolean;
isHydrated: boolean;
};
//#endregion
//#region src/lib/use-step.d.ts
interface Helpers {
goToNextStep: () => void;
goToPrevStep: () => void;
reset: () => void;
canGoToNextStep: boolean;
canGoToPrevStep: boolean;
setStep: Dispatch<SetStateAction<number>>;
}
/**
*
* @param maxStep
* @returns
* @example
* import React from 'react'
* import { useStep } from '@gmana/hook'
*
* export default function Component() {
* const [currentStep, helpers] = useStep(5)
*
* const {
* canGoToPrevStep,
* canGoToNextStep,
* goToNextStep,
* goToPrevStep,
* reset,
* setStep,
* } = helpers
*
* return (
* <>
* <p>Current step is {currentStep}</p>
* <p>Can go to previous step {canGoToPrevStep ? 'yes' : 'no'}</p>
* <p>Can go to next step {canGoToNextStep ? 'yes' : 'no'}</p>
* <button onClick={goToNextStep}>Go to next step</button>
* <button onClick={goToPrevStep}>Go to previous step</button>
* <button onClick={reset}>Reset</button>
* <button onClick={() => setStep(3)}>Set to step 3</button>
* </>
* )
* }
*
*/
declare function useStep(maxStep: number): [number, Helpers];
//#endregion
//#region src/lib/use-ternary-dark-mode.d.ts
type TernaryDarkMode = "system" | "dark" | "light";
interface UseTernaryDarkModeOutput {
isDarkMode: boolean;
ternaryDarkMode: TernaryDarkMode;
setTernaryDarkMode: Dispatch<SetStateAction<TernaryDarkMode>>;
toggleTernaryDarkMode: () => void;
}
/**
*
* @returns
* @example
* import React from 'react'
* import { useTernaryDarkMode } from '@gmana/hook'
*
* export default function Component() {
* const {
* isDarkMode,
* ternaryDarkMode,
* setTernaryDarkMode,
* toggleTernaryDarkMode,
* } = useTernaryDarkMode()
* type TernaryDarkMode = typeof ternaryDarkMode
*
* return (
* <div>
* <p>Current theme: {isDarkMode ? 'dark' : 'light'}</p>
* <p>ternaryMode: {ternaryDarkMode}</p>
* <p>
* Toggle between three modes
* <button onClick={toggleTernaryDarkMode}>
* Toggle from {ternaryDarkMode}
* </button>
* </p>
* <p>
* Select a mode
* <br />
* <select
* name="select-ternaryDarkMode"
* onChange={ev =>
* setTernaryDarkMode(ev.target.value as TernaryDarkMode)
* }
* value={ternaryDarkMode}
* >
* <option value="light">light</option>
* <option value="system">system</option>
* <option value="dark">dark</option>
* </select>
* </p>
* </div>
* )
* }
*
*/
declare function useTernaryDarkMode(): UseTernaryDarkModeOutput;
//#endregion
//#region src/lib/use-throttle.d.ts
/**
* Throttles a callback function, ensuring it's called at most once per wait milliseconds.
* Unlike debouncing, throttling ensures the function is executed regularly at intervals.
*
* @param callback - The function to throttle
* @param delay - The number of milliseconds to throttle executions to
* @param deps - Dependency array for the callback
* @returns A throttled version of the callback
*
* @example
* ```tsx
* import { useThrottle } from '@gmana/react-hooks'
* import { useState } from 'react'
*
* function ScrollComponent() {
* const [scrollY, setScrollY] = useState(0)
*
* const throttledScroll = useThrottle(
* () => setScrollY(window.scrollY),
* 100, // Update scroll position at most every 100ms
* [setScrollY]
* )
*
* React.useEffect(() => {
* window.addEventListener('scroll', throttledScroll)
* return () => window.removeEventListener('scroll', throttledScroll)
* }, [throttledScroll])
*
* return <div>Scroll Y: {scrollY}</div>
* }
* ```
*/
declare function useThrottle<T extends (...args: never[]) => unknown>(callback: T, delay: number, deps?: React.DependencyList): T;
//#endregion
//#region src/lib/use-timeout.d.ts
/**
* @example
* ```ts
* import { useState } from "react"
* import useTimeout from "@gmana/use-timeout"
*
* export default function TimeoutComponent() {
* const [count, setCount] = useState(10)
* const { clear, reset } = useTimeout(() => setCount(0), 1000)
*
* return (
* <div>
* <div>{count}</div>
* <button onClick={() => setCount(c => c + 1)}>Increment</button>
* <button onClick={clear}>Clear Timeout</button>
* <button onClick={reset}>Reset Timeout</button>
* </div>
* )
* }
* ```
*/
declare function useTimeout(callback: () => void, delay: number): {
reset: () => void;
clear: () => void;
};
//#endregion
//#region src/lib/use-toggle.d.ts
/**
*
* @param defaultValue
* @returns
* ```ts
* import useToggle from "@gmana/use-toggle"
*
* export default function ToggleComponent() {
* const [value, toggleValue] = useToggle(false)
*
* return (
* <div>
* <div>{value.toString()}</div>
* <button onClick={toggleValue}>Toggle</button>
* <button onClick={() => toggleValue(true)}>Make True</button>
* <button onClick={() => toggleValue(false)}>Make False</button>
* </div>
* )
* }
* ```
*/
declare function useToggle(defaultValue: boolean): (boolean | ((value: boolean) => void))[];
//#endregion
//#region src/lib/use-toggle-state.d.ts
type StateType = [boolean, () => void, () => void, () => void] & {
state: boolean;
open: () => void;
close: () => void;
toggle: () => void;
};
//#endregion
//#region src/lib/use-unload-warning.d.ts
declare function useUnloadWarning(condition?: boolean): void;
//#endregion
//#region src/lib/use-update-effect.d.ts
/**
*
* @param effect
* @param deps
* @example
* import React, { useEffect, useState } from 'react'
* import { useUpdateEffect } from '@gmana/hook'
*
* export default function Component() {
* const [data, setData] = useState<number>(0)
* useEffect(() => {
* console.log('Normal useEffect', { data })
* }, [data])
*
* useUpdateEffect(() => {
* console.log('Update useEffect only', { data })
* }, [data])
*
* return (
* <div>
* <p>Open your console</p>
* <button onClick={() => setData(Date.now())}>Update data</button>
* </div>
* )
* }
*/
declare function useUpdateEffect(effect: EffectCallback, deps?: DependencyList): void;
//#endregion
//#region src/lib/use-window-size.d.ts
interface WindowSize {
width: number;
height: number;
}
/**
*
* @returns
* @example
* import React from 'react'
* import { useWindowSize } from '@gmana/hook'
*
* export default function Component() {
* const { width, height } = useWindowSize()
*
* return (
* <div>
* The current window dimensions are:{' '}
* <code>{JSON.stringify({ width, height })}</code>
* </div>
* )
* }
*
*/
declare function useWindowSize(): WindowSize;
//#endregion
export { Actions, DebouncedFunction, INetworkInformation, MapOrEntries, ScriptElt, StateType, Status, UseCountdownOptions, UseCountdownReturn, UseDebounceOptions, UseDebounceReturn, UseDebouncedCallbackOptions, getCookie, removeCookie, setCookie, useArray, useAsyncEffect, useBoolean, useClipboard, useCopyToClipboard, useCountdown, useCounter, useDarkMode, useDebounce, useDebouncedCallback, useDimensions, useEffectOnce, useElementSize, useEstimateStorage, useEventListener, useFetch, useFocusTrap, useHover, useHydrated, useImageOnLoad, useIntersection, useIntersectionObserver, useInterval, useIsClient, useIsFirstRender, useIsMobile, useIsMounted, useIsomorphicLayoutEffect, useLocalStorage, useLockedBody, useMap, useMediaQuery, useNetworkInformation, useOfflineDetector, useOnClickOutside, usePrevious, useReadLocalStorage, useScreen, useScript, useScroll, useSidebar, useSsr, useStep, useTernaryDarkMode, useThrottle, useTimeout, useToggle, useUnloadWarning, useUpdateEffect, useWindowSize };