Full Code of uidotdev/usehooks for AI

main 945436df0037 cached
104 files
262.1 KB
68.7k tokens
95 symbols
1 requests
Download .txt
Showing preview only (288K chars total). Download the full file or copy to clipboard to get everything.
Repository: uidotdev/usehooks
Branch: main
Commit: 945436df0037
Files: 104
Total size: 262.1 KB

Directory structure:
gitextract_24ko9djh/

├── .gitignore
├── LICENSE
├── README.md
├── index.d.ts
├── index.js
├── package.json
├── tsconfig.json
└── usehooks.com/
    ├── .astro/
    │   └── types.d.ts
    ├── .eslintrc.json
    ├── .gitignore
    ├── .prettierrc
    ├── README.md
    ├── astro.config.mjs
    ├── generate-og-images.mjs
    ├── package.json
    ├── src/
    │   ├── components/
    │   │   ├── Button.astro
    │   │   ├── CodePreview.astro
    │   │   ├── CountdownTimer.tsx
    │   │   ├── HookDescription.astro
    │   │   ├── Install.astro
    │   │   ├── Logo.astro
    │   │   ├── LogoGithub.astro
    │   │   ├── QueryGGBanner.astro
    │   │   ├── RelatedHook.astro
    │   │   ├── StaticCodeContainer.astro
    │   │   ├── Svg.astro
    │   │   ├── codepreview/
    │   │   │   ├── CodePreview.tsx
    │   │   │   ├── CodeWrapper.tsx
    │   │   │   └── utils.ts
    │   │   └── search/
    │   │       ├── Callout.module.css
    │   │       ├── Callout.tsx
    │   │       ├── HookCard.module.css
    │   │       ├── HookCard.tsx
    │   │       ├── HookSearch.module.css
    │   │       ├── HookSearch.tsx
    │   │       ├── HookSort.module.css
    │   │       ├── HookSort.tsx
    │   │       ├── HooksList.module.css
    │   │       └── HooksList.tsx
    │   ├── content/
    │   │   ├── config.ts
    │   │   └── hooks/
    │   │       ├── useBattery.mdx
    │   │       ├── useClickAway.mdx
    │   │       ├── useContinuousRetry.mdx
    │   │       ├── useCopyToClipboard.mdx
    │   │       ├── useCountdown.mdx
    │   │       ├── useCounter.mdx
    │   │       ├── useDebounce.mdx
    │   │       ├── useDefault.mdx
    │   │       ├── useDocumentTitle.mdx
    │   │       ├── useEventListener.mdx
    │   │       ├── useFavicon.mdx
    │   │       ├── useFetch.mdx
    │   │       ├── useGeolocation.mdx
    │   │       ├── useHistoryState.mdx
    │   │       ├── useHover.mdx
    │   │       ├── useIdle.mdx
    │   │       ├── useIntersectionObserver.mdx
    │   │       ├── useInterval.mdx
    │   │       ├── useIntervalWhen.mdx
    │   │       ├── useIsClient.mdx
    │   │       ├── useIsFirstRender.mdx
    │   │       ├── useKeyPress.mdx
    │   │       ├── useList.mdx
    │   │       ├── useLocalStorage.mdx
    │   │       ├── useLockBodyScroll.mdx
    │   │       ├── useLogger.mdx
    │   │       ├── useLongPress.mdx
    │   │       ├── useMap.mdx
    │   │       ├── useMeasure.mdx
    │   │       ├── useMediaQuery.mdx
    │   │       ├── useMouse.mdx
    │   │       ├── useNetworkState.mdx
    │   │       ├── useObjectState.mdx
    │   │       ├── useOrientation.mdx
    │   │       ├── usePageLeave.mdx
    │   │       ├── usePreferredLanguage.mdx
    │   │       ├── usePrevious.mdx
    │   │       ├── useQueue.mdx
    │   │       ├── useRandomInterval.mdx
    │   │       ├── useRenderCount.mdx
    │   │       ├── useRenderInfo.mdx
    │   │       ├── useScript.mdx
    │   │       ├── useSessionStorage.mdx
    │   │       ├── useSet.mdx
    │   │       ├── useThrottle.mdx
    │   │       ├── useTimeout.mdx
    │   │       ├── useToggle.mdx
    │   │       ├── useVisibilityChange.mdx
    │   │       ├── useWindowScroll.mdx
    │   │       └── useWindowSize.mdx
    │   ├── env.d.ts
    │   ├── layouts/
    │   │   └── Layout.astro
    │   ├── pages/
    │   │   ├── 404.astro
    │   │   ├── [hook].astro
    │   │   └── index.astro
    │   ├── sections/
    │   │   ├── Footer.astro
    │   │   ├── HomeHero.astro
    │   │   ├── NavInternal.astro
    │   │   └── NavMain.astro
    │   └── styles/
    │       └── globals.css
    ├── tailwind.config.cjs
    ├── theme.json
    ├── tsconfig.json
    └── vercel.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# dotenv environment variables file
.env


# Mac files
.DS_Store



================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 ui.dev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
![useHooks](https://usehooks.com/meta/og.jpg)

# useHooks

A collection of modern, server-safe React hooks – from the [ui.dev](https://ui.dev) team.

Compatible with React v18.0.0+.

## Standard

### Install

`npm i @uidotdev/usehooks`

### Hooks

- [useBattery](https://usehooks.com/usebattery)
- [useClickAway](https://usehooks.com/useclickaway)
- [useCopyToClipboard](https://usehooks.com/usecopytoclipboard)
- [useCounter](https://usehooks.com/usecounter)
- [useDebounce](https://usehooks.com/usedebounce)
- [useDefault](https://usehooks.com/usedefault)
- [useDocumentTitle](https://usehooks.com/usedocumenttitle)
- [useFavicon](https://usehooks.com/usefavicon)
- [useGeolocation](https://usehooks.com/usegeolocation)
- [useHistoryState](https://usehooks.com/usehistorystate)
- [useHover](https://usehooks.com/usehover)
- [useIdle](https://usehooks.com/useidle)
- [useIntersectionObserver](https://usehooks.com/useintersectionobserver)
- [useIsClient](https://usehooks.com/useisclient)
- [useIsFirstRender](https://usehooks.com/useisfirstrender)
- [useList](https://usehooks.com/uselist)
- [useLocalStorage](https://usehooks.com/uselocalstorage)
- [useLockBodyScroll](https://usehooks.com/uselockbodyscroll)
- [useLongPress](https://usehooks.com/uselongpress)
- [useMap](https://usehooks.com/usemap)
- [useMeasure](https://usehooks.com/usemeasure)
- [useMediaQuery](https://usehooks.com/usemediaquery)
- [useMouse](https://usehooks.com/usemouse)
- [useNetworkState](https://usehooks.com/usenetworkstate)
- [useObjectState](https://usehooks.com/useobjectstate)
- [useOrientation](https://usehooks.com/useorientation)
- [usePreferredLanguage](https://usehooks.com/usepreferredlanguage)
- [usePrevious](https://usehooks.com/useprevious)
- [useQueue](https://usehooks.com/usequeue)
- [useRenderCount](https://usehooks.com/userendercount)
- [useRenderInfo](https://usehooks.com/userenderinfo)
- [useScript](https://usehooks.com/usescript)
- [useSessionStorage](https://usehooks.com/usesessionstorage)
- [useSet](https://usehooks.com/useset)
- [useThrottle](https://usehooks.com/usethrottle)
- [useToggle](https://usehooks.com/usetoggle)
- [useVisibilityChange](https://usehooks.com/usevisibilitychange)
- [useWindowScroll](https://usehooks.com/usewindowscroll)
- [useWindowSize](https://usehooks.com/usewindowsize)

## Experimental

### Install

`npm i @uidotdev/usehooks@experimental react@experimental react-dom@experimental`

### Hooks

- [useContinuousRetry](https://usehooks.com/usecontinuousretry)
- [useCountdown](https://usehooks.com/usecountdown)
- [useEventListener](https://usehooks.com/useeventlistener)
- [useFetch](https://usehooks.com/usefetch)
- [useInterval](https://usehooks.com/useinterval)
- [useIntervalWhen](https://usehooks.com/useintervalwhen)
- [useKeyPress](https://usehooks.com/usekeypress)
- [useLogger](https://usehooks.com/uselogger)
- [usePageLeave](https://usehooks.com/usepageleave)
- [useRandomInterval](https://usehooks.com/userandominterval)
- [useTimeout](https://usehooks.com/usetimeout)


================================================
FILE: index.d.ts
================================================
import * as React from "react";

export type BatteryManager = {
  supported: boolean;
  loading: boolean;
  level: number | null;
  charging: boolean | null;
  chargingTime: number | null;
  dischargingTime: number | null;
};

export type GeolocationState = {
  loading: boolean;
  accuracy: number | null;
  altitude: number | null;
  altitudeAccuracy: number | null;
  heading: number | null;
  latitude: number | null;
  longitude: number | null;
  speed: number | null;
  timestamp: number | null;
  error: GeolocationPositionError | null;
};

export type HistoryState<T> = {
  state: T;
  set: (newPresent: T) => void;
  undo: () => void;
  redo: () => void;
  clear: () => void;
  canUndo: boolean;
  canRedo: boolean;
};

export type LongPressOptions = {
  threshold?: number;
  onStart?: (e: Event) => void;
  onFinish?: (e: Event) => void;
  onCancel?: (e: Event) => void;
};

export type LongPressFns = {
  onMouseDown: (e: React.MouseEvent) => void;
  onMouseUp: (e: React.MouseEvent) => void;
  onMouseLeave: (e: React.MouseEvent) => void;
  onTouchStart: (e: React.TouchEvent) => void;
  onTouchEnd: (e: React.TouchEvent) => void;
};

export type MousePosition = {
  x: number;
  y: number;
  elementX: number;
  elementY: number;
  elementPositionX: number;
  elementPositionY: number;
};

export type NetworkState = {
  online: boolean;
  downlink: number | null;
  downlinkMax: number | null;
  effectiveType: string | null;
  rtt: number | null;
  saveData: boolean | null;
  type: string | null;
};

export type CustomList<T> = {
  set: (l: T[]) => void;
  push: (element: T) => void;
  removeAt: (index: number) => void;
  insertAt: (index: number, element: T) => void;
  updateAt: (index: number, element: T) => void;
  clear: () => void;
};

export type CustomQueue<T> = {
  add: (element: T) => void;
  remove: () => T | undefined;
  clear: () => void;
  first: T | undefined;
  last: T | undefined;
  size: number;
  queue: T[];
};

export type RenderInfo = {
  name: string;
  renders: number;
  sinceLastRender: number;
  timestamp: number;
};

export type SpeechOptions = {
  lang?: string;
  voice?: {
    lang?: string;
    name?: string;
  };
  rate?: number;
  pitch?: number;
  volume?: number;
};

export type SpeechState = {
  isPlaying: boolean;
  status: "init" | "play" | "pause" | "stop";
  lang: string;
  voiceInfo: {
    lang: string;
    name: string;
  };
  rate: number;
  pitch: number;
  volume: number;
};

declare module "@uidotdev/usehooks" {
  export function useBattery(): BatteryManager;

  export function useClickAway<T extends Element>(
    cb: (e: Event) => void
  ): React.MutableRefObject<T>;

  export function useCopyToClipboard(): [
    string | null,
    (value: string) => Promise<void>
  ];

  export function useCounter(
    startingValue?: number,
    options?: {
      min?: number;
      max?: number;
    }
  ): [
    number,
    {
      increment: () => void;
      decrement: () => void;
      set: (nextCount: number) => void;
      reset: () => void;
    }
  ];

  export function useDebounce<T>(value: T, delay: number): T;

  export function useDefault<T>(
    initialValue: T,
    defaultValue: T
  ): [T, React.Dispatch<React.SetStateAction<T>>];

  export function useDocumentTitle(title: string): void;

  export function useFavicon(url: string): void;

  export function useGeolocation(options?: PositionOptions): GeolocationState;

  export function useHistoryState<T>(initialPresent?: T): HistoryState<T>;

  export function useHover<T extends Element>(): [
    React.RefCallback<T>,
    boolean
  ];

  export function useIdle(ms?: number): boolean;

  export function useIntersectionObserver<T extends Element>(
    options?: IntersectionObserverInit
  ): [React.RefCallback<T>, IntersectionObserverEntry | null];

  export function useIsClient(): boolean;

  export function useIsFirstRender(): boolean;

  export function useList<T>(defaultList?: T[]): [T[], CustomList<T>];

  export function useLocalStorage<T>(
    key: string,
    initialValue?: T
  ): [T, React.Dispatch<React.SetStateAction<T>>];

  export function useLockBodyScroll(): void;

  export function useLongPress(
    callback: (e: Event) => void,
    options?: LongPressOptions
  ): LongPressFns;

  export function useMap<T>(initialState?: T): Map<T, any>;

  export function useMeasure<T extends Element>(): [
    React.RefCallback<T>,
    {
      width: number | null;
      height: number | null;
    }
  ];

  export function useMediaQuery(query: string): boolean;

  export function useMouse<T extends Element>(): [
    MousePosition,
    React.MutableRefObject<T>
  ];

  export function useNetworkState(): NetworkState;

  export function useObjectState<T>(initialValue: T): [T, (arg: T) => void];

  export function useOrientation(): {
    angle: number;
    type: string;
  };

  export function usePreferredLanguage(): string;

  export function usePrevious<T>(newValue: T): T;

  export function useQueue<T>(initialValue?: T[]): CustomQueue<T>;

  export function useRenderCount(): number;

  export function useRenderInfo(name?: string): RenderInfo | undefined;

  export function useScript(
    src: string,
    options?: {
      removeOnUnmount?: boolean;
    }
  ): "unknown" | "loading" | "ready" | "error";

  export function useSessionStorage<T>(
    key: string,
    initialValue: T
  ): [T, React.Dispatch<React.SetStateAction<T>>];

  export function useSet<T>(values?: T[]): Set<T>;

  export function useSpeech(text: string, options?: SpeechOptions): SpeechState;

  export function useThrottle<T>(value: T, delay: number): T;

  export function useToggle(
    initialValue?: boolean
  ): [boolean, (newValue?: boolean) => void];

  export function useVisibilityChange(): boolean;

  export function useWindowScroll(): [
    {
      x: number | null;
      y: number | null;
    },
    (args: unknown) => void
  ];

  export function useWindowSize(): {
    width: number | null;
    height: number | null;
  };
}


================================================
FILE: index.js
================================================
import * as React from "react";

function isShallowEqual(object1, object2) {
  const keys1 = Object.keys(object1);
  const keys2 = Object.keys(object2);

  if (keys1.length !== keys2.length) {
    return false;
  }

  for (let key of keys1) {
    if (object1[key] !== object2[key]) {
      return false;
    }
  }

  return true;
}

function isTouchEvent({ nativeEvent }) {
  return window.TouchEvent
    ? nativeEvent instanceof TouchEvent
    : "touches" in nativeEvent;
}

function isMouseEvent(event) {
  return event.nativeEvent instanceof MouseEvent;
}

function throttle(cb, ms) {
  let lastTime = 0;
  return () => {
    const now = Date.now();
    if (now - lastTime >= ms) {
      cb();
      lastTime = now;
    }
  };
}

function isPlainObject(value) {
  return Object.prototype.toString.call(value) === "[object Object]";
}

function dispatchStorageEvent(key, newValue) {
  window.dispatchEvent(new StorageEvent("storage", { key, newValue }));
}

export function useBattery() {
  const [state, setState] = React.useState({
    supported: true,
    loading: true,
    level: null,
    charging: null,
    chargingTime: null,
    dischargingTime: null,
  });

  React.useEffect(() => {
    if (!navigator.getBattery) {
      setState((s) => ({
        ...s,
        supported: false,
        loading: false,
      }));
      return;
    }

    let battery = null;

    const handleChange = () => {
      setState({
        supported: true,
        loading: false,
        level: battery.level,
        charging: battery.charging,
        chargingTime: battery.chargingTime,
        dischargingTime: battery.dischargingTime,
      });
    };

    navigator.getBattery().then((b) => {
      battery = b;
      handleChange();

      b.addEventListener("levelchange", handleChange);
      b.addEventListener("chargingchange", handleChange);
      b.addEventListener("chargingtimechange", handleChange);
      b.addEventListener("dischargingtimechange", handleChange);
    });

    return () => {
      if (battery) {
        battery.removeEventListener("levelchange", handleChange);
        battery.removeEventListener("chargingchange", handleChange);
        battery.removeEventListener("chargingtimechange", handleChange);
        battery.removeEventListener("dischargingtimechange", handleChange);
      }
    };
  }, []);

  return state;
}

export function useClickAway(cb) {
  const ref = React.useRef(null);
  const refCb = React.useRef(cb);

  React.useLayoutEffect(() => {
    refCb.current = cb;
  });

  React.useEffect(() => {
    const handler = (e) => {
      const element = ref.current;
      if (element && !element.contains(e.target)) {
        refCb.current(e);
      }
    };

    document.addEventListener("mousedown", handler);
    document.addEventListener("touchstart", handler);

    return () => {
      document.removeEventListener("mousedown", handler);
      document.removeEventListener("touchstart", handler);
    };
  }, []);

  return ref;
}

function oldSchoolCopy(text) {
  const tempTextArea = document.createElement("textarea");
  tempTextArea.value = text;
  document.body.appendChild(tempTextArea);
  tempTextArea.select();
  document.execCommand("copy");
  document.body.removeChild(tempTextArea);
}

export function useCopyToClipboard() {
  const [state, setState] = React.useState(null);

  const copyToClipboard = React.useCallback((value) => {
    const handleCopy = async () => {
      try {
        if (navigator?.clipboard?.writeText) {
          await navigator.clipboard.writeText(value);
          setState(value);
        } else {
          throw new Error("writeText not supported");
        }
      } catch (e) {
        oldSchoolCopy(value);
        setState(value);
      }
    };

    handleCopy();
  }, []);

  return [state, copyToClipboard];
}

export function useCounter(startingValue = 0, options = {}) {
  const { min, max } = options;

  if (typeof min === "number" && startingValue < min) {
    throw new Error(
      `Your starting value of ${startingValue} is less than your min of ${min}.`
    );
  }

  if (typeof max === "number" && startingValue > max) {
    throw new Error(
      `Your starting value of ${startingValue} is greater than your max of ${max}.`
    );
  }

  const [count, setCount] = React.useState(startingValue);

  const increment = React.useCallback(() => {
    setCount((c) => {
      const nextCount = c + 1;

      if (typeof max === "number" && nextCount > max) {
        return c;
      }

      return nextCount;
    });
  }, [max]);

  const decrement = React.useCallback(() => {
    setCount((c) => {
      const nextCount = c - 1;

      if (typeof min === "number" && nextCount < min) {
        return c;
      }

      return nextCount;
    });
  }, [min]);

  const set = React.useCallback(
    (nextCount) => {
      setCount((c) => {
        if (typeof max === "number" && nextCount > max) {
          return c;
        }

        if (typeof min === "number" && nextCount < min) {
          return c;
        }

        return nextCount;
      });
    },
    [max, min]
  );

  const reset = React.useCallback(() => {
    setCount(startingValue);
  }, [startingValue]);

  return [
    count,
    {
      increment,
      decrement,
      set,
      reset,
    },
  ];
}

export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = React.useState(value);

  React.useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

export function useDefault(initialValue, defaultValue) {
  const [state, setState] = React.useState(initialValue);

  if (typeof state === "undefined" || state === null) {
    return [defaultValue, setState];
  }

  return [state, setState];
}

export function useDocumentTitle(title) {
  React.useEffect(() => {
    document.title = title;
  }, [title]);
}

export function useFavicon(url) {
  React.useEffect(() => {
    let link = document.querySelector(`link[rel~="icon"]`);

    if (!link) {
      link = document.createElement("link");
      link.type = "image/x-icon";
      link.rel = "icon";
      link.href = url;
      document.head.appendChild(link);
    } else {
      link.href = url;
    }
  }, [url]);
}

export function useGeolocation(options = {}) {
  const [state, setState] = React.useState({
    loading: true,
    accuracy: null,
    altitude: null,
    altitudeAccuracy: null,
    heading: null,
    latitude: null,
    longitude: null,
    speed: null,
    timestamp: null,
    error: null,
  });

  const optionsRef = React.useRef(options);

  React.useEffect(() => {
    const onEvent = ({ coords, timestamp }) => {
      setState({
        loading: false,
        timestamp,
        latitude: coords.latitude,
        longitude: coords.longitude,
        altitude: coords.altitude,
        accuracy: coords.accuracy,
        altitudeAccuracy: coords.altitudeAccuracy,
        heading: coords.heading,
        speed: coords.speed,
      });
    };

    const onEventError = (error) => {
      setState((s) => ({
        ...s,
        loading: false,
        error,
      }));
    };

    navigator.geolocation.getCurrentPosition(
      onEvent,
      onEventError,
      optionsRef.current
    );

    const watchId = navigator.geolocation.watchPosition(
      onEvent,
      onEventError,
      optionsRef.current
    );

    return () => {
      navigator.geolocation.clearWatch(watchId);
    };
  }, []);

  return state;
}

const initialUseHistoryStateState = {
  past: [],
  present: null,
  future: [],
};

const useHistoryStateReducer = (state, action) => {
  const { past, present, future } = state;

  if (action.type === "UNDO") {
    return {
      past: past.slice(0, past.length - 1),
      present: past[past.length - 1],
      future: [present, ...future],
    };
  } else if (action.type === "REDO") {
    return {
      past: [...past, present],
      present: future[0],
      future: future.slice(1),
    };
  } else if (action.type === "SET") {
    const { newPresent } = action;

    if (action.newPresent === present) {
      return state;
    }

    return {
      past: [...past, present],
      present: newPresent,
      future: [],
    };
  } else if (action.type === "CLEAR") {
    return {
      ...initialUseHistoryStateState,
      present: action.initialPresent,
    };
  } else {
    throw new Error("Unsupported action type");
  }
};

export function useHistoryState(initialPresent = {}) {
  const initialPresentRef = React.useRef(initialPresent);

  const [state, dispatch] = React.useReducer(useHistoryStateReducer, {
    ...initialUseHistoryStateState,
    present: initialPresentRef.current,
  });

  const canUndo = state.past.length !== 0;
  const canRedo = state.future.length !== 0;

  const undo = React.useCallback(() => {
    if (canUndo) {
      dispatch({ type: "UNDO" });
    }
  }, [canUndo]);

  const redo = React.useCallback(() => {
    if (canRedo) {
      dispatch({ type: "REDO" });
    }
  }, [canRedo]);

  const set = React.useCallback(
    (newPresent) => dispatch({ type: "SET", newPresent }),
    []
  );

  const clear = React.useCallback(
    () =>
      dispatch({ type: "CLEAR", initialPresent: initialPresentRef.current }),
    []
  );

  return { state: state.present, set, undo, redo, clear, canUndo, canRedo };
}

export function useHover() {
  const [hovering, setHovering] = React.useState(false);
  const previousNode = React.useRef(null);

  const handleMouseEnter = React.useCallback(() => {
    setHovering(true);
  }, []);

  const handleMouseLeave = React.useCallback(() => {
    setHovering(false);
  }, []);

  const customRef = React.useCallback(
    (node) => {
      if (previousNode.current?.nodeType === Node.ELEMENT_NODE) {
        previousNode.current.removeEventListener(
          "mouseenter",
          handleMouseEnter
        );
        previousNode.current.removeEventListener(
          "mouseleave",
          handleMouseLeave
        );
      }

      if (node?.nodeType === Node.ELEMENT_NODE) {
        node.addEventListener("mouseenter", handleMouseEnter);
        node.addEventListener("mouseleave", handleMouseLeave);
      }

      previousNode.current = node;
    },
    [handleMouseEnter, handleMouseLeave]
  );

  return [customRef, hovering];
}

export function useIdle(ms = 1000 * 60) {
  const [idle, setIdle] = React.useState(false);

  React.useEffect(() => {
    let timeoutId;

    const handleTimeout = () => {
      setIdle(true);
    };

    const handleEvent = throttle((e) => {
      setIdle(false);

      window.clearTimeout(timeoutId);
      timeoutId = window.setTimeout(handleTimeout, ms);
    }, 500);

    const handleVisibilityChange = () => {
      if (!document.hidden) {
        handleEvent();
      }
    };

    timeoutId = window.setTimeout(handleTimeout, ms);

    window.addEventListener("mousemove", handleEvent);
    window.addEventListener("mousedown", handleEvent);
    window.addEventListener("resize", handleEvent);
    window.addEventListener("keydown", handleEvent);
    window.addEventListener("touchstart", handleEvent);
    window.addEventListener("wheel", handleEvent);
    document.addEventListener("visibilitychange", handleVisibilityChange);

    return () => {
      window.removeEventListener("mousemove", handleEvent);
      window.removeEventListener("mousedown", handleEvent);
      window.removeEventListener("resize", handleEvent);
      window.removeEventListener("keydown", handleEvent);
      window.removeEventListener("touchstart", handleEvent);
      window.removeEventListener("wheel", handleEvent);
      document.removeEventListener("visibilitychange", handleVisibilityChange);
      window.clearTimeout(timeoutId);
    };
  }, [ms]);

  return idle;
}

export function useIntersectionObserver(options = {}) {
  const { threshold = 1, root = null, rootMargin = "0px" } = options;
  const [entry, setEntry] = React.useState(null);

  const previousObserver = React.useRef(null);

  const customRef = React.useCallback(
    (node) => {
      if (previousObserver.current) {
        previousObserver.current.disconnect();
        previousObserver.current = null;
      }

      if (node?.nodeType === Node.ELEMENT_NODE) {
        const observer = new IntersectionObserver(
          ([entry]) => {
            setEntry(entry);
          },
          { threshold, root, rootMargin }
        );

        observer.observe(node);
        previousObserver.current = observer;
      }
    },
    [threshold, root, rootMargin]
  );

  return [customRef, entry];
}

export function useIsClient() {
  const [isClient, setIsClient] = React.useState(false);

  React.useEffect(() => {
    setIsClient(true);
  }, []);

  return isClient;
}

export function useIsFirstRender() {
  const renderRef = React.useRef(true);

  if (renderRef.current === true) {
    renderRef.current = false;
    return true;
  }

  return renderRef.current;
}

export function useList(defaultList = []) {
  const [list, setList] = React.useState(defaultList);

  const set = React.useCallback((l) => {
    setList(l);
  }, []);

  const push = React.useCallback((element) => {
    setList((l) => [...l, element]);
  }, []);

  const removeAt = React.useCallback((index) => {
    setList((l) => [...l.slice(0, index), ...l.slice(index + 1)]);
  }, []);

  const insertAt = React.useCallback((index, element) => {
    setList((l) => [...l.slice(0, index), element, ...l.slice(index)]);
  }, []);

  const updateAt = React.useCallback((index, element) => {
    setList((l) => l.map((e, i) => (i === index ? element : e)));
  }, []);

  const clear = React.useCallback(() => setList([]), []);

  return [list, { set, push, removeAt, insertAt, updateAt, clear }];
}

const setLocalStorageItem = (key, value) => {
  const stringifiedValue = JSON.stringify(value);
  window.localStorage.setItem(key, stringifiedValue);
  dispatchStorageEvent(key, stringifiedValue);
};

const removeLocalStorageItem = (key) => {
  window.localStorage.removeItem(key);
  dispatchStorageEvent(key, null);
};

const getLocalStorageItem = (key) => {
  return window.localStorage.getItem(key);
};

const useLocalStorageSubscribe = (callback) => {
  window.addEventListener("storage", callback);
  return () => window.removeEventListener("storage", callback);
};

const getLocalStorageServerSnapshot = () => {
  throw Error("useLocalStorage is a client-only hook");
};

export function useLocalStorage(key, initialValue) {
  const getSnapshot = () => getLocalStorageItem(key);

  const store = React.useSyncExternalStore(
    useLocalStorageSubscribe,
    getSnapshot,
    getLocalStorageServerSnapshot
  );

  const setState = React.useCallback(
    (v) => {
      try {
        const nextState = typeof v === "function" ? v(JSON.parse(store)) : v;

        if (nextState === undefined || nextState === null) {
          removeLocalStorageItem(key);
        } else {
          setLocalStorageItem(key, nextState);
        }
      } catch (e) {
        console.warn(e);
      }
    },
    [key, store]
  );

  React.useEffect(() => {
    if (
      getLocalStorageItem(key) === null &&
      typeof initialValue !== "undefined"
    ) {
      setLocalStorageItem(key, initialValue);
    }
  }, [key, initialValue]);

  return [store ? JSON.parse(store) : initialValue, setState];
}

export function useLockBodyScroll() {
  React.useLayoutEffect(() => {
    const originalStyle = window.getComputedStyle(document.body).overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = originalStyle;
    };
  }, []);
}

export function useLongPress(callback, options = {}) {
  const { threshold = 400, onStart, onFinish, onCancel } = options;
  const isLongPressActive = React.useRef(false);
  const isPressed = React.useRef(false);
  const timerId = React.useRef();

  return React.useMemo(() => {
    if (typeof callback !== "function") {
      return {};
    }

    const start = (event) => {
      if (!isMouseEvent(event) && !isTouchEvent(event)) return;

      if (onStart) {
        onStart(event);
      }

      isPressed.current = true;
      timerId.current = setTimeout(() => {
        callback(event);
        isLongPressActive.current = true;
      }, threshold);
    };

    const cancel = (event) => {
      if (!isMouseEvent(event) && !isTouchEvent(event)) return;

      if (isLongPressActive.current) {
        if (onFinish) {
          onFinish(event);
        }
      } else if (isPressed.current) {
        if (onCancel) {
          onCancel(event);
        }
      }

      isLongPressActive.current = false;
      isPressed.current = false;

      if (timerId.current) {
        window.clearTimeout(timerId.current);
      }
    };

    const mouseHandlers = {
      onMouseDown: start,
      onMouseUp: cancel,
      onMouseLeave: cancel,
    };

    const touchHandlers = {
      onTouchStart: start,
      onTouchEnd: cancel,
    };

    return {
      ...mouseHandlers,
      ...touchHandlers,
    };
  }, [callback, threshold, onCancel, onFinish, onStart]);
}

export function useMap(initialState) {
  const mapRef = React.useRef(new Map(initialState));
  const [, reRender] = React.useReducer((x) => x + 1, 0);

  mapRef.current.set = (...args) => {
    Map.prototype.set.apply(mapRef.current, args);
    reRender();
    return mapRef.current;
  };

  mapRef.current.clear = (...args) => {
    Map.prototype.clear.apply(mapRef.current, args);
    reRender();
  };

  mapRef.current.delete = (...args) => {
    const res = Map.prototype.delete.apply(mapRef.current, args);
    reRender();

    return res;
  };

  return mapRef.current;
}

export function useMeasure() {
  const [dimensions, setDimensions] = React.useState({
    width: null,
    height: null,
  });

  const previousObserver = React.useRef(null);

  const customRef = React.useCallback((node) => {
    if (previousObserver.current) {
      previousObserver.current.disconnect();
      previousObserver.current = null;
    }

    if (node?.nodeType === Node.ELEMENT_NODE) {
      const observer = new ResizeObserver(([entry]) => {
        if (entry && entry.borderBoxSize) {
          const { inlineSize: width, blockSize: height } =
            entry.borderBoxSize[0];

          setDimensions({ width, height });
        }
      });

      observer.observe(node);
      previousObserver.current = observer;
    }
  }, []);

  return [customRef, dimensions];
}

export function useMediaQuery(query) {
  const subscribe = React.useCallback(
    (callback) => {
      const matchMedia = window.matchMedia(query);

      matchMedia.addEventListener("change", callback);
      return () => {
        matchMedia.removeEventListener("change", callback);
      };
    },
    [query]
  );

  const getSnapshot = () => {
    return window.matchMedia(query).matches;
  };

  const getServerSnapshot = () => {
    throw Error("useMediaQuery is a client-only hook");
  };

  return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}

export function useMouse() {
  const [state, setState] = React.useState({
    x: 0,
    y: 0,
    elementX: 0,
    elementY: 0,
    elementPositionX: 0,
    elementPositionY: 0,
  });

  const ref = React.useRef(null);

  React.useLayoutEffect(() => {
    const handleMouseMove = (event) => {
      let newState = {
        x: event.pageX,
        y: event.pageY,
      };

      if (ref.current?.nodeType === Node.ELEMENT_NODE) {
        const { left, top } = ref.current.getBoundingClientRect();
        const elementPositionX = left + window.scrollX;
        const elementPositionY = top + window.scrollY;
        const elementX = event.pageX - elementPositionX;
        const elementY = event.pageY - elementPositionY;

        newState.elementX = elementX;
        newState.elementY = elementY;
        newState.elementPositionX = elementPositionX;
        newState.elementPositionY = elementPositionY;
      }

      setState((s) => {
        return {
          ...s,
          ...newState,
        };
      });
    };

    document.addEventListener("mousemove", handleMouseMove);

    return () => {
      document.removeEventListener("mousemove", handleMouseMove);
    };
  }, []);

  return [state, ref];
}

const getConnection = () => {
  return (
    navigator?.connection ||
    navigator?.mozConnection ||
    navigator?.webkitConnection
  );
};

const useNetworkStateSubscribe = (callback) => {
  window.addEventListener("online", callback, { passive: true });
  window.addEventListener("offline", callback, { passive: true });

  const connection = getConnection();

  if (connection) {
    connection.addEventListener("change", callback, { passive: true });
  }

  return () => {
    window.removeEventListener("online", callback);
    window.removeEventListener("offline", callback);

    if (connection) {
      connection.removeEventListener("change", callback);
    }
  };
};

const getNetworkStateServerSnapshot = () => {
  throw Error("useNetworkState is a client-only hook");
};

export function useNetworkState() {
  const cache = React.useRef({});

  const getSnapshot = () => {
    const online = navigator.onLine;
    const connection = getConnection();

    const nextState = {
      online,
      downlink: connection?.downlink,
      downlinkMax: connection?.downlinkMax,
      effectiveType: connection?.effectiveType,
      rtt: connection?.rtt,
      saveData: connection?.saveData,
      type: connection?.type,
    };

    if (isShallowEqual(cache.current, nextState)) {
      return cache.current;
    } else {
      cache.current = nextState;
      return nextState;
    }
  };

  return React.useSyncExternalStore(
    useNetworkStateSubscribe,
    getSnapshot,
    getNetworkStateServerSnapshot
  );
}

export function useObjectState(initialValue) {
  const [state, setState] = React.useState(initialValue);

  const handleUpdate = React.useCallback((arg) => {
    if (typeof arg === "function") {
      setState((s) => {
        const newState = arg(s);

        if (isPlainObject(newState)) {
          return {
            ...s,
            ...newState,
          };
        }
      });
    }

    if (isPlainObject(arg)) {
      setState((s) => ({
        ...s,
        ...arg,
      }));
    }
  }, []);

  return [state, handleUpdate];
}

export function useOrientation() {
  const [orientation, setOrientation] = React.useState({
    angle: 0,
    type: "landscape-primary",
  });

  React.useLayoutEffect(() => {
    const handleChange = () => {
      const { angle, type } = window.screen.orientation;
      setOrientation({
        angle,
        type,
      });
    };

    const handle_orientationchange = () => {
      setOrientation({
        type: "UNKNOWN",
        angle: window.orientation,
      });
    };

    if (window.screen?.orientation) {
      handleChange();
      window.screen.orientation.addEventListener("change", handleChange);
    } else {
      handle_orientationchange();
      window.addEventListener("orientationchange", handle_orientationchange);
    }

    return () => {
      if (window.screen?.orientation) {
        window.screen.orientation.removeEventListener("change", handleChange);
      } else {
        window.removeEventListener(
          "orientationchange",
          handle_orientationchange
        );
      }
    };
  }, []);

  return orientation;
}

const usePreferredLanguageSubscribe = (cb) => {
  window.addEventListener("languagechange", cb);
  return () => window.removeEventListener("languagechange", cb);
};

const getPreferredLanguageSnapshot = () => {
  return navigator.language;
};

const getPreferredLanguageServerSnapshot = () => {
  throw Error("usePreferredLanguage is a client-only hook");
};

export function usePreferredLanguage() {
  return React.useSyncExternalStore(
    usePreferredLanguageSubscribe,
    getPreferredLanguageSnapshot,
    getPreferredLanguageServerSnapshot
  );
}

export function usePrevious(value) {
  const [current, setCurrent] = React.useState(value);
  const [previous, setPrevious] = React.useState(null);

  if (value !== current) {
    setPrevious(current);
    setCurrent(value);
  }

  return previous;
}

export function useQueue(initialValue = []) {
  const [queue, setQueue] = React.useState(initialValue);

  const add = React.useCallback((element) => {
    setQueue((q) => [...q, element]);
  }, []);

  const remove = React.useCallback(() => {
    let removedElement;

    setQueue(([first, ...q]) => {
      removedElement = first;
      return q;
    });

    return removedElement;
  }, []);

  const clear = React.useCallback(() => {
    setQueue([]);
  }, []);

  return {
    add,
    remove,
    clear,
    first: queue[0],
    last: queue[queue.length - 1],
    size: queue.length,
    queue,
  };
}

export function useRenderCount() {
  const count = React.useRef(0);

  count.current++;

  return count.current;
}

export function useRenderInfo(name = "Unknown") {
  const count = React.useRef(0);
  const lastRender = React.useRef();
  const now = Date.now();

  count.current++;

  React.useEffect(() => {
    lastRender.current = Date.now();
  });

  const sinceLastRender = lastRender.current ? now - lastRender.current : 0;

  if (process.env.NODE_ENV !== "production") {
    const info = {
      name,
      renders: count.current,
      sinceLastRender,
      timestamp: now,
    };

    console.log(info);

    return info;
  }
}

export function useScript(src, options = {}) {
  const [status, setStatus] = React.useState("loading");
  const optionsRef = React.useRef(options);

  React.useEffect(() => {
    let script = document.querySelector(`script[src="${src}"]`);

    const domStatus = script?.getAttribute("data-status");
    if (domStatus) {
      setStatus(domStatus);
      return;
    }

    if (script === null) {
      script = document.createElement("script");
      script.src = src;
      script.async = true;
      script.setAttribute("data-status", "loading");
      document.body.appendChild(script);

      const handleScriptLoad = () => {
        script.setAttribute("data-status", "ready");
        setStatus("ready");
        removeEventListeners();
      };

      const handleScriptError = () => {
        script.setAttribute("data-status", "error");
        setStatus("error");
        removeEventListeners();
      };

      const removeEventListeners = () => {
        script.removeEventListener("load", handleScriptLoad);
        script.removeEventListener("error", handleScriptError);
      };

      script.addEventListener("load", handleScriptLoad);
      script.addEventListener("error", handleScriptError);

      const removeOnUnmount = optionsRef.current.removeOnUnmount;

      return () => {
        if (removeOnUnmount === true) {
          script.remove();
          removeEventListeners();
        }
      };
    } else {
      setStatus("unknown");
    }
  }, [src]);

  return status;
}

const setSessionStorageItem = (key, value) => {
  const stringifiedValue = JSON.stringify(value);
  window.sessionStorage.setItem(key, stringifiedValue);
  dispatchStorageEvent(key, stringifiedValue);
};

const removeSessionStorageItem = (key) => {
  window.sessionStorage.removeItem(key);
  dispatchStorageEvent(key, null);
};

const getSessionStorageItem = (key) => {
  return window.sessionStorage.getItem(key);
};

const useSessionStorageSubscribe = (callback) => {
  window.addEventListener("storage", callback);
  return () => window.removeEventListener("storage", callback);
};

const getSessionStorageServerSnapshot = () => {
  throw Error("useSessionStorage is a client-only hook");
};

export function useSessionStorage(key, initialValue) {
  const getSnapshot = () => getSessionStorageItem(key);

  const store = React.useSyncExternalStore(
    useSessionStorageSubscribe,
    getSnapshot,
    getSessionStorageServerSnapshot
  );

  const setState = React.useCallback(
    (v) => {
      try {
        const nextState = typeof v === "function" ? v(JSON.parse(store)) : v;

        if (nextState === undefined || nextState === null) {
          removeSessionStorageItem(key);
        } else {
          setSessionStorageItem(key, nextState);
        }
      } catch (e) {
        console.warn(e);
      }
    },
    [key, store]
  );

  React.useEffect(() => {
    if (
      getSessionStorageItem(key) === null &&
      typeof initialValue !== "undefined"
    ) {
      setSessionStorageItem(key, initialValue);
    }
  }, [key, initialValue]);

  return [store ? JSON.parse(store) : initialValue, setState];
}

export function useSet(values) {
  const setRef = React.useRef(new Set(values));
  const [, reRender] = React.useReducer((x) => x + 1, 0);

  setRef.current.add = (...args) => {
    const res = Set.prototype.add.apply(setRef.current, args);
    reRender();

    return res;
  };

  setRef.current.clear = (...args) => {
    Set.prototype.clear.apply(setRef.current, args);
    reRender();
  };

  setRef.current.delete = (...args) => {
    const res = Set.prototype.delete.apply(setRef.current, args);
    reRender();

    return res;
  };

  return setRef.current;
}

export function useThrottle(value, interval = 500) {
  const [throttledValue, setThrottledValue] = React.useState(value);
  const lastUpdated = React.useRef(null);

  React.useEffect(() => {
    const now = Date.now();

    if (lastUpdated.current && now >= lastUpdated.current + interval) {
      lastUpdated.current = now;
      setThrottledValue(value);
    } else {
      const id = window.setTimeout(() => {
        lastUpdated.current = now;
        setThrottledValue(value);
      }, interval);

      return () => window.clearTimeout(id);
    }
  }, [value, interval]);

  return throttledValue;
}

export function useToggle(initialValue) {
  const [on, setOn] = React.useState(() => {
    if (typeof initialValue === "boolean") {
      return initialValue;
    }

    return Boolean(initialValue);
  });

  const handleToggle = React.useCallback((value) => {
    if (typeof value === "boolean") {
      return setOn(value);
    }

    return setOn((v) => !v);
  }, []);

  return [on, handleToggle];
}

const useVisibilityChangeSubscribe = (callback) => {
  document.addEventListener("visibilitychange", callback);

  return () => {
    document.removeEventListener("visibilitychange", callback);
  };
};

const getVisibilityChangeSnapshot = () => {
  return document.visibilityState;
};

const getVisibilityChangeServerSnapshot = () => {
  throw Error("useVisibilityChange is a client-only hook");
};

export function useVisibilityChange() {
  const visibilityState = React.useSyncExternalStore(
    useVisibilityChangeSubscribe,
    getVisibilityChangeSnapshot,
    getVisibilityChangeServerSnapshot
  );

  return visibilityState === "visible";
}

export function useWindowScroll() {
  const [state, setState] = React.useState({
    x: null,
    y: null,
  });

  const scrollTo = React.useCallback((...args) => {
    if (typeof args[0] === "object") {
      window.scrollTo(args[0]);
    } else if (typeof args[0] === "number" && typeof args[1] === "number") {
      window.scrollTo(args[0], args[1]);
    } else {
      throw new Error(
        `Invalid arguments passed to scrollTo. See here for more info. https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo`
      );
    }
  }, []);

  React.useLayoutEffect(() => {
    const handleScroll = () => {
      setState({ x: window.scrollX, y: window.scrollY });
    };

    handleScroll();
    window.addEventListener("scroll", handleScroll);

    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return [state, scrollTo];
}

export function useWindowSize() {
  const [size, setSize] = React.useState({
    width: null,
    height: null,
  });

  React.useLayoutEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    handleResize();
    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return size;
}


================================================
FILE: package.json
================================================
{
  "name": "@uidotdev/usehooks",
  "version": "2.4.1",
  "description": "A collection of modern, server-safe React hooks – from the ui.dev team",
  "type": "module",
  "repository": "uidotdev/usehooks",
  "devDependencies": {
    "@types/react": "^18.2.20",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "typescript": "^5.1.6"
  },
  "exports": {
    "default": "./index.js"
  },
  "engines": {
    "node": ">=16"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "types": "index.d.ts",
  "peerDependencies": {
    "react": ">=18.0.0",
    "react-dom": ">=18.0.0"
  },
  "author": "Tyler McGinnis, Ben Adam",
  "license": "MIT"
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "noImplicitAny": true,
    "allowJs": true,
    "outDir": "./irrelevant/unused"
  }
}


================================================
FILE: usehooks.com/.astro/types.d.ts
================================================
declare module 'astro:content' {
	interface Render {
		'.mdx': Promise<{
			Content: import('astro').MarkdownInstance<{}>['Content'];
			headings: import('astro').MarkdownHeading[];
			remarkPluginFrontmatter: Record<string, any>;
		}>;
	}
}

declare module 'astro:content' {
	interface Render {
		'.md': Promise<{
			Content: import('astro').MarkdownInstance<{}>['Content'];
			headings: import('astro').MarkdownHeading[];
			remarkPluginFrontmatter: Record<string, any>;
		}>;
	}
}

declare module 'astro:content' {
	export { z } from 'astro/zod';
	export type CollectionEntry<C extends keyof AnyEntryMap> = AnyEntryMap[C][keyof AnyEntryMap[C]];

	// TODO: Remove this when having this fallback is no longer relevant. 2.3? 3.0? - erika, 2023-04-04
	/**
	 * @deprecated
	 * `astro:content` no longer provide `image()`.
	 *
	 * Please use it through `schema`, like such:
	 * ```ts
	 * import { defineCollection, z } from "astro:content";
	 *
	 * defineCollection({
	 *   schema: ({ image }) =>
	 *     z.object({
	 *       image: image(),
	 *     }),
	 * });
	 * ```
	 */
	export const image: never;

	// This needs to be in sync with ImageMetadata
	export type ImageFunction = () => import('astro/zod').ZodObject<{
		src: import('astro/zod').ZodString;
		width: import('astro/zod').ZodNumber;
		height: import('astro/zod').ZodNumber;
		format: import('astro/zod').ZodUnion<
			[
				import('astro/zod').ZodLiteral<'png'>,
				import('astro/zod').ZodLiteral<'jpg'>,
				import('astro/zod').ZodLiteral<'jpeg'>,
				import('astro/zod').ZodLiteral<'tiff'>,
				import('astro/zod').ZodLiteral<'webp'>,
				import('astro/zod').ZodLiteral<'gif'>,
				import('astro/zod').ZodLiteral<'svg'>
			]
		>;
	}>;

	type BaseSchemaWithoutEffects =
		| import('astro/zod').AnyZodObject
		| import('astro/zod').ZodUnion<import('astro/zod').AnyZodObject[]>
		| import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
		| import('astro/zod').ZodIntersection<
				import('astro/zod').AnyZodObject,
				import('astro/zod').AnyZodObject
		  >;

	type BaseSchema =
		| BaseSchemaWithoutEffects
		| import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;

	export type SchemaContext = { image: ImageFunction };

	type DataCollectionConfig<S extends BaseSchema> = {
		type: 'data';
		schema?: S | ((context: SchemaContext) => S);
	};

	type ContentCollectionConfig<S extends BaseSchema> = {
		type?: 'content';
		schema?: S | ((context: SchemaContext) => S);
	};

	type CollectionConfig<S> = ContentCollectionConfig<S> | DataCollectionConfig<S>;

	export function defineCollection<S extends BaseSchema>(
		input: CollectionConfig<S>
	): CollectionConfig<S>;

	type AllValuesOf<T> = T extends any ? T[keyof T] : never;
	type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
		ContentEntryMap[C]
	>['slug'];

	export function getEntryBySlug<
		C extends keyof ContentEntryMap,
		E extends ValidContentEntrySlug<C> | (string & {})
	>(
		collection: C,
		// Note that this has to accept a regular string too, for SSR
		entrySlug: E
	): E extends ValidContentEntrySlug<C>
		? Promise<CollectionEntry<C>>
		: Promise<CollectionEntry<C> | undefined>;

	export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
		collection: C,
		entryId: E
	): Promise<CollectionEntry<C>>;

	export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
		collection: C,
		filter?: (entry: CollectionEntry<C>) => entry is E
	): Promise<E[]>;
	export function getCollection<C extends keyof AnyEntryMap>(
		collection: C,
		filter?: (entry: CollectionEntry<C>) => unknown
	): Promise<CollectionEntry<C>[]>;

	export function getEntry<
		C extends keyof ContentEntryMap,
		E extends ValidContentEntrySlug<C> | (string & {})
	>(entry: {
		collection: C;
		slug: E;
	}): E extends ValidContentEntrySlug<C>
		? Promise<CollectionEntry<C>>
		: Promise<CollectionEntry<C> | undefined>;
	export function getEntry<
		C extends keyof DataEntryMap,
		E extends keyof DataEntryMap[C] | (string & {})
	>(entry: {
		collection: C;
		id: E;
	}): E extends keyof DataEntryMap[C]
		? Promise<DataEntryMap[C][E]>
		: Promise<CollectionEntry<C> | undefined>;
	export function getEntry<
		C extends keyof ContentEntryMap,
		E extends ValidContentEntrySlug<C> | (string & {})
	>(
		collection: C,
		slug: E
	): E extends ValidContentEntrySlug<C>
		? Promise<CollectionEntry<C>>
		: Promise<CollectionEntry<C> | undefined>;
	export function getEntry<
		C extends keyof DataEntryMap,
		E extends keyof DataEntryMap[C] | (string & {})
	>(
		collection: C,
		id: E
	): E extends keyof DataEntryMap[C]
		? Promise<DataEntryMap[C][E]>
		: Promise<CollectionEntry<C> | undefined>;

	/** Resolve an array of entry references from the same collection */
	export function getEntries<C extends keyof ContentEntryMap>(
		entries: {
			collection: C;
			slug: ValidContentEntrySlug<C>;
		}[]
	): Promise<CollectionEntry<C>[]>;
	export function getEntries<C extends keyof DataEntryMap>(
		entries: {
			collection: C;
			id: keyof DataEntryMap[C];
		}[]
	): Promise<CollectionEntry<C>[]>;

	export function reference<C extends keyof AnyEntryMap>(
		collection: C
	): import('astro/zod').ZodEffects<
		import('astro/zod').ZodString,
		C extends keyof ContentEntryMap
			? {
					collection: C;
					slug: ValidContentEntrySlug<C>;
			  }
			: {
					collection: C;
					id: keyof DataEntryMap[C];
			  }
	>;
	// Allow generic `string` to avoid excessive type errors in the config
	// if `dev` is not running to update as you edit.
	// Invalid collection names will be caught at build time.
	export function reference<C extends string>(
		collection: C
	): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;

	type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
	type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
		ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
	>;

	type ContentEntryMap = {
		"hooks": {
"useBattery.mdx": {
	id: "useBattery.mdx";
  slug: "usebattery";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useClickAway.mdx": {
	id: "useClickAway.mdx";
  slug: "useclickaway";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useContinuousRetry.mdx": {
	id: "useContinuousRetry.mdx";
  slug: "usecontinuousretry";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useCopyToClipboard.mdx": {
	id: "useCopyToClipboard.mdx";
  slug: "usecopytoclipboard";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useCountdown.mdx": {
	id: "useCountdown.mdx";
  slug: "usecountdown";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useCounter.mdx": {
	id: "useCounter.mdx";
  slug: "usecounter";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useDebounce.mdx": {
	id: "useDebounce.mdx";
  slug: "usedebounce";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useDefault.mdx": {
	id: "useDefault.mdx";
  slug: "usedefault";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useDocumentTitle.mdx": {
	id: "useDocumentTitle.mdx";
  slug: "usedocumenttitle";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useEventListener.mdx": {
	id: "useEventListener.mdx";
  slug: "useeventlistener";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useFavicon.mdx": {
	id: "useFavicon.mdx";
  slug: "usefavicon";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useFetch.mdx": {
	id: "useFetch.mdx";
  slug: "usefetch";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useGeolocation.mdx": {
	id: "useGeolocation.mdx";
  slug: "usegeolocation";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useHistoryState.mdx": {
	id: "useHistoryState.mdx";
  slug: "usehistorystate";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useHover.mdx": {
	id: "useHover.mdx";
  slug: "usehover";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useIdle.mdx": {
	id: "useIdle.mdx";
  slug: "useidle";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useIntersectionObserver.mdx": {
	id: "useIntersectionObserver.mdx";
  slug: "useintersectionobserver";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useInterval.mdx": {
	id: "useInterval.mdx";
  slug: "useinterval";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useIntervalWhen.mdx": {
	id: "useIntervalWhen.mdx";
  slug: "useintervalwhen";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useIsClient.mdx": {
	id: "useIsClient.mdx";
  slug: "useisclient";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useIsFirstRender.mdx": {
	id: "useIsFirstRender.mdx";
  slug: "useisfirstrender";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useKeyPress.mdx": {
	id: "useKeyPress.mdx";
  slug: "usekeypress";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useList.mdx": {
	id: "useList.mdx";
  slug: "uselist";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useLocalStorage.mdx": {
	id: "useLocalStorage.mdx";
  slug: "uselocalstorage";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useLockBodyScroll.mdx": {
	id: "useLockBodyScroll.mdx";
  slug: "uselockbodyscroll";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useLogger.mdx": {
	id: "useLogger.mdx";
  slug: "uselogger";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useLongPress.mdx": {
	id: "useLongPress.mdx";
  slug: "uselongpress";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useMap.mdx": {
	id: "useMap.mdx";
  slug: "usemap";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useMeasure.mdx": {
	id: "useMeasure.mdx";
  slug: "usemeasure";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useMediaQuery.mdx": {
	id: "useMediaQuery.mdx";
  slug: "usemediaquery";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useMouse.mdx": {
	id: "useMouse.mdx";
  slug: "usemouse";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useNetworkState.mdx": {
	id: "useNetworkState.mdx";
  slug: "usenetworkstate";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useObjectState.mdx": {
	id: "useObjectState.mdx";
  slug: "useobjectstate";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useOrientation.mdx": {
	id: "useOrientation.mdx";
  slug: "useorientation";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"usePageLeave.mdx": {
	id: "usePageLeave.mdx";
  slug: "usepageleave";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"usePreferredLanguage.mdx": {
	id: "usePreferredLanguage.mdx";
  slug: "usepreferredlanguage";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"usePrevious.mdx": {
	id: "usePrevious.mdx";
  slug: "useprevious";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useQueue.mdx": {
	id: "useQueue.mdx";
  slug: "usequeue";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useRandomInterval.mdx": {
	id: "useRandomInterval.mdx";
  slug: "userandominterval";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useRenderCount.mdx": {
	id: "useRenderCount.mdx";
  slug: "userendercount";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useRenderInfo.mdx": {
	id: "useRenderInfo.mdx";
  slug: "userenderinfo";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useScript.mdx": {
	id: "useScript.mdx";
  slug: "usescript";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useSessionStorage.mdx": {
	id: "useSessionStorage.mdx";
  slug: "usesessionstorage";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useSet.mdx": {
	id: "useSet.mdx";
  slug: "useset";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useThrottle.mdx": {
	id: "useThrottle.mdx";
  slug: "usethrottle";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useTimeout.mdx": {
	id: "useTimeout.mdx";
  slug: "usetimeout";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useToggle.mdx": {
	id: "useToggle.mdx";
  slug: "usetoggle";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useVisibilityChange.mdx": {
	id: "useVisibilityChange.mdx";
  slug: "usevisibilitychange";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useWindowScroll.mdx": {
	id: "useWindowScroll.mdx";
  slug: "usewindowscroll";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
"useWindowSize.mdx": {
	id: "useWindowSize.mdx";
  slug: "usewindowsize";
  body: string;
  collection: "hooks";
  data: InferEntrySchema<"hooks">
} & { render(): Render[".mdx"] };
};

	};

	type DataEntryMap = {
		
	};

	type AnyEntryMap = ContentEntryMap & DataEntryMap;

	type ContentConfig = typeof import("../src/content/config");
}


================================================
FILE: usehooks.com/.eslintrc.json
================================================
{
  "extends": "next/core-web-vitals"
}


================================================
FILE: usehooks.com/.gitignore
================================================
node_modules
/node_modules
.DS_Store
.env
.env.*
.next
dist/*
/dist
public/meta/*
!public/meta/og.jpg

================================================
FILE: usehooks.com/.prettierrc
================================================
{}

================================================
FILE: usehooks.com/README.md
================================================
# usehooks.com

## 🚀 Project Structure

Inside this project, you'll see the following folders and files:

```
/
├── public/
│   └── favicon.png
│   └── img
├── src/
│   ├── components/
│   │   └── Button.astro
│   ├── layouts/
│   │   └── Layout.astro
│   ├── pages/
│   │   └── index.astro
│   ├── sections/
│   │   └── HomeHero.astro
└── package.json
```

Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.

There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.

Any static assets, like images, can be placed in the `public/` directory.

## 🧞 Commands

All commands are run from the root of the project, from a terminal:

| Command                | Action                                           |
| :--------------------- | :----------------------------------------------- |
| `npm install`          | Installs dependencies                            |
| `npm run dev`          | Starts local dev server at `localhost:3000`      |
| `npm run build`        | Build your production site to `./dist/`          |
| `npm run preview`      | Preview your build locally, before deploying     |
| `npm run astro ...`    | Run CLI commands like `astro add`, `astro check` |
| `npm run astro --help` | Get help using the Astro CLI                     |

Check out [Astro documentation](https://docs.astro.build).

================================================
FILE: usehooks.com/astro.config.mjs
================================================
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import customTheme from "./theme.json";
import tailwind from "@astrojs/tailwind";

// import vercel from "@astrojs/vercel/edge";

import mdx from "@astrojs/mdx";

// https://astro.build/config
import sitemap from "@astrojs/sitemap";

// https://astro.build/config
export default defineConfig({
  site: "https://usehooks.com",
  trailingSlash: "never",
  integrations: [react(), tailwind(), mdx(), sitemap()],
  output: "static",
  // adapter: vercel(),
  markdown: {
    shikiConfig: {
      // Choose from Shiki's built-in themes (or add your own)
      // https://github.com/shikijs/shiki/blob/main/docs/themes.md
      theme: customTheme,
      // Add custom languages
      // Note: Shiki has countless langs built-in, including .astro!
      // https://github.com/shikijs/shiki/blob/main/docs/languages.md
      langs: [],
      // Enable word wrap to prevent horizontal scrolling
      wrap: false
    }
  }
});

================================================
FILE: usehooks.com/generate-og-images.mjs
================================================
import { readdir, readFile, writeFile } from "fs/promises";
import { join, extname } from "path";
import matter from "gray-matter";
import { Resvg } from "@resvg/resvg-js";
import { html } from "satori-html";
import satori from "satori";

const dir = "./src/content/hooks";
try {
  const files = await readdir(dir);
  const logo = await readFile(
    new URL("./public/img/logo-useHooks.svg", import.meta.url),
    {
      encoding: "base64",
      width: 300,
      height: 44,
    }
  );

  for (const file of files) {
    if (extname(file) !== ".mdx") {
      continue;
    }

    const content = await readFile(join(dir, file), "utf8");
    const result = matter(content);

    await generateOgImage({
      title: result.data.name,
      slug: result.data.name.toLowerCase(),
      description: result.data.tagline,
      logo
    });
  }
} catch (err) {
  console.error("Error:", err);
}

async function generateOgImage({ title, slug, description, logo }) {
  const svg = await satori(
    html` <style>
        div {
          display: flex;
        }

        .wrapper {
          display: flex;
          flex-direction: column;
          background-color: #0f0d0e;
          height: 630px;
          padding: 80px;
        }

        .top {
          display: flex;
          justify-content: space-between;
          align-items: center;
        }

        .bottom {
          display: flex;
          flex-direction: column;
          justify-content: flex-end;
          flex-basis: 100%;
          width: 90%;
          padding-bottom: 40px;
        }

        .logo {
          width: 300px;
          height: 44px;
        }

        .install {
          font-size: 32px;
          font-family: "Fira Code";
          color: #f9f4da;
        }

        .install span {
          color: #0ba95b;
          padding-right: 16px;
        }

        .title {
          margin-top: 16px;
          font-size: 64px;
          font-family: "Outfit Bold";
          font-weight: 700;
          color: #12b5e5;
        }

        .description {
          margin-top: 16px;
          color: #f9f4da;
          font-family: "Outfit";
          font-size: 40px;
          font-weight: 400;
        }
      </style>
      <div class="wrapper">
        <div class="top">
          <img class="logo" src="data:image/svg+xml;base64,${logo}" />
          <div class="install"><span>></span> npm i @uidotdev/usehooks</div>
        </div>
        <div class="bottom">
          <div class="title">${title}</div>
          <div class"description">${description}</div>
        </div>
      </div>`,
    {
      fonts: [
        {
          name: "Outfit",
          data: await readFile(
            new URL("./public/fonts/outfit-regular.ttf", import.meta.url)
          ),
          weight: "400",
          style: "normal",
        },
        {
          name: "Outfit Bold",
          data: await readFile(
            new URL("./public/fonts/outfit-bold.ttf", import.meta.url)
          ),
          weight: "700",
          style: "normal",
        },
        {
          name: "Fira Code",
          data: await readFile(
            new URL("./public/fonts/firacode-regular.ttf", import.meta.url)
          ),
          weight: "400",
          style: "normal",
        },
      ],
      width: 1200,
      height: 630,
    }
  );

  const resvg = new Resvg(svg, {
    fitTo: {
      mode: "original",
    },
  });
  const pngData = resvg.render();
  const pngBuffer = pngData.asPng();
  await writeFile(
    new URL(`./public/meta/${slug}.png`, import.meta.url),
    pngBuffer
  );
  console.log("✅", slug);
}


================================================
FILE: usehooks.com/package.json
================================================
{
  "name": "reacthooks.com",
  "type": "module",
  "version": "2.0.0",
  "private": true,
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "node ./generate-og-images.mjs && astro build",
    "preview": "astro preview",
    "astro": "astro"
  },
  "dependencies": {
    "@astrojs/mdx": "^0.19.3",
    "@astrojs/react": "^2.2.0",
    "@astrojs/sitemap": "^1.3.1",
    "@astrojs/tailwind": "^3.1.3",
    "@astrojs/ts-plugin": "^1.0.6",
    "@codesandbox/sandpack-react": "^2.6.6",
    "@types/react": "^18.2.7",
    "@types/react-dom": "^18.2.4",
    "astro": "^2.5.2",
    "classnames": "^2.3.2",
    "framer-motion": "^10.9.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "tailwindcss": "^3.2.7"
  },
  "devDependencies": {
    "@resvg/resvg-js": "^2.4.1",
    "satori": "^0.9.1",
    "satori-html": "^0.3.2"
  }
}


================================================
FILE: usehooks.com/src/components/Button.astro
================================================
---
export interface Props {
	text: string;
	size: string;
	href?: string;
	type: "link" | "button" | "submit" | "reset";
	color: string;
	class?: string;
}

const { type, href, text, size, color } = Astro.props;
const { class: className } = Astro.props;
---

{ type === "link" ? <a class={`button ${size} ${color} ${className}`} href={href}>{text}</a> : <button type={type} class={`button ${size} ${color} ${className}`}>{text}</button> }

<style>
	.button {
		padding: 1em 2em;
		background-color: var(--yellow);
		border-radius: 9999px;
		border: var(--border-dark);
	 	font-family: var(--font-outfit);
		font-size: clamp(.8rem, 1.5vw, 1rem);
		font-weight: 700;
		text-transform: uppercase;
		text-decoration: none;
		color: var(--charcoal);
		cursor: pointer;
		box-shadow: 1px 1px 0 var(--charcoal);
		transition: box-shadow 150ms ease-in-out;
	}

	.button.large {
		padding: 1em 2em;
	}

	.button.small {
		padding: .5em 1em;
	}

	.button.yellow {
		background-color: var(--yellow);
	}

	.button.green {
		background-color: var(--green);
	}

	.button.pink {
		background-color: var(--pink);
	}

	.button::selection {
		background-color: var(--purple);
	}

	.button:not(:disabled):focus-visible,
	.button:not(:disabled):hover {
		outline: none;
		box-shadow: var(--focus-object);
	}

	.button:disabled {
		opacity: .5;
		cursor: not-allowed;
	}
	
</style>


================================================
FILE: usehooks.com/src/components/CodePreview.astro
================================================
---
import CodePreview from "./codepreview/CodePreview";
import { getFiles } from "./codepreview/utils";

const { sandboxId, previewHeight } = Astro.props;
const files = await getFiles({ id: sandboxId });
---

<div>
  <h2>Demo:</h2>
  <div style={{ height: previewHeight }}>
    <CodePreview client:only files={files} previewHeight={previewHeight} />
  </div>
</div>


================================================
FILE: usehooks.com/src/components/CountdownTimer.tsx
================================================
import { Fragment, useEffect, useState } from "react";

interface CountdownProps {
  targetDate: string; // YYYY-MM-DD format
}

interface TimeLeft {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
}

function calculateTimeLeft(targetDate: string): TimeLeft {
  const target = new Date(`${targetDate}T00:00:00-08:00`);
  const now = new Date();
  const difference = +target - +now;

  if (difference <= 0) {
    return {
      days: 0,
      hours: 0,
      minutes: 0,
      seconds: 0,
    };
  }

  return {
    days: Math.floor(difference / (1000 * 60 * 60 * 24)),
    hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
    minutes: Math.floor((difference / 1000 / 60) % 60),
    seconds: Math.floor((difference / 1000) % 60),
  };
}

const formatNumber = (number: number) => number.toString().padStart(2, "0");

const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
  const [timeLeft, setTimeLeft] = useState<TimeLeft>(
    calculateTimeLeft(targetDate)
  );

  useEffect(() => {
    const timer = setInterval(() => {
      const newTimeLeft = calculateTimeLeft(targetDate);
      setTimeLeft(newTimeLeft);
      if (
        newTimeLeft.days === 0 &&
        newTimeLeft.hours === 0 &&
        newTimeLeft.minutes === 0 &&
        newTimeLeft.seconds === 0
      ) {
        clearInterval(timer);
      }
    }, 1000);

    return () => clearInterval(timer);
  }, [targetDate]);

  if (
    timeLeft.days === 0 &&
    timeLeft.hours === 0 &&
    timeLeft.minutes === 0 &&
    timeLeft.seconds === 0
  ) {
    return null;
  }

  return (
    <div className="countdown flex gap-1 justify-center text-md">
      {["days", "hours", "minutes", "seconds"].map((unit, index) => (
        <Fragment key={unit}>
          {index > 0 && <span className="countdown-colon pt-1 grid justify-center">:</span>}
          <div className={`${unit} grid grid-cols-2 gap-x-1 gap-y-1.5`}>
            <span className="countdown-number h-[2.2em] aspect-[6/7] grid place-content-center rounded-sm bg-brand-beige font-semibold">
              {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(0)}
            </span>
            <span className="countdown-number h-[2.2em] aspect-[6/7] grid place-content-center rounded-sm bg-brand-beige font-semibold">
              {formatNumber(timeLeft[unit as keyof TimeLeft]).charAt(1)}
            </span>
            <p className="countdown-label col-span-full text-sm">{unit}</p>
          </div>
        </Fragment>
      ))}
    </div>
  );
};

export default Countdown;


================================================
FILE: usehooks.com/src/components/HookDescription.astro
================================================
---
import Button from "./Button.astro";
const { name } = Astro.props;
---

<div class="description">
  <div>
    <h2>Description:</h2>
    <slot />
  </div>
  <aside class="callout">
    <img
      src="/img/react-gg-logo.svg"
      width="464"
      height="85"
      class="logo"
      alt="React.gg"
    />
    <p>
      Want to learn how to build {name} yourself? Check out <a
        href="https://react.gg?s=usehooks">react.gg</a
      > – the interactive way to master modern React.
    </p>
    <Button
      text="Learn More"
      size="small"
      href="https://react.gg?s=usehooks"
      type="link"
      color="green"
      class="get-started"
    />
  </aside>
</div>

<style>
  .description {
    --callout-width: 300px;
    display: grid;
    gap: 1.5rem 2rem;
    font-size: clamp(1.1rem, 2.2vw, 1.3rem);
  }

  @media screen and (min-width: 811px) {
    .description {
      grid-template-columns: 1fr var(--callout-width);
    }
  }

  .callout {
    margin: 1rem 0 1.5rem;
    align-self: start;
    padding: 1.5rem;
    padding-bottom: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    border: var(--border-dark);
    border-color: var(--coal);
    border-radius: 0.5rem;
    font-size: clamp(0.9rem, 2vw, 1.1rem);
    color: var(--white);
    text-decoration: none;
    text-align: center;
    transition: all 200ms ease-in-out;
  }

  .callout img.logo {
    max-width: 150px;
  }

  .callout .button {
    transform: translateY(50%);
  }

  .callout p {
    max-width: 40ch;
  }

  @media screen and (min-width: 811px) {
    .callout {
      max-width: var(--callout-width);
      margin-bottom: 0;
    }
  }
</style>


================================================
FILE: usehooks.com/src/components/Install.astro
================================================
---
const { text } = Astro.props;
const { class: className } = Astro.props;
---

<div class={`install ${className}`}>
  <code class={className}>{text}</code>
  <button class={`copy-btn ${className}`}>Copy</button>
</div>

<script>
  const copyButton = document.querySelector(`.copy-btn`);
  const copyText = document.querySelector(`.install code`).textContent;

  copyButton.addEventListener("click", async () => {
    try {
      if (navigator?.clipboard?.writeText) {
        await navigator.clipboard.writeText(copyText);
        copyButton.textContent = "Copied!";
        setTimeout(() => {
          copyButton.textContent = "Copy";
        }, 3000);
      } else {
        throw new Error("writeText not supported");
      }
    } catch (e) {
      oldSchoolCopy(copyText);
    }
  });

  function oldSchoolCopy(text) {
    const tempTextArea = document.createElement("textarea");
    tempTextArea.value = text;
    document.body.appendChild(tempTextArea);
    tempTextArea.select();
    document.execCommand("copy");
    document.body.removeChild(tempTextArea);
  }
</script>

<style>
  .install {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: clamp(0.7rem, 3vw, 2rem);
    background-color: var(--charcoal);
    padding: clamp(1rem, 4vw, 1.3rem) clamp(1rem, 4vw, 1.5rem);
    border-radius: 0.5rem;
  }

  .install code {
    margin-top: 0.3rem;
    padding: 0;
    font-size: clamp(0.8rem, 3vw, 1.2rem) !important;
  }

  .install code::before {
    content: ">";
    display: inline-block;
    margin-right: 0.5rem;
    color: var(--green);
  }

  .copy-btn {
    padding: 0.2rem 0.4rem;
    padding-left: 1.7rem;
    background: no-repeat url("/img/icon-copy.svg") 0.4rem 50% / 0.9rem auto;
    border: var(--border-light);
    border-radius: 0.3rem;
    font-size: clamp(0.7rem, 2vw, 0.9rem);
    font-weight: 500;
    transition: all 200ms ease-in-out;
  }

  .copy-btn:hover,
  .copy-btn:focus-visible {
    background-color: var(--coal);
    color: var(--white);
  }
</style>


================================================
FILE: usehooks.com/src/components/Logo.astro
================================================
---
const { class: className } = Astro.props;
---

<a href="https://ui.dev" class={`logo ui-logo image ${className}`}>
	<img src="/img/ui-logo.svg" width="62" height="62" alt="ui.dev" />
</a>

<style>
	.ui-logo {
		display: inline-flex;
		border-radius: 50%;
	}

	.ui-logo:hover {
		transform: scale(1.1);
	}

	.ui-logo img {
		width: clamp(3rem, 10vw, 4rem);
		display: block;
		height: auto;
	}
</style>

================================================
FILE: usehooks.com/src/components/LogoGithub.astro
================================================
---
const { class: className } = Astro.props;
---

<a href="https://github.com/uidotdev/usehooks" class={`logo image github ${className}`}>
	<img src="/img/icon-github.svg" alt="GitHub" width="48" height="48" />
</a>

<style>
	.github {
		display: inline-flex;
		border-radius: 50%;
	}

	.github:focus-visible {
		background-color: transparent;
	}

	.github:hover {
		transform: scale(1.1);
	}

	.github img {
		width: clamp(1.8rem, 7vw, 2.8rem);
		display: block;
		height: auto;
	}
</style>

================================================
FILE: usehooks.com/src/components/QueryGGBanner.astro
================================================
---
import Button from "./Button.astro";
import CountdownTimer from "./CountdownTimer";
---

<aside class="reactgg-container container">
  <div class="reactgg-banner">
    <a href="https://react.gg?s=usehooks" class="reactgg-header">
      <img
        src={"/img/banner-sale-reactgg.svg"}
        alt="react.gg - The interactive way to master modern React"
        class="reactgg-headline"
      />
    </a>
    <div class="reactgg-spacer"></div>
    <div class="reactgg-cta">
      <div class="reactgg-cta-container">
        <h2>react.gg Launch Sale</h2>
        <p>Get 30% off through May 23rd</p>
        <CountdownTimer client:only targetDate="2025-05-24" />
        <Button
          text="Join Now"
          size="small"
          href="https://react.gg?s=usehooks"
          type="link"
          color="yellow"
          class="join-now mt-4 mb-1 lg:mb-2 inline-block"
        />
      </div>
    </div>
  </div>
</aside>

<style>
  .reactgg-container {
    width: 100%;
    max-width: 1300px;
    margin: 0 auto 30px;
  }

  .reactgg-banner {
    width: 100%;
    background-color: var(--brand-charcoal);
    border: 2px solid var(--brand-charcoal);
    border-radius: 8px;
    box-shadow: 0.2rem 0.2rem 0 var(--brand-charcoal);
    overflow: hidden;
    @media (min-width: 1024px) {
      display: grid;
      grid-template-columns: 1fr 40px 1fr;
    }
  }

  .reactgg-header {
    display: grid;
    align-self: center;
    place-items: center;
    align-content: center;
    height: 190px;
    overflow: hidden;
    @media (min-width: 1024px) {
      height: 280px;
      grid-column: 1 / 3;
      grid-row: 1;
    }
  }

  .reactgg-headline {
    width: 110%;
    max-width: none;
  }

  .reactgg-spacer {
    width: 40px;
    margin-left: 20px;
    display: none;
    background-color: var(--brand-green);
    z-index: 0;
    transform: skew(-7deg);
    @media (min-width: 1024px) {
      display: block;
      grid-column: 2;
      grid-row: 1;
      height: 100%;
    }
  }

  .reactgg-cta {
    padding: 0.5rem;
    display: grid;
    justify-content: center;
    background-color: var(--brand-green);
    color: var(--brand-charcoal);
    z-index: 10;
    @media (min-width: 1024px) {
      padding-bottom: 0;
      grid-row: 1;
      grid-column: 3;
    }
  }

  .reactgg-cta-container {
    margin-top: 0.5rem;
    margin-bottom: 0.5rem;
    place-self: center;
    text-align: center;
    text-transform: uppercase;

    h2 {
      margin-top: 0;
      margin-bottom: 0.5rem;
      font-size: 1.25rem;
      line-height: 1.75rem;
      font-weight: 600;
      color: var(--brand-coal);
      @media (min-width: 1024px) {
        font-size: 1.5rem;
        line-height: 2rem;
      }
      @media (min-width: 1280px) {
        font-size: 1.875rem;
        line-height: 2.25rem;
      }
    }

    h2 + p {
      margin-top: 0;
      margin-bottom: 1.2rem;
      text-transform: none;
      font-size: 90%;
    }
  }

  :global(.countdown-colon),
  :global(.countdown-number) {
    font-size: clamp(1rem, 4vw, 1.5rem);
  }
</style>


================================================
FILE: usehooks.com/src/components/RelatedHook.astro
================================================
---
import { getEntry } from "astro:content";
import HookCard from "./search/HookCard";

const { slug } = Astro.props;
const relatedHook = await getEntry("hooks", slug);
const { name, tagline } = relatedHook.data;
---

<HookCard name={name} tagline={tagline} />

================================================
FILE: usehooks.com/src/components/StaticCodeContainer.astro
================================================
---
import CodeWrapper from './codepreview/CodeWrapper'
---
<div>
  <h2>Example:</h2>
  <div class="min-h-[500px]">
    <CodeWrapper client:only>
      <slot />
    </CodeWrapper>
  </div>
</div>

<noscript>
  <slot />
</noscript>

================================================
FILE: usehooks.com/src/components/Svg.astro
================================================
---
export interface Props {
  name: string;
}

const { name } = Astro.props as Props;
const { default: innerHTML } = await import(`../svg/${name}.svg?raw`);
---

<Fragment set:html={innerHTML} />

================================================
FILE: usehooks.com/src/components/codepreview/CodePreview.tsx
================================================
import {
  SandpackProvider,
  SandpackPreview,
  SandpackStack,
  SandpackFiles,
} from "@codesandbox/sandpack-react";
import cx from "classnames";

export type PreviewProps = {
  previewHeight?: string;
  files?: SandpackFiles | undefined;
};

export default function CodePreview({
  previewHeight = "250px",
  files,
}: PreviewProps) {
  const sandpackProviderProps = {
    files,
    initMode: "user-visible",
    autorun: false,
    logLevel: 0,
  };

  return (
    <div className="editor-wrapper code-preview border-solid border border-brand-coal rounded-[.5rem] overflow-hidden not-prose">
      <SandpackProvider
        template="react"
        theme={{
          colors: {
            surface1: "var(--coal)",
            surface2: "var(--coal)",
          },
        }}
        {...sandpackProviderProps}
      >
        <SandpackStack className="relative">
          <SandpackPreview
            showOpenInCodeSandbox={false}
            showRefreshButton={false}
            style={{ height: previewHeight }}
            className={cx("visible h-full")}
          />
        </SandpackStack>
      </SandpackProvider>
    </div>
  );
}


================================================
FILE: usehooks.com/src/components/codepreview/CodeWrapper.tsx
================================================
import { useState } from "react";
import { motion } from "framer-motion";
import { set } from "astro/zod";

const springConfig = {
  damping: 20,
  stiffness: 120,
  mass: 0.15,
};

export default function CodeWrapper({
  children,
}: {
  children: React.ReactNode;
}) {
  const [expanded, setExpanded] = useState(false);
  const [hideExpand, setHideExpand] = useState(false);

  return (
    <div className="relative bg-brand-coal rounded-[.5rem]">
      <motion.div
        variants={{
          expanded: {
            height: "auto",
          },
          collapsed: {
            height: "500px",
          },
        }}
        transition={springConfig}
        initial="collapsed"
        animate={expanded ? "expanded" : "collapsed"}
        className="relative overflow-auto "
      >
        <div
          ref={(el) => {
            if (el) {
              const { height } = el.getBoundingClientRect();
              if (height < 500) {
                setExpanded(true);
                setHideExpand(true);
              }
            }
          }}
        >
          {children}
        </div>
      </motion.div>
      {!hideExpand && (
        <div
          className={`${
            expanded ? "relative" : "absolute"
          } bottom-0 w-full p-4`}
        >
          <button
            className="font-bold uppercase rounded-full px-4 py-1 flex text-xs items-center justify-center bg-brand-charcoal text-brand-beige gap-2 hover:shadow-sm hover:bg-brand-green hover:text-brand-charcoal border border-brand-charcoal transition-all"
            onClick={() => setExpanded(!expanded)}
          >
            {expanded ? "Collapse" : "Expand"}
          </button>
        </div>
      )}
    </div>
  );
}


================================================
FILE: usehooks.com/src/components/codepreview/utils.ts
================================================
import { SandpackFile } from "@codesandbox/sandpack-react";

export async function getFiles({ id }: { id: string }) {
  const configUrl = `https://codesandbox.io/api/v1/sandboxes/${id}/sandpack`;
  const response = await fetch(configUrl);
  if (response.ok) {
    const data = await response.json();
    // This hardcodes the active file, we should find a better way to do this
    return updateFiles(data.files);
  }
  return {};
}

// https://codesandbox.io/s/challenge-ui-test-zurc8s

function getChallengeConfig(json: string) {
  const csb = JSON.parse(json);

  if (csb?.previewConfig) {
    return csb.previewConfig;
  }

  return {
    visibleFiles: [],
    activeFile: "/src/App.js",
  };
}

export function updateFiles(files: { [key: string]: SandpackFile }) {
  const previewConfig = getChallengeConfig(files["/package.json"].code);
  Object.keys(files).map((key) => {
    if (key === previewConfig.activeFile) {
      files[key].active = true;
    }
    if (!previewConfig.visibleFiles.includes(key)) {
      files[key].hidden = true;
    }
  });
  return files;
}


================================================
FILE: usehooks.com/src/components/search/Callout.module.css
================================================
.callout :global(a) {
  height: 100%;
  padding: var(--body-padding);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  border: var(--border-dark);
  border-radius: 0.5rem;
  font-size: clamp(0.9rem, 2vw, 1.1rem);
  text-align: center;
  transition: all 200ms ease-in-out;
}

.callout :global(a:hover) {
  transform: scale(1.03);
}

.callout :global(img:not(.logo)) {
  max-width: 120px;
  margin-top: calc(var(--body-padding) * -1.2);
  margin-bottom: 0.5rem;
}

.callout :global(img.d20) {
  width: 70px;
}

.callout :global(img.money) {
  width: 110px;
}

.callout :global(img.spinner) {
  width: 80px;
}

.callout :global(img.hot-sauce) {
  width: 80px;
}

.callout :global(img.logo) {
  max-width: 150px;
}


================================================
FILE: usehooks.com/src/components/search/Callout.tsx
================================================
import styles from "./Callout.module.css";

type Props = {
  image: string;
  imageWidth: string;
  imageHeight: string;
  imageAlt: string;
  pitch: string;
};

export default function Callout({
  image,
  imageWidth,
  imageHeight,
  imageAlt,
  pitch,
}) {
  return (
    <li className={styles.callout}>
      <a href="https://react.gg?s=usehooks" className="logo image">
        <img
          src={`/img/${image}.svg`}
          width={imageWidth}
          height={imageHeight}
          className={image}
          alt={imageAlt}
        />
        <img
          src="/img/react-gg-logo.svg"
          width="464"
          height="85"
          className="logo"
          alt="React.gg"
        />
        <p>{pitch}</p>
      </a>
    </li>
  );
}


================================================
FILE: usehooks.com/src/components/search/HookCard.module.css
================================================
.hook > :global(a) {
  height: 100%;
  padding: var(--body-padding);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background-color: var(--charcoal);
  border-radius: 0.5rem;
  transition: transform 200ms ease-in-out;
}

.hook > :global(a:hover) {
  transform: scale(1.03);
}

.card-title {
  color: var(--blue);
  text-transform: none;
  font-family: var(--font-outfit);
  font-size: clamp(1.1rem, 3vw, 1.4rem);
  font-weight: 600;
}

.card-description {
  margin-bottom: 0.5rem;
  font-size: clamp(0.9rem, 2vw, 1.1rem);
}

.arrow {
  width: 28px;
  aspect-ratio: 3 / 2;
  margin-top: auto;
  align-self: flex-end;
  transition: all 200ms ease-in-out;
}

:global(a:hover) .arrow {
  transform: translateX(0.6rem);
}


================================================
FILE: usehooks.com/src/components/search/HookCard.tsx
================================================
import styles from './HookCard.module.css';

export default function HookCard({
  name,
  tagline,
}: {
  name: string;
  tagline: string;
}) {
  return (
    <li className={styles.hook}>
      <a href={`/${name.toLowerCase()}`}>
        <h3 className={styles["card-title"]}>{name}</h3>
        <p className={styles["card-description"]}>{tagline}</p>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          className={styles.arrow}
          viewBox="0 0 36 24"
        >
          <path
            fill="none"
            stroke="#12b2e2"
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth="2"
            d="M1.86 12h30.18M21.31 1.15 34.14 12 21.31 22.85"
          />
        </svg>
      </a>
    </li>
  );
}


================================================
FILE: usehooks.com/src/components/search/HookSearch.module.css
================================================
.hooks-search {
  position: relative;
  align-self: flex-start;
}

.input {
  padding: 0.4rem 1rem;
  padding-right: 2rem;
  background-color: var(--coal);
  border: var(--border-light);
  border-radius: 1.5rem;
  font-size: clamp(0.8rem, 2vw, 1rem);
  color: var(--white);
  transition: all 150ms ease-in-out;
}

.input:focus {
  outline: none;
  border-color: var(--charcoal);
  box-shadow: var(--focus-object);
}

.input + :global(button) {
  position: absolute;
  right: 0.8rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: 1rem;
  transition: all 150ms ease-in-out;
}

:global(input[type="search"]::-webkit-search-decoration),
:global(input[type="search"]::-webkit-search-cancel-button),
:global(input[type="search"]::-webkit-search-results-button),
:global(input[type="search"]::-webkit-search-results-decoration) {
  display: none;
}


================================================
FILE: usehooks.com/src/components/search/HookSearch.tsx
================================================
import styles from "./HookSearch.module.css";

export default function HookSearch({
  handleChange,
  handleClear,
  value,
}: {
  handleClear: () => void;
  handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  value: string;
}) {
  return (
    <div className={styles["hooks-search"]}>
      <input
        onChange={handleChange}
        className={styles.input}
        value={value}
        type="search"
        id="search"
        placeholder="Search"
      />
      <button id="search-cancel" aria-label="Clear" onClick={handleClear}>
        ✕
      </button>
    </div>
  );
}


================================================
FILE: usehooks.com/src/components/search/HookSort.module.css
================================================
.toggle {
  padding: 0.2rem 0.4rem;
  border: var(--border-light);
  border-radius: 0.3rem;
  font-size: clamp(0.7rem, 2vw, 0.9rem);
  font-weight: 500;
  transition: all 200ms ease-in-out;
}

.toggle.active {
  background-color: var(--yellow);
  border-color: var(--yellow);
  color: var(--charcoal);
  cursor: default;
}

.toggle:not(.active):hover {
  background-color: var(--charcoal);
}


================================================
FILE: usehooks.com/src/components/search/HookSort.tsx
================================================
import styles from "./HookSort.module.css";

export default function HookSort({
  setSort,
  value,
}: {
  setSort: (value: "name" | "popular") => void;
  value: "name" | "popular";
}) {
  return (
    <div className="hooks-sort flex items-center gap-2">
      <small>Sort:</small>
      <button
        onClick={() => setSort("popular")}
        className={`${styles.toggle} ${
          value === "popular" ? styles.active : ""
        }`}
      >
        Popular
      </button>
      <button
        onClick={() => setSort("name")}
        className={`${styles.toggle} ${value === "name" ? styles.active : ""}`}
      >
        Name
      </button>
    </div>
  );
}


================================================
FILE: usehooks.com/src/components/search/HooksList.module.css
================================================
.hooks-grid {
  max-width: 980px;
  margin: 2rem auto;
}

.hooks-controls {
  padding: 1rem var(--body-padding);
  display: flex;
  justify-content: flex-end;
}

.hooks-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  align-items: stretch;
  gap: 1.6rem;
}


================================================
FILE: usehooks.com/src/components/search/HooksList.tsx
================================================
import { useState } from "react";
import Callout from "./Callout";
import HookCard from "./HookCard";
import HookSort from "./HookSort";
import styles from "./HooksList.module.css";

function insertAtIntervals(arr, items) {
  let newArr = [...arr]; // create a copy of the array
  let step = Math.ceil(newArr.length / items.length);

  // Insert the first item at the beginning of the array
  newArr.unshift(items[0]);

  for (let i = 1; i < items.length; i++) {
    let position = i * step + 1; // +1 to account for the first item
    newArr.splice(position, 0, items[i]);
  }

  return newArr;
}

function sortAlphabetical(a, b) {
  if (a.data.name < b.data.name) {
    return -1;
  }
  if (a.data.name > b.data.name) {
    return 1;
  }
  return 0;
}

function sortByPopularity(a, b) {
  if (a.data.rank < b.data.rank) {
    return -1;
  }
  if (a.data.rank > b.data.rank) {
    return 1;
  }
  return 0;
}

const sortMap = {
  name: sortAlphabetical,
  popular: sortByPopularity,
};

export default function HooksList({ hooks }) {
  const [sort, setSort] = useState<"name" | "popular">("popular");

  const list = hooks.sort(sortMap[sort]);
  const listWithCallouts = insertAtIntervals(list, [
    {
      id: "Callout 1",
      image: "d20",
      imageWidth: "222",
      imageHeight: "206",
      imageAlt: "20-sided die",
      pitch:
        "It’s dangerous to go alone! Master React by learning how to build useHooks yourself.",
    },
    {
      id: "Callout 2",
      image: "spinner",
      imageWidth: "284",
      imageHeight: "180",
      imageAlt: "board game spinner and all options are React",
      pitch:
        "There’s no better way to learn useHooks than by building it yourself.",
    },
    {
      id: "Callout 3",
      image: "money",
      imageWidth: "210",
      imageHeight: "210",
      imageAlt: "$100 Monopoly-style money",
      pitch:
        "Please give us your money.",
    },
    {
      id: "Callout 4",
      image: "hot-sauce",
      imageWidth: "206",
      imageHeight: "224",
      imageAlt: "travel-style postcard from React that says “Enjoy the views!",
      pitch:
        "The all new interactive way to master modern React (for fun and profit).",
    }
  ]);

  return (
    <section className={styles["hooks-grid"]}>
      <div className={styles["hooks-controls"]}>
        <HookSort setSort={setSort} value={sort} />
      </div>
      <ul className={styles["hooks-list"]}>
        {listWithCallouts.map(
          ({ data, id, image, imageWidth, imageHeight, imageAlt, pitch }) => {
            if (!data) {
              return (
                <Callout
                  key={id}
                  image={image}
                  imageWidth={imageWidth}
                  imageHeight={imageHeight}
                  imageAlt={imageAlt}
                  pitch={pitch}
                />
              );
            }
            return (
              <HookCard key={id} name={data.name} tagline={data.tagline} />
            );
          }
        )}
      </ul>
    </section>
  );
}


================================================
FILE: usehooks.com/src/content/config.ts
================================================
import { defineCollection, z, reference } from 'astro:content';

export const collections = {
  hooks: defineCollection({
    schema: z.object({
      experimental: z.boolean().optional(),
      draft: z.boolean().default(false),
      sandboxId: z.string().optional(),
      previewHeight: z.string().optional(),
      name: z.string(),
      tagline: z.string(),
      ogImage: z.string().optional(),
      rank: z.number(),
      relatedHooks: z.array(
        reference('hooks').optional()
      ).optional(),
    }),
  }),
};

================================================
FILE: usehooks.com/src/content/hooks/useBattery.mdx
================================================
---
name: useBattery
rank: 32
tagline: Track the battery status of a user’s device with useBattery.
sandboxId: usebattery-o8js1p
previewHeight: 320px
relatedHooks:
  - usenetworkstate
  - usepreferredlanguage
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useBattery hook is useful for accessing and monitoring the battery status
  of the user’s device in a React application. By using this hook, you can
  easily retrieve information such as the battery level, charging status, and
  estimated charging and discharging times. It provides a state object that
  includes properties like supported, loading, level, charging, chargingTime,
  and dischargingTime.
</HookDescription>

<div class="reference">
  ### Return Values
  The hook returns an object containing the following properties:

  <div class="table-container">
  | Name             | Type    | Description |
  | ---------------- | ------- | ----------- |
  | supported        | boolean | Indicates whether the Battery Status API is supported in the user’s browser. |
  | loading          | boolean | Indicates if the battery information is still loading. |
  | level            | number  | Represents the level of the system’s battery. 0.0 means that the system’s battery is completely discharged, and 1.0 means the battery is completely charged. |
  | charging         | boolean | Represents whether the system’s battery is charging. `true` means the battery is charging, `false` means it’s not. |
  | chargingTime     | number  | Represents the time remaining in seconds until the system’s battery is fully charged. |
  | dischargingTime  | number  | Represents the time remaining in seconds until the system’s battery is completely discharged and the system is about to be suspended. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import { useBattery } from "@uidotdev/usehooks";
import Battery from "./Battery";

export default function App() {
  const { loading, level, charging, chargingTime, dischargingTime } =
    useBattery();
  return (
    <>
      <div className="wrapper">
        <h1>useBattery</h1>
        {!loading ? (
          <Battery
            level={level * 100}
            charging={charging}
            chargingTime={chargingTime}
            dischargingTime={dischargingTime}
          />
        ) : (
          <h2>Loading...</h2>
        )}
      </div>
    </>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useClickAway.mdx
================================================
---
name: useClickAway
rank: 47
tagline: Detect clicks outside of specific component with useClickAway.
sandboxId: useclickaway-p4hl4m
previewHeight: 250px
relatedHooks:
  - uselongpress
  - usehover
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useClickAway hook is a useful for detecting clicks outside a specific
  component. It allows you to pass a callback function that will be triggered
  whenever a click occurs outside the component’s area. This hook is
  particularly helpful when implementing dropdown menus, modals, or any other UI
  elements that need to be closed when the user clicks outside of them. By
  attaching event listeners to the document, the hook checks if the click target
  is within the component’s reference, and if not, it invokes the provided
  callback function.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name | Type     | Description |
  | ---- | -------- | ----------- |
  | callback | function | The callback function that is provided as an argument to `useClickAway`. This function is invoked whenever a click event is detected outside of the referenced element. The event object from the click is passed to this callback function. |
  </div>

  ### Return Values

  <div class="table-container">
  | Name | Type         | Description |
  | ---- | ------------ | ----------- |
  | ref  | React ref    | This is a ref object returned by the hook. It should be attached to a React element to monitor click events. The ref provides a way to access the properties of the element it is attached to. |
  </div>
</div>


<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useClickAway } from "@uidotdev/usehooks";
import { closeIcon } from "./icons";

export default function App() {
  const [isOpen, setIsOpen] = React.useState(false);
  const ref = useClickAway(() => {
    setIsOpen(false);
  });

  const handleOpenModal = () => {
    if (isOpen === false) {
      setIsOpen(true);
    }
  };

  return (
    <>
      <section>
        <h1>useClickAway</h1>
        <button className="link" onClick={handleOpenModal}>
          Open Modal
        </button>
      </section>
      {isOpen && (
        <dialog ref={ref}>
          <button onClick={() => setIsOpen(false)}>{closeIcon}</button>
          <h2>Modal</h2>
          <p>
            Click outside the modal to close (or use the button) whatever you
            prefer.
          </p>
        </dialog>
      )}
    </>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useContinuousRetry.mdx
================================================
---
name: useContinuousRetry
experimental: true
rank: 12
tagline: Automates retries of a callback function until it succeeds with useContinuousRetry
sandboxId: usecontinuousretry-v0uf1n
previewHeight: 380px
relatedHooks:
  - usesessionstorage
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useContinuousRetry hook allows you to repeatedly call a specified callback
  function at a defined interval until the callback returns a truthy value,
  indicating a successful resolution. This hook is particularly handy when
  dealing with asynchronous operations or API calls that may fail temporarily
  and need to be retried automatically. It encapsulates the logic of retrying
  and provides a clean interface to handle retry-related states, such as whether
  the retry process has resolved or not.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name     | Type     | Description                                                                                                                                                  |
  | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------|
  | callback | function | The callback function to be executed repeatedly until it returns a truthy value.                                                                             |
  | interval | number   | (Optional) The interval in milliseconds at which the callback function is executed. Default value is 100 milliseconds.                                       |
  | options  | object   | (Optional) An object containing a `maxRetries` property which tells `useContinuousRetry` the maximum amount of retry attempts it should make before stopping |
  </div>

  ### Return Value

  <div class="table-container">
  | Type    | Description                                                                                |
  | ------- | ------------------------------------------------------------------------------------------ |
  | boolean | `true` if the callback function has resolved (returned a truthy value), `false` otherwise. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useContinuousRetry } from "@uidotdev/usehooks";

export default function App() {
  const [count, setCount] = React.useState(0);
  const hasResolved = useContinuousRetry(() => {
    console.log("retrying");
    return count > 10;
  }, 1000);

  return (
    <section>
      <h1>useContinuousRetry</h1>
      <button className="primary" onClick={() => setCount(count + 1)}>
        {count}
      </button>
      <pre>{JSON.stringify({ hasResolved, count }, null, 2)}</pre>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useCopyToClipboard.mdx
================================================
---
name: useCopyToClipboard
rank: 31
tagline: Copy text to the clipboard using useCopyToClipboard.
sandboxId: usecopytoclipboard-y22r6w
previewHeight: 300px
relatedHooks:
  - usemap
  - usequeue
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useCopyToClipboard hook is useful because it abstracts the complexity of
  copying text to the clipboard in a cross-browser compatible manner. It
  utilizes the modern navigator.clipboard.writeText method if available, which
  provides a more efficient and secure way to copy text. In case the writeText
  method is not supported by the browser, it falls back to a traditional method
  using the document.execCommand("copy") approach.
</HookDescription>

<div class="reference">
  ### Return Value

  The `useCopyToClipboard` hook returns an array with the following elements:

  <div class="table-container">
  | Index | Type     | Description                                            |
  | ----- | -------- | ------------------------------------------------------ |
  | 0     | string   | The value that was last copied to the clipboard.       |
  | 1     | function | A function to copy a specified value to the clipboard. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useCopyToClipboard } from "@uidotdev/usehooks";
import { copyIcon, checkIcon } from "./icons";

const randomHash = crypto.randomUUID();

export default function App() {
  const [copiedText, copyToClipboard] = useCopyToClipboard();
  const hasCopiedText = Boolean(copiedText);
  return (
    <section>
      <h1>useCopyToClipboard</h1>
      <article>
        <label>Fake API Key</label>
        <pre>
          <code>{randomHash}</code>
          <button
            disabled={hasCopiedText}
            className="link"
            onClick={() => copyToClipboard(randomHash)}
          >
            {hasCopiedText ? checkIcon : copyIcon}
          </button>
        </pre>
      </article>
      {hasCopiedText && (
        <dialog open={hasCopiedText}>
          <h4>
            Copied{" "}
            <span role="img" aria-label="Celebrate Emoji">
              🎉
            </span>
          </h4>
          <textarea placeholder="Paste your copied text" />
        </dialog>
      )}
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useCountdown.mdx
================================================
---
name: useCountdown
experimental: true
rank: 21
tagline: Create countdown timers using useCountdown.
sandboxId: usecountdown-6gzbhg
previewHeight: 340px
relatedHooks:
  - useinterval
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useCountdown hook is useful for creating a countdown timer. By specifying
  an endTime and various options such as the interval between ticks and callback
  functions for each tick and completion, the hook sets up an interval that
  updates the count and triggers the appropriate callbacks until the countdown
  reaches zero. The countdown value is returned, allowing you to easily
  incorporate and display the countdown in your components.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name    | Type   | Description                                                                                                                                                                                                                                                                                                         |
  | ------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | endTime | number | The end time of the countdown in milliseconds since the Unix epoch.                                                                                                                                                                                                                                                 |
  | options | object | An object containing the following options:<ul><li>`interval`: The interval in milliseconds at which the countdown should tick.</li><li>`onComplete`: A callback function to be called when the countdown reaches zero.</li><li>`onTick`: A callback function to be called on each tick of the countdown.</li></ul> |
  </div>

  ### Return Value

  <div class="table-container">
  | Name  | Type   | Description                         |
  | ----- | ------ | ----------------------------------- |
  | count | number | The current count of the countdown. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useCountdown } from "@uidotdev/usehooks";

export default function App() {
  const [endTime, setEndTime] = React.useState(new Date(Date.now() + 10000));
  const [complete, setComplete] = React.useState(false);

  const count = useCountdown(endTime, {
    interval: 1000,
    onTick: () => console.log("tick"),
    onComplete: (time) => setComplete(true),
  });

  const handleClick = (time) => {
    if (complete === true) return;
    const nextTime = endTime.getTime() + time;
    setEndTime(new Date(nextTime));
  };

  return (
    <section>
      <header>
        <h1>useCountdown</h1>
      </header>
      <span className="countdown">{count}</span>
      {complete === false && (
        <div className="button-row">
          <button onClick={() => handleClick(5000)}>+5s</button>
          <button onClick={() => handleClick(10000)}>+10s</button>
          <button onClick={() => handleClick(15000)}>+15s</button>
        </div>
      )}
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useCounter.mdx
================================================
---
name: useCounter
rank: 49
tagline: Manage a counter value with minimum and maximum limits with useCounter.
sandboxId: usecounter-ddeo74
previewHeight: 450px
relatedHooks:
  - usecountdown
  - useprevious
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useCounter hook is useful for managing a counter value with additional
  options for minimum and maximum limits. The hook takes a starting value and
  options object as parameters, which can specify minimum and maximum limits for
  the counter. If the starting value falls outside the specified limits, an
  error is thrown. The hook returns the current count value and an object
  containing functions to increment, decrement, set a specific count, and reset
  the counter.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name          | Type   | Description                                        |
  | ------------- | ------ | -------------------------------------------------- |
  | startingValue | number | The initial value for the counter. Default is `0`. |
  | options       | object | Additional options for the counter.                |
  | options.min   | number | The minimum value allowed for the counter.         |
  | options.max   | number | The maximum value allowed for the counter.         |
  </div>

  ### Return Value

  The `useCounter` hook returns an array with two elements:

  <div class="table-container">
  | Name            | Parameters        | Description                                          |
  | --------------- | ----------------- | ---------------------------------------------------- |
  | `[0]`           |                   | The current value of the counter.                    |
  | `[1].increment` |                   | Increments the counter by 1.                         |
  | `[1].decrement` |                   | Decrements the counter by 1.                         |
  | `[1].set`       | nextCount: number | Sets the counter to the specified `nextCount` value. |
  | `[1].reset`     |                   | Resets the counter to the initial `startingValue`.   |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useCounter } from "@uidotdev/usehooks";

export default function App() {
  const [count, { increment, decrement, set, reset }] = useCounter(5, {
    min: 5,
    max: 10,
  });

  return (
    <section>
      <h1>UseCounter</h1>
      <h6>with optional min / max</h6>
      <button disabled={count >= 10} className="link" onClick={increment}>
        Increment
      </button>
      <button disabled={count <= 5} className="link" onClick={decrement}>
        Decrement
      </button>
      <button className="link" onClick={() => set(6)}>
        Set to 6
      </button>
      <button className="link" onClick={reset}>
        Reset
      </button>
      <p>{count}</p>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useDebounce.mdx
================================================
---
name: useDebounce
rank: 1
tagline: Delay the execution of function or state update with useDebounce.
sandboxId: usedebounce-hmo7m3
previewHeight: 1000px
relatedHooks:
  - usethrottle
  - usefetch
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useDebounce hook is useful for delaying the execution of functions or
  state updates until a specified time period has passed without any further
  changes to the input value. This is especially useful in scenarios such as
  handling user input or triggering network requests, where it effectively
  reduces unnecessary computations and ensures that resource-intensive
  operations are only performed after a pause in the input activity.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name  | Type   | Description |
  | ----- | ------ | ----------- |
  | value | any    | The value that you want to debounce. This can be of any type. |
  | delay | number | The delay time in milliseconds. After this amount of time, the latest value is used. |
  </div>

  ### Return Values

  <div class="table-container">
  | Name          | Type | Description |
  | ------------- | ---- | ----------- |
  | debouncedValue| any  | The debounced value. After the delay time has passed without the `value` changing, this will be updated to the latest `value`. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useDebounce } from "@uidotdev/usehooks";
import searchHackerNews from "./searchHackerNews";
import SearchResults from "./SearchResults";

export default function App() {
  const [searchTerm, setSearchTerm] = React.useState("js");
  const [results, setResults] = React.useState([]);
  const [isSearching, setIsSearching] = React.useState(false);
  const debouncedSearchTerm = useDebounce(searchTerm, 300);

  const handleChange = (e) => {
    setSearchTerm(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    setSearchTerm(formData.get("search"));
    e.target.reset();
    e.target.focus();
  };

  React.useEffect(() => {
    const searchHN = async () => {
      let results = [];
      setIsSearching(true);
      if (debouncedSearchTerm) {
        const data = await searchHackerNews(debouncedSearchTerm);
        results = data?.hits || [];
      }

      setIsSearching(false);
      setResults(results);
    };

    searchHN();
  }, [debouncedSearchTerm]);

  return (
    <section>
      <header>
        <h1>useDebounce</h1>
        <form onSubmit={handleSubmit}>
          <input
            name="search"
            placeholder="Search HN"
            style={{ background: "var(--charcoal)" }}
            onChange={handleChange}
          />
          <button className="primary" disabled={isSearching} type="submit">
            {isSearching ? "..." : "Search"}
          </button>
        </form>
      </header>
      <SearchResults results={results} />
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useDefault.mdx
================================================
---
name: useDefault
rank: 44
tagline: Manage state with default values using useDefault.
sandboxId: usedefault-5c0pyt
previewHeight: 250px
relatedHooks:
  - useprevious
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The `useDefault` hook behaves similar to `useState` but with one difference – if the state of the hook is `undefined` or `null`, `useDefault` will default the state to a provided default value.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name         | Type     | Description |
  | ------------ | -------- | ----------- |
  | initialValue | any      | The initial value of the state returned from `useDefault` |
  | defaultValue | any      | The default value to be used if the state is `undefined` or `null`. |
  </div>

  ### Return Values

  <div class="table-container">
  | Name     | Type            | Description |
  | -------- | --------------- | ----------- |
  | state    | any             | The current state. If the state is `undefined` or `null`, `defaultValue` is returned instead. |
  | setState | function        | The state setter function returned from `React.useState()`. This can be called to update the state. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useDefault } from "@uidotdev/usehooks";

export default function App() {
  const initialState = { name: "Tyler" };
  const defaultState = { name: "Ben" };

  const [user, setUser] = useDefault(initialState, defaultState);

  return (
    <section>
      <h1>useDefault</h1>

      <button
        title="Sets the value to Lynn"
        className="link"
        onClick={() => setUser({ name: "Lynn" })}
      >
        Lynn
      </button>
      <button
        title="Sets the value to Tyler"
        className="link"
        onClick={() => setUser({ name: "Tyler" })}
      >
        Tyler
      </button>
      <button
        title="Sets the value to null causing it to use the default value"
        className="link"
        onClick={() => setUser(null)}
      >
        Null
      </button>
      <pre>
        <code>{JSON.stringify(user)}</code>
      </pre>
    </section>
  );
}

```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useDocumentTitle.mdx
================================================
---
name: useDocumentTitle
rank: 40
tagline: Dynamically update the title of a webpage with useDocumentTitle.
sandboxId: usedocumenttitle-6vmc1n
previewHeight: 300px
relatedHooks:
  - usefavicon
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useDocumentTitle hook is useful for dynamically updating the title of a
  webpage based on the current state or data. This hook proves beneficial in
  scenarios where the title needs to be dynamically updated, such as displaying
  the name of the currently selected item or reflecting changes in application
  state.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name  | Type   | Description                           |
  | ----- | ------ | ------------------------------------- |
  | title | string | The title to be set for the document. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useDocumentTitle } from "@uidotdev/usehooks";

export default function App() {
  const [count, setCount] = React.useState(0);

  useDocumentTitle(`Clicked ${count} times.`);
  return (
    <section>
      <h1>useDocumentTitle</h1>
      <h6>
        <a
          className="link"
          href="https://6vmc1n.csb.app/"
          target="_blank"
          rel="noreferrer"
        >
          Try in a new tab
        </a>
      </h6>
      <button className="primary" onClick={() => setCount(count + 1)}>
        Increment Count: {count}
      </button>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useEventListener.mdx
================================================
---
name: useEventListener
experimental: true
rank: 26
tagline: Listen for events on a target element with useEventListener.
sandboxId: useeventlistener-51h645
previewHeight: 250px
relatedHooks:
  - usescript
  - usehover
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useEventListener hook enables you to add event listeners to a target
  element within a React component. By using useEventListener, you can handle
  various types of events, such as mouse events or keyboard events, and specify
  the target element, event name, event handler function, and additional
  options. applications.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name            | Type               | Description                                                                                                         |
  | --------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- |
  | target          | ref or DOM element | The target element to attach the event listener to. It can be either a ref object (`ref.current`) or a DOM element. |
  | eventName       | string             | The name of the event to listen for.                                                                                |
  | handler         | function           | The event handler function to be executed when the event is triggered.                                              |
  | options         | object             | (Optional) Additional options for the event listener.                                                               |
  | options.capture | boolean            | Specifies whether the event should be captured during the capturing phase. Default is `false`.                      |
  | options.passive | boolean            | Specifies whether the event listener will not call `preventDefault()`. Default is `false`.                          |
  | options.once    | boolean            | Specifies whether the event listener should only be invoked once. Default is `false`.                               |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useEventListener } from "@uidotdev/usehooks";
import { closeIcon } from "./icons";

export default function App() {
  const ref = React.useRef(null);
  const [isOpen, setIsOpen] = React.useState(false);

  const handleClick = (e) => {
    const element = ref.current;
    if (element && !element.contains(e.target)) {
      setIsOpen(false);
    }
  };

  useEventListener(document, "mousedown", handleClick);

  return (
    <section>
      <h1>useEventListener</h1>
      <div style={{ minHeight: "200vh" }}>
        <button className="link" onClick={() => setIsOpen(true)}>
          Click me
        </button>
      </div>
      {isOpen && (
        <dialog ref={ref}>
          <button onClick={() => setIsOpen(false)}>{closeIcon}</button>
          <h2>Modal</h2>
          <p>
            Click outside the modal to close (or use the button) whatever you
            prefer.
          </p>
        </dialog>
      )}
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useFavicon.mdx
================================================
---
name: useFavicon
rank: 43
tagline: Dynamically update the favicon with useFavicon.
sandboxId: usefavicon-xckn4k
previewHeight: 200px
relatedHooks:
  - usedocumenttitle
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useFavicon hook is a useful for dynamically update the favicon (shortcut icon) of a web page. By using this hook, you can easily change the favicon by providing a new URL as a parameter. It creates a new link element with the specified URL, sets its type and relationship attributes appropriately, and appends it to the `<head>` section of the document.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name | Type   | Description                                        |
  | ---- | ------ | -------------------------------------------------- |
  | url  | string | The URL of the favicon to be set for the document. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useFavicon } from "@uidotdev/usehooks";

export default function App() {
  const [favicon, setFavicon] = React.useState(
    "https://ui.dev/favicon/favicon-32x32.png"
  );

  useFavicon(favicon);

  return (
    <section>
      <h1>useFavicon</h1>

      <button
        title="Set the favicon to Bytes' logo"
        className="link"
        onClick={() =>
          setFavicon("https://bytes.dev/favicon/favicon-32x32.png")
        }
      >
        Bytes
      </button>
      <button
        title="Set the favicon to React Newsletter's logo"
        className="link"
        onClick={() =>
          setFavicon("https://reactnewsletter.com/favicon/favicon-32x32.png")
        }
      >
        React Newsletter
      </button>

      <button
        title="Set the favicon to uidotdev's logo"
        className="link"
        onClick={() => setFavicon("https://ui.dev/favicon/favicon-32x32.png")}
      >
        ui.dev
      </button>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useFetch.mdx
================================================
---
name: useFetch
experimental: true
rank: 11
tagline: Fetch data with accurate states, caching, and no stale responses using useFetch.
sandboxId: usefetch-yky89o
previewHeight: 670px
relatedHooks:
  - useintersectionobserver
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useFetch React hook is useful for handling data fetching and state
  management in React components. It allows you to easily fetch data from a
  specified URL using the fetch API and provides a consistent pattern for
  handling loading, success, and error states. It also incorporates an internal
  caching mechanism to store and retrieve previously fetched data, improving
  performance by reducing redundant network requests. Additionally, it includes
  a mechanism to ignore stale responses if the component unmounts or if a new
  request is made before the previous one completes.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name    | Type   | Description                                                                              |
  | ------- | ------ | ---------------------------------------------------------------------------------------- |
  | url     | string | The URL to fetch data from.                                                              |
  | options | object | (Optional) Additional options for the fetch request, such as headers or request methods. |
  </div>

  ### Return Values

  <div class="table-container">
  | Name        | Type               | Description                                                                    |
  | ----------- | ------------------ | ------------------------------------------------------------------------------ |
  | state       | object             | The state object containing the fetched data and error status.                 |
  | state.error | error \| undefined | The error object if an error occurred during the fetch, otherwise `undefined`. |
  | state.data  | any \| undefined   | The fetched data if the fetch was successful, otherwise `undefined`.           |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useFetch } from "@uidotdev/usehooks";
import Card from "./Card";

export default function App() {
  const [count, setCount] = React.useState(1);

  const { error, data } = useFetch(
    `https://pokeapi.co/api/v2/pokemon/${count}`
  );

  return (
    <section>
      <h1>useFetch</h1>
      <button
        disabled={count < 2}
        className="link"
        onClick={() => setCount((c) => c - 1)}
      >
        Prev
      </button>
      <button className="link" onClick={() => setCount((c) => c + 1)}>
        Next
      </button>
      <Card loading={!data} error={error} data={data} />
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useGeolocation.mdx
================================================
---
name: useGeolocation
rank: 36
tagline: Access and monitor a user's geolocation (after they give permission) with useGeolocation.
sandboxId: usegeolocation-3f1d0f
previewHeight: 900px
relatedHooks:
  - usemap
  - useset
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useGeolocation hook is useful for accessing and monitoring the user's
  geolocation (after they give permission) in a React application. By utilizing
  the hook, developers can easily retrieve the user's current position,
  including latitude, longitude, altitude, accuracy, heading, speed, and
  timestamp.
</HookDescription>

<div class="reference">
  ### Parameters

<div class="table-container">
| Name    | Type   | Description |
| ------- | ------ | ----------- |
| options | object | This is an optional configuration object provided when calling `useGeolocation`. It is used when calling `navigator.geolocation.getCurrentPosition()` and `navigator.geolocation.watchPosition()`. Some of the attributes it accepts are `enableHighAccuracy`, `timeout`, and `maximumAge`. |
</div>

### Return Values

The hook returns an object containing the following properties:

  <div class="table-container">
  | Name              | Type    | Description |
  | ----------------- | ------- | ----------- |
  | loading           | boolean | A boolean indicating if the geolocation data is currently being fetched. |
  | accuracy          | number  | The accuracy of the latitude and longitude properties in meters. |
  | altitude          | number  | The altitude in meters above the mean sea level. |
  | altitudeAccuracy  | number  | The accuracy of altitude in meters. |
  | heading           | number  | The direction in which the device is traveling. This value, specified in degrees, indicates how far off from heading true north the device is. |
  | latitude          | number  | The latitude in decimal degrees. |
  | longitude         | number  | The longitude in decimal degrees. |
  | speed             | number  | The current ground speed of the device, specified in meters per second. |
  | timestamp         | number  | The timestamp at which the geolocation data was retrieved. |
  | error             | object  | An error object, if an error occurred while retrieving the geolocation data. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useGeolocation } from "@uidotdev/usehooks";
import Demo from "./Demo";

export default function App() {
  return (
    <section>
      <h1>useGeolocation</h1>
      <Location />
    </section>
  );
}

function Location() {
  const state = useGeolocation();

  if (state.loading) {
    return <p>loading... (you may need to enable permissions)</p>;
  }

  if (state.error) {
    return <p>Enable permissions to access your location data</p>;
  }

  return <Demo state={state} />;
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useHistoryState.mdx
================================================
---
name: useHistoryState
rank: 35
tagline: Add undo / redo functionality with useHistoryState.
sandboxId: usehistorystate-tubwyj
previewHeight: 400px
relatedHooks:
  - usemap
  - useset
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useHistoryState hook is useful for managing state with undo and redo
  capabilities in React components. The hook offers functions like undo, redo,
  set, and clear to interact with the state as well as other state related
  values like canUndo and canRedo.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name           | Type   | Description                                        |
  | -------------- | ------ | -------------------------------------------------- |
  | initialPresent | object | (Optional) The initial state value. Default: `{}`. |
  </div>

  ### Return Value

  The `useHistoryState` hook returns an object with the following properties and functions:

  <div class="table-container">
  | Name    | Type     | Description                                                |
  | ------- | -------- | ---------------------------------------------------------- |
  | state   | any      | The current state value.                                   |
  | set     | function | A function to set the state value.                         |
  | undo    | function | A function to undo the previous state.                     |
  | redo    | function | A function to redo the next state.                         |
  | clear   | function | A function to clear the state history and reset the state. |
  | canUndo | boolean  | Indicates whether an undo action is available.             |
  | canRedo | boolean  | Indicates whether a redo action is available.              |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import Form from "./Form";
import { useHistoryState } from "@uidotdev/usehooks";

export default function App() {
  const { state, set, undo, redo, clear, canUndo, canRedo } = useHistoryState({
    items: [],
  });

  const addTodo = (val) => {
    set({
      ...state,
      items: state.items.concat({ id: crypto.randomUUID(), name: val }),
    });
  };

  const removeTodo = (id) => {
    set({
      ...state,
      items: state.items.filter((item) => item.id !== id),
    });
  };

  return (
    <section>
      <header>
        <h1>useHistoryState</h1>
        <div>
          <button disabled={!canUndo} className="link" onClick={undo}>
            Undo
          </button>
          <button disabled={!canRedo} className="link" onClick={redo}>
            Redo
          </button>

          <button
            disabled={!state.items.length}
            className="link"
            onClick={clear}
          >
            Clear
          </button>
        </div>
        <Form addItem={addTodo} />
      </header>

      <ul>
        {state.items.map((item, index) => {
          return (
            <li key={index}>
              <span>{item.name}</span>
              <button className="link" onClick={() => removeTodo(item.id)}>
                Delete
              </button>
            </li>
          );
        })}
      </ul>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useHover.mdx
================================================
---
name: useHover
rank: 24
tagline: Track whether an element is being hovered over with useHover.
sandboxId: usehover-9lxxmo
previewHeight: 420px
relatedHooks:
  - uselockbodyscroll
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useHover hook allows you to track whether an element is being hovered over
  or not. The hook returns a reference to attach to the target element and the
  current hovering status, enabling you to apply conditional rendering or
  perform any desired actions based on the hover state.
</HookDescription>

<div class="reference">
  ### Return Value

  The `useHover` hook returns an array with the following elements:

  <div class="table-container">
  | Index | Type    | Description                                                                |
  | ----- | ------- | -------------------------------------------------------------------------- |
  | 0     | ref     | A ref object that can be attached to the element intended to be hovered.   |
  | 1     | boolean | A boolean value indicating whether the element is currently being hovered. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useHover } from "@uidotdev/usehooks";

function getRandomColor() {
  const colors = ["green", "blue", "purple", "red", "pink"];
  return colors[Math.floor(Math.random() * colors.length)];
}

export default function App() {
  const [ref, hovering] = useHover();

  const backgroundColor = hovering
    ? `var(--${getRandomColor()})`
    : "var(--charcoal)";

  return (
    <section>
      <h1>useHover</h1>
      <article ref={ref} style={{ backgroundColor }}>
        Hovering? {hovering ? "Yes" : "No"}
      </article>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useIdle.mdx
================================================
---
name: useIdle
rank: 33
tagline: Detect user inactivity with useIdle.
sandboxId: useidle-myg19f
previewHeight: 200px
relatedHooks:
  - usevisibilitychange
  - usebattery
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useIdle hook is a useful tool for detecting user inactivity within a web
  application. It tracks user interaction and determines if a specified duration
  of time has passed without any activity. This hook is particularly handy for
  implementing features like automatic logout, displaying notifications after a
  period of inactivity, or adjusting UI elements based on user engagement.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name | Type   | Description |
  | ---- | ------ | ----------- |
  | ms   | number | This is the duration of idle time (in milliseconds) after which the `idle` state will be set to `true`. The default value is `20 * 1000` (20 seconds). |
  </div>

  ### Return Values

  <div class="table-container">
  | Name | Type    | Description |
  | ---- | ------- | ----------- |
  | idle | boolean | A boolean indicating if the user is idle. It is `true` if the user has been idle for at least `ms` milliseconds. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useIdle } from "@uidotdev/usehooks";

export default function App() {
  const idle = useIdle(5000);
  return (
    <section>
      <h1>useIdle</h1>
      <div>
        <span className={idle ? "idle" : ""} />
        <label>Status: {idle ? "Idle" : "Active"}</label>
      </div>
      {idle ? <p>Time to move your mouse</p> : <p>Hold still and wait</p>}
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useIntersectionObserver.mdx
================================================
---
name: useIntersectionObserver
rank: 5
tagline: Track and manage the visibility of your DOM elements within the viewport with useIntersectionObserver.
sandboxId: useintersectionobserver-uxlxkn
previewHeight: 500px
relatedHooks:
  - usemeasure
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useIntersectionObserver hook is useful because it provides a
  straightforward, built-in method for tracking the visibility and position of a
  DOM element in relation to the viewport. By leveraging the Intersection
  Observer API, it can greatly optimize performance and provide efficient,
  real-time updates for lazy-loading, infinite scrolling, or other
  visibility-dependent elements. With customizable thresholds and root margins,
  developers have fine-grained control over when the hook triggers, improving
  the user experience by dynamically loading content only when necessary.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name       | Type    | Default | Description                                                                                                                                                                                                                                                |
  | ---------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | threshold  | number  | 1       | Either a single number or an array of numbers between 0 and 1, indicating at what percentage of the target’s visibility the observer’s callback should be executed.                                                                                        |
  | root       | element | null    | The Element that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null.                                                                                                              |
  | rootMargin | string  | "0%"    | Margin around the root. Can have values similar to the CSS margin property. The values can be percentages. This set of values serves to grow or shrink each side of the root element’s bounding box before computing intersections. Defaults to all zeros. |
  </div>

  ### Return Value

  <div class="table-container">
  | Name  | Type             | Description                                                                                                     |
  | ----- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
  | ref   | React ref object | A React reference that can be attached to a DOM element to observe.                                             |
  | entry | object           | An object containing information about the intersection. This object is similar to `IntersectionObserverEntry`. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useIntersectionObserver } from "@uidotdev/usehooks";
import demoData from "./demoData";

const Section = ({ imgUrl, caption, href }) => {
  const [ref, entry] = useIntersectionObserver({
    threshold: 0,
    root: null,
    rootMargin: "0px",
  });

  return (
    <section>
      <figure ref={ref}>
        {entry?.isIntersecting && (
          <>
            <img src={imgUrl} alt={caption} />
            <figcaption>
              <a href={href} alt={caption} target="_blank" rel="noreferrer">
                {caption}
              </a>
            </figcaption>
          </>
        )}
      </figure>
    </section>
  );
};

export default function App() {
  return (
    <main>
      <header>
        <h1>useIntersectionObserver</h1>
        <h6>
          (Memes from <a href="https://bytes.dev">bytes.dev</a>)
        </h6>
      </header>

      {demoData.map(({ imgUrl, href, caption }, index) => {
        return (
          <Section key={index} imgUrl={imgUrl} href={href} caption={caption} />
        );
      })}
    </main>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useInterval.mdx
================================================
---
name: useInterval
experimental: true
rank: 19
tagline: Schedule periodic actions like data polling or animations with useInterval.
sandboxId: useinterval-81c43g
previewHeight: 500px
relatedHooks:
  - useintervalwhen
  - usecontinuousretry
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useInterval hook provides a convenient way to create and manage intervals.
  The hook sets up an interval that repeatedly invokes the callback function at
  the specified interval. The interval is automatically cleared when the
  component unmounts or when the interval duration changes. This hook is useful
  for scenarios where you need to perform a certain action or update the
  component periodically, such as polling for data updates or implementing
  animations.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name | Type     | Description                                                     |
  | ---- | -------- | --------------------------------------------------------------- |
  | cb   | function | The callback function to be executed at the specified interval. |
  | ms   | number   | The interval duration in milliseconds.                          |
  </div>

  ### Return Value

  <div class="table-container">
  | Name          | Type     | Description                                              |
  | ------------- | -------- | -------------------------------------------------------- |
  | clearInterval | function | A function to clear the interval and stop the execution. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useInterval } from "@uidotdev/usehooks";

const colors = ["green", "blue", "purple", "red", "pink", "beige", "yellow"];

export default function App() {
  const [running, setIsRunning] = React.useState(true);
  const [index, setIndex] = React.useState(0);

  const clear = useInterval(() => {
    setIndex(index + 1);
  }, 1000);

  const handleStop = () => {
    clear();
    setIsRunning(false);
  };

  const color = colors[index % colors.length];
  return (
    <section>
      <h1>useInterval</h1>
      <button disabled={!running} className="link" onClick={handleStop}>
        {running ? "Stop" : "Stopped"}
      </button>
      <div style={{ backgroundColor: `var(--${color})` }} />
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useIntervalWhen.mdx
================================================
---
name: useIntervalWhen
experimental: true
rank: 18
tagline: Create dynamic timers that can be started, paused, or resumed with useIntervalWhen.
sandboxId: useintervalwhen-cp7e31
previewHeight: 400px
relatedHooks:
  - userandominterval
  - usecontinuousretry
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useIntervalWhen hook is useful for creating an interval timer that can be
  controlled based on certain conditions. It allows you to specify a callback
  function to be executed at a regular interval specified by the ms parameter.
  Additionally, you can choose whether the interval should start immediately or
  wait for a trigger by setting the startImmediately parameter. This hook is
  particularly handy when you need to create dynamic timers that can be started,
  paused, or resumed based on specific conditions.
</HookDescription>
<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name                     | Type     | Description                                                                                         |
  | ------------------------ | -------- | --------------------------------------------------------------------------------------------------- |
  | cb                       | function | The callback function to be executed at the specified interval when the condition is met.           |
  | options                  | object   | An object containing the following options.                                                         |
  | options.ms               | number   | The interval duration in milliseconds.                                                              |
  | options.when             | boolean  | The condition that determines whether the interval should be active (`true`) or paused (`false`).   |
  | options.startImmediately | boolean  | (Optional) Whether to start the interval immediately when the condition is met. Default is `false`. |
  </div>

  ### Return Value

  <div class="table-container">
  | Type     | Description                                               |
  | -------- | --------------------------------------------------------- |
  | function | A function to clear the interval and pause the execution. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useIntervalWhen } from "@uidotdev/usehooks";

export default function App() {
  const [count, setCount] = React.useState(0);
  const [when, setWhen] = React.useState(false);

  useIntervalWhen(
    () => {
      setCount((c) => c + 0.1);
    },
    { ms: 100, when, startImmediately: true }
  );

  return (
    <section>
      <h1>useIntervalWhen</h1>
      <button title="Click to toggle the timer" onClick={() => setWhen(!when)}>
        {count.toLocaleString("en-US", {
          maximumFractionDigits: 2,
          minimumFractionDigits: 2,
        })}
        <span className="btn link">{when ? "stop" : "start"}</span>
      </button>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useIsClient.mdx
================================================
---
name: useIsClient
rank: 22
tagline: Determine whether the code is running on the client-side or server-side with useIsClient.
sandboxId: useisclient-4edyux
previewHeight: 240px
relatedHooks:
  - userenderinfo
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  useIsClient is useful for determining if it's safe to run certain client-only hooks like useMediaQuery or useLocalStorage. It returns a boolean determining if React's useEffect hook has finished running (which means the app is being rendered on the client and it's safe to use browser specific APIs).
</HookDescription>

<div class="reference">
  ### Parameters

  The `useIsClient` hook does not accept any parameters.

  ### Return Value

  <div class="table-container">
  | Name     | Type    | Description                                                        |
  | -------- | ------- | ------------------------------------------------------------------ |
  | isClient | boolean | `true` if running in a client-side environment, `false` otherwise. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useIsClient } from "@uidotdev/usehooks";

export default function App() {
  const isClient = useIsClient();

  return (
    <section>
      <h1>useIsClient</h1>
      <h6>Is Client? </h6>
      <p>{isClient ? "If you can see this ... you already know" : "No"}</p>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useIsFirstRender.mdx
================================================
---
name: useIsFirstRender
rank: 41
tagline: Differentiate between the first and subsequent renders with useIsFirstRender.
sandboxId: useisfirstrender-rny2tm
previewHeight: 300px
relatedHooks:
  - userendercount
  - uselogger
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useIsFirstRender hook is a useful for determining whether the current
  render is the first render of a component. This hook is particularly valuable
  when you want to conditionally execute certain logic or render specific
  components only on the initial render, providing an efficient way to
  differentiate between the first and subsequent renders.
</HookDescription>

<div class="reference">
  ### Return Value

  <div class="table-container">
  | Name          | Type    | Description                                      |
  | ------------- | ------- | ------------------------------------------------ |
  | isFirstRender | boolean | `true` for the first render, `false` for others. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useIsFirstRender } from "@uidotdev/usehooks";

export default function App() {
  const isFirstRender = useIsFirstRender();
  const [count, rerender] = React.useReducer((x) => x + 1, 1);

  return (
    <section>
      <h1>useIsFirstRender</h1>
      <h2>First Render? {isFirstRender ? "Yes" : "No"}</h2>
      <button className="primary" onClick={rerender}>
        re-render
      </button>
      <p>Render Count: {count}</p>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useKeyPress.mdx
================================================
---
name: useKeyPress
experimental: true
rank: 27
tagline: Detect and perform actions on key press events with useKeyPress.
sandboxId: usekeypress-7vgzwf
previewHeight: 350px
relatedHooks:
  - useeventlistener
  - usehover
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useKeyPress hook is a useful for detecting key presses and performing
  specific actions based on the pressed key. By calling useKeyPress with the
  desired key and callback function, the hook sets up an event listener that
  triggers the callback when the specified key is pressed.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name                 | Type                  | Description                                                                                            |
  | -------------------- | --------------------- | ------------------------------------------------------------------------------------------------------ |
  | key                  | string                | The key to listen for.                                                                                 |
  | cb                   | function              | The callback function to be executed when the key is pressed.                                          |
  | options              | object                | (Optional) Additional options for the key press event.                                                 |
  | options.event        | string                | (Optional) The event type to listen for. Default is `"keydown"`.                                       |
  | options.target       | DOM element or window | (Optional) The target element or `window` object to attach the event listener to. Default is `window`. |
  | options.eventOptions | object                | (Optional) Additional options for the event listener.                                                  |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useKeyPress } from "@uidotdev/usehooks";

export default function App() {
  const [activeKey, setActiveKey] = React.useState("");

  useKeyPress("ArrowRight", onKeyPress);
  useKeyPress("ArrowLeft", onKeyPress);
  useKeyPress("ArrowUp", onKeyPress);
  useKeyPress("ArrowDown", onKeyPress);

  function onKeyPress(e) {
    e.preventDefault();
    setActiveKey(e.key);
    setTimeout(() => {
      setActiveKey("");
    }, 600);
  }

  return (
    <section>
      <h1>useKeyPress</h1>
      <p>Press one of the arrow keys on your keyboard</p>
      <article>
        <button className={activeKey === "ArrowUp" ? "pressed" : ""}>
          <span>&uarr;</span>
        </button>
        <button className={activeKey === "ArrowLeft" ? "pressed" : ""}>
          <span>&larr;</span>
        </button>
        <button className={activeKey === "ArrowDown" ? "pressed" : ""}>
          <span>&darr;</span>
        </button>
        <button className={activeKey === "ArrowRight" ? "pressed" : ""}>
          <span>&rarr;</span>
        </button>
      </article>
      {Boolean(activeKey) && <label>{activeKey} was pressed</label>}
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useList.mdx
================================================
---
name: useList
rank: 48
tagline: Manage and manipulate lists with useList.
sandboxId: uselist-5mmpw2
previewHeight: 550px
relatedHooks:
  - usehistorystate
  - useprevious
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useList hook is useful for managing and manipulating lists in a React
  component. It encapsulates the list state and provides a set of convenient
  methods to modify the list. By using this hook, you can easily initialize a
  list with a default value and access various methods to add, remove, update,
  or clear elements within the list.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name        | Type  | Description                                              |
  | ----------- | ----- | -------------------------------------------------------- |
  | defaultList | array | The initial list of elements. Default is an empty array. |
  </div>

  ### Return Value

  The `useList` hook returns an array with two elements:

  <div class="table-container">
  | Name           | Parameters                  | Description                                                       |
  | -------------- | --------------------------- | ----------------------------------------------------------------- |
  | `[0]`          |                             | The current list of elements.                                     |
  | `[1].set`      | l: array                    | Replaces the entire list with a new array `l`.                    |
  | `[1].push`     | element: any                | Appends the `element` to the end of the list.                     |
  | `[1].removeAt` | index: number               | Removes the element at the specified `index` from the list.       |
  | `[1].insertAt` | index: number, element: any | Inserts the `element` at the specified `index` in the list.       |
  | `[1].updateAt` | index: number, element: any | Replaces the element at the specified `index` with the `element`. |
  | `[1].clear`    |                             | Removes all elements from the list.                               |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import { useList } from "@uidotdev/usehooks";
import ListDemo from "./ListDemo";

export default function App() {
  const [list, { set, push, removeAt, insertAt, updateAt, clear }] = useList([
    "First",
    "Second",
    "Third",
  ]);

  return (
    <section>
      <header>
        <h1>UseList</h1>
        <button
          disabled={list.length < 1}
          className="link"
          onClick={() => insertAt(1, "woo")}
        >
          Insert After First
        </button>
        <button
          disabled={list.length < 2}
          className="link"
          onClick={() => removeAt(1)}
        >
          Remove Second Item
        </button>
        <button className="link" onClick={() => set([1, 2, 3])}>
          Reset
        </button>
        <button className="link" onClick={() => clear()}>
          Clear
        </button>
      </header>
      <ListDemo
        list={list}
        updateAt={updateAt}
        push={push}
        removeAt={removeAt}
      />
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useLocalStorage.mdx
================================================
---
name: useLocalStorage
rank: 2
tagline: Store, retrieve, and synchronize data from the browser’s localStorage API with useLocalStorage
sandboxId: uselocalstorage-e6v6ey
previewHeight: 670px
relatedHooks:
  - usesessionstorage
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useLocalStorage hook provides a convenient way to synchronize the state of
  a component with the data stored in local storage. It automatically reads the
  initial value from local storage when the component mounts and updates the
  local storage whenever the state changes. Additionally, it listens for changes
  in local storage made by other tabs or windows and keeps the component’s state
  up to date.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name | Type | Description |
  |------|------|-------------|
  | key | string | The key used to access the local storage value. |
  | initialValue | any | The initial value to use if there is no item in the local storage with the provided key. |
  </div>

  ### Return Values

  <div class="table-container">
  | Name | Type | Description |
  |------|------|-------------|
  | localState | any | The current state of the value stored in local storage. |
  | handleSetState | function | A function to set the state of the value in the local storage. This function accepts a new value or a function that returns a new value. The value is also saved in the local storage under the provided key. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useLocalStorage } from "@uidotdev/usehooks";
import createDrawing from "./createDrawing";

export default function App() {
  const [drawing, saveDrawing] = useLocalStorage("drawing", null);
  const ref = React.useRef(null);
  React.useEffect(() => {
    createDrawing(ref.current, drawing, saveDrawing);
  }, [drawing, saveDrawing]);

  return (
    <section>
      <header>
        <h1>useLocalStorage</h1>

        <button className="link" onClick={() => window.location.reload()}>
          Reload Window
        </button>
        <button
          className="link"
          onClick={() => {
            window.localStorage.clear();
            window.location.reload();
          }}
        >
          Clear Local Storage
        </button>
      </header>
      <figure>
        <canvas ref={ref} width={800} height={800} />
        <figcaption>(draw something)</figcaption>
      </figure>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useLockBodyScroll.mdx
================================================
---
name: useLockBodyScroll
rank: 20
tagline: Temporarily disable scrolling on the document body with useLockBodyScroll.
sandboxId: uselockbodyscroll-on17pi
previewHeight: 500px
relatedHooks:
  - usescript
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useLockBodyScroll hook temporarily disables scrolling on the document
  body. This can be beneficial in scenarios where you want to restrict scrolling
  while displaying a modal, a dropdown menu, or any other component that
  requires the user’s focus. Once the component using this hook is unmounted or
  no longer needed, the hook returns a cleanup function that restores the
  original overflow style, ensuring that the scroll behavior is reverted to its
  previous state.
</HookDescription>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useLockBodyScroll } from "@uidotdev/usehooks";
import { closeIcon } from "./icons";
import DemoContent from "./DemoContent";

function Modal({ handleClose }) {
  useLockBodyScroll();

  return (
    <div>
      <dialog>
        <header>
          <button onClick={handleClose}>{closeIcon}</button>
          <h2>Modal</h2>
        </header>
        <section>
          <DemoContent />
        </section>
      </dialog>
    </div>
  );
}

export default function App() {
  const [isOpen, setIsOpen] = React.useState(false);
  return (
    <>
      {isOpen && <Modal handleClose={() => setIsOpen(false)} />}
      <main>
        <header>
          <h1>useLockBodyScroll</h1>
        </header>
        {["red", "blue", "green", "pink", "purple", "yellow"].map((color) => {
          return (
            <section
              style={{
                backgroundColor: `var(--${color})`,
                width: "100%",
                height: "50vh",
              }}
            />
          );
        })}
        <button className="primary" onClick={() => setIsOpen(true)}>
          openModal
        </button>
      </main>
    </>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useLogger.mdx
================================================
---
name: useLogger
experimental: true
rank: 39
tagline: Debug lifecycle events with useLogger.
sandboxId: uselogger-o2e03i
previewHeight: 500px
relatedHooks:
  - userendercount
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useLogger hook is useful for logging various lifecycle events in a React
  component. This custom hook accepts a name parameter and additional arguments,
  and it logs the lifecycle events (mounted, updated, and unmounted) along with
  the provided name and arguments. This useLogger hook can be employed to
  facilitate debugging, monitoring, or performance optimization by providing
  insights into when and how a component’s lifecycle events occur.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name    | Type   | Description                            |
  | ------- | ------ | -------------------------------------- |
  | name    | string | The name or identifier for the logger. |
  | ...rest | any    | Additional arguments to be logged.     |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useLogger } from "@uidotdev/usehooks";

function FirstChild(props) {
  useLogger(props.name, props);
  return (
    <li className={props.isActive ? "active" : ""}>
      <h5>{props.name}</h5>
      <p>{props.count}</p>
    </li>
  );
}

export default function App() {
  const [count, setCount] = React.useState(0);

  const handleClick = () => setCount(count + 1);

  return (
    <section>
      <h1>useLogger</h1>
      <h6>(Check the console)</h6>
      <button className="primary" onClick={handleClick}>
        Increment Count
      </button>
      <ul>
        {["First", "Second", "Third"].map((item, index) => {
          const isActive = count % 3 === index;
          return (
            <FirstChild
              key={index}
              name={item}
              isActive={isActive}
              count={count}
            />
          );
        })}
      </ul>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useLongPress.mdx
================================================
---
name: useLongPress
rank: 42
tagline: Enable precise control of long-press interactions for both touch and mouse events with useLongPress.
sandboxId: uselongpress-0fpxj6
previewHeight: 240px
relatedHooks:
  - usemeasure
  - usetoggle
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useLongPress hook enhances React applications by managing long-press
  interactions for both mouse and touch events, thereby ensuring a consistent
  user experience across devices. This hook not only broadens user interactivity
  but also allows for customization, providing developers the flexibility to
  adjust the long-press functionality according to their application needs.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name                 | Type     | Description |
  | -------------------- | -------- | ----------- |
  | callback             | function | This is the function to be executed when a long press event is detected. |
  | options              | object   | This is an optional configuration object provided when calling `useLongPress`. |
  | options.threshold    | number   | This is the time (in milliseconds) the user must press and hold to trigger a long press event. Default value is `400`. |
  | options.onStart      | function | This function is called when the user starts pressing. |
  | options.onFinish     | function | This function is called when a long press event finishes successfully (the user releases after the threshold). |
  | options.onCancel     | function | This function is called when a press event is cancelled (the user releases before the threshold). |
  </div>

  ### Return Values

  <div class="table-container">
  | Name         | Type     | Description |
  | ------------ | -------- | ----------- |
  | onMouseDown  | function | This is the mouse down event handler. |
  | onMouseUp    | function | This is the mouse up event handler. |
  | onMouseLeave | function | This is the mouse leave event handler. |
  | onTouchStart | function | This is the touch start event handler. |
  | onTouchEnd   | function | This is the touch end event handler. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useLongPress } from "@uidotdev/usehooks";
import { closeIcon } from "./icons";

export default function App() {
  const [isOpen, setIsOpen] = React.useState(false);
  const attrs = useLongPress(
    () => {
      setIsOpen(true);
    },
    {
      onStart: (event) => console.log("Press started"),
      onFinish: (event) => console.log("Press Finished"),
      onCancel: (event) => console.log("Press cancelled"),
      threshold: 500,
    }
  );

  return (
    <section>
      <h1>useLongPress</h1>
      <button {...attrs} className="primary">
        Press Me
      </button>
      {isOpen && (
        <dialog>
          <button onClick={() => setIsOpen(false)}>{closeIcon}</button>
          <h2>Modal</h2>
          <p>This is a modal triggered by a long press.</p>
        </dialog>
      )}
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useMap.mdx
================================================
---
name: useMap
rank: 28
tagline: Synchronize and update state based on the Map data structure with useMap.
sandboxId: usemap-ev8nd1
previewHeight: 300px
relatedHooks:
  - usequeue
  - useset
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useMap hook provides a wrapper around the JavaScript Map object and allows
  you to easily update and synchronize the map state with the component’s
  rendering. By using this hook, you can add, delete, or clear entries in the
  map while ensuring that the component re-renders whenever these operations are
  performed.
</HookDescription>

<div class="reference">
  ### Return Value

  <div class="table-container">
  | Name | Type       | Description                                            |
  | ---- | ---------- | ------------------------------------------------------ |
  | map  | map object | An instance of the `Map` object with enhanced methods. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useMap } from "@uidotdev/usehooks";

export default function App() {
  const map = useMap([
    ["Jazz", 32],
    ["Suns", 50],
  ]);

  return (
    <section>
      <h1>useMap</h1>
      <table>
        <thead>
          <tr>
            <th colSpan={4}>Jazz vs Suns</th>
          </tr>
        </thead>
        <tbody>
          {Array.from(map.keys()).map((team) => {
            const score = map.get(team);
            return (
              <tr key={team}>
                <th style={{ width: "25%" }}>{team}</th>
                <td style={{ width: "50%" }}>{score}</td>
                <td style={{ width: "12.5%" }}>
                  <button
                    className="link"
                    onClick={() => {
                      map.set(team, score + 2);
                    }}
                  >
                    + 2
                  </button>
                </td>
                <td style={{ width: "12.5%" }}>
                  <button
                    className="link"
                    onClick={() => {
                      map.set(team, score + 3);
                    }}
                  >
                    + 3
                  </button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useMeasure.mdx
================================================
---
name: useMeasure
rank: 46
tagline: Effortlessly measure and track your component’s dimensions with useMeasure.
sandboxId: usemeasure-3kyegb
previewHeight: 300px
relatedHooks:
  - usepreferredlanguage
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useMeasure hook provides a convenient and efficient way to monitor and
  respond to changes in the size of a React component. This custom hook uses the
  ResizeObserver API to actively track changes in the component’s dimensions,
  such as width and height, and keeps them available as state. The returned ref
  is used on the element whose dimensions you want to measure, making it a
  valuable tool for responsive design and dynamic layout adjustments.
</HookDescription>

<div class="reference">
  ### Return Value

  <div class="table-container">
  | Name | Type | Description |
  | --- | --- | --- |
  | ref | React ref object | A React reference that can be attached to a DOM element to observe. |
  | rect | object | An object containing the width and height of the observed element. |
  </div>

  ### rect Object Properties

  <div class="table-container">
  | Property | Type | Description |
  | --- | --- | --- |
  | width | number | Width of the observed element. |
  | height | number | Height of the observed element. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useMeasure } from "@uidotdev/usehooks";

function Measure({ type = "horizontal" }) {
  const [ref, { width, height }] = useMeasure();

  return (
    <figure>
      <figcaption>
        <span>{type}</span>
      </figcaption>
      <article
        ref={ref}
        className={type}
        style={{
          resize: type
        }}
      >
        {type === "horizontal" ? (
          <label>width: {Math.floor(width)}</label>
        ) : (
          <label>height: {Math.floor(height)}</label>
        )}
      </article>
    </figure>
  );
}

export default function App() {
  return (
    <section>
      <h1>useMeasure</h1>
      <p>(Resize the rulers)</p>
      <Measure />
    </section>
  );
}

```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useMediaQuery.mdx
================================================
---
name: useMediaQuery
rank: 7
tagline: Subscribe and respond to media query changes with useMediaQuery.
sandboxId: usemediaquery-qu6bcp
previewHeight: 400px
relatedHooks:
  - useintersectionobserver
  - usemeasure
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useMediaQuery hook leverages the window.matchMedia API to subscribe to CSS
  media query changes, thereby providing real-time responsiveness to dynamic
  changes in the viewport or screen orientation. It allows the component to
  rerender whenever the media query’s result changes. It throws an error if
  attempted to be used on the server-side (media queries only work in the
  browser).
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name  | Type   | Description                       |
  | ----- | ------ | --------------------------------- |
  | query | string | The media query to listen changes |
  </div>

  ### Return Value

  <div class="table-container">
  | Type    | Description                                                                                         |
  | ------- | --------------------------------------------------------------------------------------------------- |
  | boolean | Returns a boolean value indicating whether the media query matches the current state of the device. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useMediaQuery } from "@uidotdev/usehooks";
import { phone, tablet, laptop, desktop } from "./icons";

export default function App() {
  const isSmallDevice = useMediaQuery("only screen and (max-width : 768px)");
  const isMediumDevice = useMediaQuery(
    "only screen and (min-width : 769px) and (max-width : 992px)"
  );
  const isLargeDevice = useMediaQuery(
    "only screen and (min-width : 993px) and (max-width : 1200px)"
  );
  const isExtraLargeDevice = useMediaQuery(
    "only screen and (min-width : 1201px)"
  );

  return (
    <section>
      <h1>useMediaQuery</h1>
      Resize your browser windows to see changes.
      <article>
        <figure className={isSmallDevice ? "active" : ""}>
          {phone}
          <figcaption>Small</figcaption>
        </figure>
        <figure className={isMediumDevice ? "active" : ""}>
          {tablet}
          <figcaption>Medium</figcaption>
        </figure>
        <figure className={isLargeDevice ? "active" : ""}>
          {laptop}
          <figcaption>Large</figcaption>
        </figure>
        <figure className={isExtraLargeDevice ? "active" : ""}>
          {desktop}
          <figcaption>Extra Large</figcaption>
        </figure>
      </article>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useMouse.mdx
================================================
---
name: useMouse
rank: 50
tagline: Track and retrieve the position of the mouse cursor with useMouse.
sandboxId: usemouse-8yu3mi
previewHeight: 450px
relatedHooks:
  - usehover
  - useeventlistener
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useMouse hook is useful for tracking and retrieving the position of the
  mouse cursor within a component. By utilizing this hook, developers can easily
  access information such as the current coordinates of the mouse cursor (x and
  y), the position of the mouse cursor relative to an element within the
  component (elementX and elementY), and the position of that element on the
  page (elementPositionX and elementPositionY).
</HookDescription>

<div class="reference">
  ### Parameters

  None.

  ### Return Value

  The `useMouse` hook returns an array with two elements:

  <div class="table-container">
  | Name    | Type   | Description                                                                      |
  | ------- | ------ | -------------------------------------------------------------------------------- |
  | `state` | object | An object representing the current mouse position and element information.       |
  | `ref`   | object | A ref object that can be used to track the mouse position on a specific element. |
  </div>

  The `state` object has the following properties:

  <div class="table-container">
  | Name                     | Type   | Description                                                                             |
  | ------------------------ | ------ | --------------------------------------------------------------------------------------- |
  | `state.x`                | number | The current horizontal position of the mouse relative to the page.                      |
  | `state.y`                | number | The current vertical position of the mouse relative to the page.                        |
  | `state.elementX`         | number | The current horizontal position of the mouse relative to the element’s top-left corner. |
  | `state.elementY`         | number | The current vertical position of the mouse relative to the element’s top-left corner.   |
  | `state.elementPositionX` | number | The current horizontal position of the element relative to the page.                    |
  | `state.elementPositionY` | number | The current vertical position of the element relative to the page.                      |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useMouse } from "@uidotdev/usehooks";
import Demo from "./Demo";

export default function App() {
  const [mouse, ref] = useMouse();

  const xIntersecting = mouse.elementX > 0 && mouse.elementX < 300;
  const yIntersecting = mouse.elementY > 0 && mouse.elementY < 300;
  const isIntersecting = xIntersecting && yIntersecting;

  return (
    <section>
      <h1>useMouse</h1>
      <article
        ref={ref}
        style={{ width: "300px", height: "300px" }}
        className={isIntersecting ? "active" : ""}
      >
        Use a ref to add coords relative to the element
      </article>
      <Demo {...mouse} />
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useNetworkState.mdx
================================================
---
name: useNetworkState
rank: 6
tagline: Monitor and adapt to network conditions seamlessly with useNetworkState.
sandboxId: usenetworkstate-k4t3qt
previewHeight: 400px
relatedHooks:
  - usepreferredlanguage
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useNetworkState React hook delivers real-time insights about the network
  status in your application, helping to adapt app behavior to varying
  connectivity conditions. It offers up-to-date snapshots of the network state,
  such as online/offline status, connection speed, and type. Through its use,
  you can respond effectively to network changes, fostering a seamless user
  experience even under unstable or varying connectivity. Please note, this hook
  is intended for client-side use only.
</HookDescription>

<div class="reference">
  ### Return Value

  The `useNetworkState` hook returns an object representing the current network state with the following properties:

  <div class="table-container">
  | Name          | Type    | Description                                                                                                                                                                                          |
  | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | online        | boolean | Indicates whether the browser is online or offline.                                                                                                                                                  |
  | downlink      | number  | The effective bandwidth estimate in megabits per second, rounded to the nearest multiple of 25 kilobits per seconds.                                                                                 |
  | downlinkMax   | number  | The maximum downlink speed, in megabits per second (Mbps), for the underlying connection technology.                                                                                                 |
  | effectiveType | string  | The effective type of the connection for general web browsing purposes (either 'slow-2g', '2g', '3g', or '4g').                                                                                      |
  | rtt           | number  | The estimated round-trip latency of the connection, in milliseconds.                                                                                                                                 |
  | saveData      | boolean | Whether the user has requested a reduced data usage mode from the user agent.                                                                                                                        |
  | type          | string  | The type of connection a device is using to communicate with the network. It could be one of the following values: 'bluetooth', 'cellular', 'ethernet', 'none', 'wifi', 'wimax', 'other', 'unknown'. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useNetworkState } from "@uidotdev/usehooks";

export default function App() {
  const network = useNetworkState();

  return (
    <section>
      <h1>useNetworkState</h1>
      <table>
        <tbody>
          {Object.keys(network).map((key) => {
            return (
              <tr key={key} className={key}>
                <th>{key}</th>
                <td>{`${network[key]}`}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useObjectState.mdx
================================================
---
name: useObjectState
rank: 38
tagline: Manage complex state objects with useObjectState.
sandboxId: useobjectstate-75lmg2
previewHeight: 350px
relatedHooks:
  - usevisibilitychange
  - useset
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescription name={frontmatter.name}>
  The useObjectState hook is useful for managing complex state objects. It
  provides a convenient way to initialize and update state values using a single
  hook. By using this hook, you can easily create and maintain state objects
  with initial values, and then update them through a flexible "handleUpdate"
  function. This function accepts either a callback function or an object,
  allowing you to merge new values into the existing state object.
</HookDescription>

<div class="reference">
  ### Parameters

  <div class="table-container">
  | Name         | Type   | Description                         |
  | ------------ | ------ | ----------------------------------- |
  | initialValue | object | (Optional) The initial state value. |
  </div>

  ### Return Value

  The `useObjectState` hook returns an array with the following elements:

  <div class="table-container">
  | Index | Type     | Description                            |
  | ----- | -------- | -------------------------------------- |
  | 0     | object   | The current state object.              |
  | 1     | function | A function to update the state object. |
  </div>
</div>

<CodePreview
  sandboxId={frontmatter.sandboxId}
  previewHeight={frontmatter.previewHeight}
/>

<StaticCodeContainer>

```jsx
import * as React from "react";
import { useObjectState } from "@uidotdev/usehooks";

const initialState = {
  team: "Utah Jazz",
  wins: 2138,
  losses: 1789,
  championships: 0,
};

export default function App() {
  const [stats, setStats] = useObjectState(initialState);

  const addWin = () => {
    setStats((s) => ({
      wins: s.wins + 1,
    }));
  };

  const addLoss = () => {
    setStats((s) => ({
      losses: s.losses + 1,
    }));
  };

  const reset = () => {
    setStats(initialState);
  };

  return (
    <section>
      <h1>useObjectState</h1>

      <button className="link" onClick={addWin}>
        Add Win
      </button>
      <button className="link" onClick={addLoss}>
        Add Loss
      </button>

      <button className="link" onClick={() => alert("lol")}>
        Add Championship
      </button>
      <button className="link" onClick={reset}>
        Reset
      </button>

      <table>
        <thead>
          <tr>
            {Object.keys(stats).map((key) => {
              return <th>{key}</th>;
            })}
          </tr>
        </thead>
        <tbody>
          <tr>
            {Object.keys(stats).map((key) => {
              return <td>{`${stats[key]}`}</td>;
            })}
          </tr>
        </tbody>
      </table>
    </section>
  );
}
```

</StaticCodeContainer>


================================================
FILE: usehooks.com/src/content/hooks/useOrientation.mdx
================================================
---
name: useOrientation
rank: 8
tagline: Manage and respond to changes in device orientation with useOrientation.
sandboxId: useorientation-f04nej
previewHeight: 400px
relatedHooks:
  - usenetworkstate
  - usemediaquery
---

import CodePreview from "../../components/CodePreview.astro";
import HookDescription from "../../components/HookDescription.astro";
import StaticCodeContainer from "../../components/StaticCodeContainer.astro";

<HookDescr
Download .txt
gitextract_24ko9djh/

├── .gitignore
├── LICENSE
├── README.md
├── index.d.ts
├── index.js
├── package.json
├── tsconfig.json
└── usehooks.com/
    ├── .astro/
    │   └── types.d.ts
    ├── .eslintrc.json
    ├── .gitignore
    ├── .prettierrc
    ├── README.md
    ├── astro.config.mjs
    ├── generate-og-images.mjs
    ├── package.json
    ├── src/
    │   ├── components/
    │   │   ├── Button.astro
    │   │   ├── CodePreview.astro
    │   │   ├── CountdownTimer.tsx
    │   │   ├── HookDescription.astro
    │   │   ├── Install.astro
    │   │   ├── Logo.astro
    │   │   ├── LogoGithub.astro
    │   │   ├── QueryGGBanner.astro
    │   │   ├── RelatedHook.astro
    │   │   ├── StaticCodeContainer.astro
    │   │   ├── Svg.astro
    │   │   ├── codepreview/
    │   │   │   ├── CodePreview.tsx
    │   │   │   ├── CodeWrapper.tsx
    │   │   │   └── utils.ts
    │   │   └── search/
    │   │       ├── Callout.module.css
    │   │       ├── Callout.tsx
    │   │       ├── HookCard.module.css
    │   │       ├── HookCard.tsx
    │   │       ├── HookSearch.module.css
    │   │       ├── HookSearch.tsx
    │   │       ├── HookSort.module.css
    │   │       ├── HookSort.tsx
    │   │       ├── HooksList.module.css
    │   │       └── HooksList.tsx
    │   ├── content/
    │   │   ├── config.ts
    │   │   └── hooks/
    │   │       ├── useBattery.mdx
    │   │       ├── useClickAway.mdx
    │   │       ├── useContinuousRetry.mdx
    │   │       ├── useCopyToClipboard.mdx
    │   │       ├── useCountdown.mdx
    │   │       ├── useCounter.mdx
    │   │       ├── useDebounce.mdx
    │   │       ├── useDefault.mdx
    │   │       ├── useDocumentTitle.mdx
    │   │       ├── useEventListener.mdx
    │   │       ├── useFavicon.mdx
    │   │       ├── useFetch.mdx
    │   │       ├── useGeolocation.mdx
    │   │       ├── useHistoryState.mdx
    │   │       ├── useHover.mdx
    │   │       ├── useIdle.mdx
    │   │       ├── useIntersectionObserver.mdx
    │   │       ├── useInterval.mdx
    │   │       ├── useIntervalWhen.mdx
    │   │       ├── useIsClient.mdx
    │   │       ├── useIsFirstRender.mdx
    │   │       ├── useKeyPress.mdx
    │   │       ├── useList.mdx
    │   │       ├── useLocalStorage.mdx
    │   │       ├── useLockBodyScroll.mdx
    │   │       ├── useLogger.mdx
    │   │       ├── useLongPress.mdx
    │   │       ├── useMap.mdx
    │   │       ├── useMeasure.mdx
    │   │       ├── useMediaQuery.mdx
    │   │       ├── useMouse.mdx
    │   │       ├── useNetworkState.mdx
    │   │       ├── useObjectState.mdx
    │   │       ├── useOrientation.mdx
    │   │       ├── usePageLeave.mdx
    │   │       ├── usePreferredLanguage.mdx
    │   │       ├── usePrevious.mdx
    │   │       ├── useQueue.mdx
    │   │       ├── useRandomInterval.mdx
    │   │       ├── useRenderCount.mdx
    │   │       ├── useRenderInfo.mdx
    │   │       ├── useScript.mdx
    │   │       ├── useSessionStorage.mdx
    │   │       ├── useSet.mdx
    │   │       ├── useThrottle.mdx
    │   │       ├── useTimeout.mdx
    │   │       ├── useToggle.mdx
    │   │       ├── useVisibilityChange.mdx
    │   │       ├── useWindowScroll.mdx
    │   │       └── useWindowSize.mdx
    │   ├── env.d.ts
    │   ├── layouts/
    │   │   └── Layout.astro
    │   ├── pages/
    │   │   ├── 404.astro
    │   │   ├── [hook].astro
    │   │   └── index.astro
    │   ├── sections/
    │   │   ├── Footer.astro
    │   │   ├── HomeHero.astro
    │   │   ├── NavInternal.astro
    │   │   └── NavMain.astro
    │   └── styles/
    │       └── globals.css
    ├── tailwind.config.cjs
    ├── theme.json
    ├── tsconfig.json
    └── vercel.json
Download .txt
SYMBOL INDEX (95 symbols across 13 files)

FILE: index.d.ts
  type BatteryManager (line 3) | type BatteryManager = {
  type GeolocationState (line 12) | type GeolocationState = {
  type HistoryState (line 25) | type HistoryState<T> = {
  type LongPressOptions (line 35) | type LongPressOptions = {
  type LongPressFns (line 42) | type LongPressFns = {
  type MousePosition (line 50) | type MousePosition = {
  type NetworkState (line 59) | type NetworkState = {
  type CustomList (line 69) | type CustomList<T> = {
  type CustomQueue (line 78) | type CustomQueue<T> = {
  type RenderInfo (line 88) | type RenderInfo = {
  type SpeechOptions (line 95) | type SpeechOptions = {
  type SpeechState (line 106) | type SpeechState = {

FILE: index.js
  function isShallowEqual (line 3) | function isShallowEqual(object1, object2) {
  function isTouchEvent (line 20) | function isTouchEvent({ nativeEvent }) {
  function isMouseEvent (line 26) | function isMouseEvent(event) {
  function throttle (line 30) | function throttle(cb, ms) {
  function isPlainObject (line 41) | function isPlainObject(value) {
  function dispatchStorageEvent (line 45) | function dispatchStorageEvent(key, newValue) {
  function useBattery (line 49) | function useBattery() {
  function useClickAway (line 105) | function useClickAway(cb) {
  function oldSchoolCopy (line 133) | function oldSchoolCopy(text) {
  function useCopyToClipboard (line 142) | function useCopyToClipboard() {
  function useCounter (line 166) | function useCounter(startingValue = 0, options = {}) {
  function useDebounce (line 239) | function useDebounce(value, delay) {
  function useDefault (line 255) | function useDefault(initialValue, defaultValue) {
  function useDocumentTitle (line 265) | function useDocumentTitle(title) {
  function useFavicon (line 271) | function useFavicon(url) {
  function useGeolocation (line 287) | function useGeolocation(options = {}) {
  function useHistoryState (line 389) | function useHistoryState(initialPresent = {}) {
  function useHover (line 426) | function useHover() {
  function useIdle (line 464) | function useIdle(ms = 1000 * 60) {
  function useIntersectionObserver (line 512) | function useIntersectionObserver(options = {}) {
  function useIsClient (line 543) | function useIsClient() {
  function useIsFirstRender (line 553) | function useIsFirstRender() {
  function useList (line 564) | function useList(defaultList = []) {
  function useLocalStorage (line 616) | function useLocalStorage(key, initialValue) {
  function useLockBodyScroll (line 654) | function useLockBodyScroll() {
  function useLongPress (line 664) | function useLongPress(callback, options = {}) {
  function useMap (line 728) | function useMap(initialState) {
  function useMeasure (line 753) | function useMeasure() {
  function useMediaQuery (line 785) | function useMediaQuery(query) {
  function useMouse (line 809) | function useMouse() {
  function useNetworkState (line 891) | function useNetworkState() {
  function useObjectState (line 923) | function useObjectState(initialValue) {
  function useOrientation (line 951) | function useOrientation() {
  function usePreferredLanguage (line 1009) | function usePreferredLanguage() {
  function usePrevious (line 1017) | function usePrevious(value) {
  function useQueue (line 1029) | function useQueue(initialValue = []) {
  function useRenderCount (line 1062) | function useRenderCount() {
  function useRenderInfo (line 1070) | function useRenderInfo(name = "Unknown") {
  function useScript (line 1097) | function useScript(src, options = {}) {
  function useSessionStorage (line 1177) | function useSessionStorage(key, initialValue) {
  function useSet (line 1215) | function useSet(values) {
  function useThrottle (line 1241) | function useThrottle(value, interval = 500) {
  function useToggle (line 1264) | function useToggle(initialValue) {
  function useVisibilityChange (line 1300) | function useVisibilityChange() {
  function useWindowScroll (line 1310) | function useWindowScroll() {
  function useWindowSize (line 1344) | function useWindowSize() {

FILE: usehooks.com/.astro/types.d.ts
  type Render (line 2) | interface Render {
  type Render (line 12) | interface Render {
  type CollectionEntry (line 23) | type CollectionEntry<C extends keyof AnyEntryMap> = AnyEntryMap[C][keyof...
  type ImageFunction (line 45) | type ImageFunction = () => import('astro/zod').ZodObject
  type BaseSchemaWithoutEffects (line 62) | type BaseSchemaWithoutEffects =
  type BaseSchema (line 71) | type BaseSchema =
  type SchemaContext (line 75) | type SchemaContext = { image: ImageFunction };
  type DataCollectionConfig (line 77) | type DataCollectionConfig<S extends BaseSchema> = {
  type ContentCollectionConfig (line 82) | type ContentCollectionConfig<S extends BaseSchema> = {
  type CollectionConfig (line 87) | type CollectionConfig<S> = ContentCollectionConfig<S> | DataCollectionCo...
  type AllValuesOf (line 93) | type AllValuesOf<T> = T extends any ? T[keyof T] : never;
  type ValidContentEntrySlug (line 94) | type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
  type ReturnTypeOrOriginal (line 195) | type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R...
  type InferEntrySchema (line 196) | type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod')...
  type ContentEntryMap (line 200) | type ContentEntryMap = {
  type DataEntryMap (line 556) | type DataEntryMap = {
  type AnyEntryMap (line 560) | type AnyEntryMap = ContentEntryMap & DataEntryMap;
  type ContentConfig (line 562) | type ContentConfig = typeof import("../src/content/config");

FILE: usehooks.com/generate-og-images.mjs
  function generateOgImage (line 39) | async function generateOgImage({ title, slug, description, logo }) {

FILE: usehooks.com/src/components/CountdownTimer.tsx
  type CountdownProps (line 3) | interface CountdownProps {
  type TimeLeft (line 7) | interface TimeLeft {
  function calculateTimeLeft (line 14) | function calculateTimeLeft(targetDate: string): TimeLeft {

FILE: usehooks.com/src/components/codepreview/CodePreview.tsx
  type PreviewProps (line 9) | type PreviewProps = {
  function CodePreview (line 14) | function CodePreview({

FILE: usehooks.com/src/components/codepreview/CodeWrapper.tsx
  function CodeWrapper (line 11) | function CodeWrapper({

FILE: usehooks.com/src/components/codepreview/utils.ts
  function getFiles (line 3) | async function getFiles({ id }: { id: string }) {
  function getChallengeConfig (line 16) | function getChallengeConfig(json: string) {
  function updateFiles (line 29) | function updateFiles(files: { [key: string]: SandpackFile }) {

FILE: usehooks.com/src/components/search/Callout.tsx
  type Props (line 3) | type Props = {
  function Callout (line 11) | function Callout({

FILE: usehooks.com/src/components/search/HookCard.tsx
  function HookCard (line 3) | function HookCard({

FILE: usehooks.com/src/components/search/HookSearch.tsx
  function HookSearch (line 3) | function HookSearch({

FILE: usehooks.com/src/components/search/HookSort.tsx
  function HookSort (line 3) | function HookSort({

FILE: usehooks.com/src/components/search/HooksList.tsx
  function insertAtIntervals (line 7) | function insertAtIntervals(arr, items) {
  function sortAlphabetical (line 22) | function sortAlphabetical(a, b) {
  function sortByPopularity (line 32) | function sortByPopularity(a, b) {
  function HooksList (line 47) | function HooksList({ hooks }) {
Condensed preview — 104 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (291K chars).
[
  {
    "path": ".gitignore",
    "chars": 839,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directo"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2023 ui.dev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 3025,
    "preview": "![useHooks](https://usehooks.com/meta/og.jpg)\n\n# useHooks\n\nA collection of modern, server-safe React hooks – from the [u"
  },
  {
    "path": "index.d.ts",
    "chars": 5991,
    "preview": "import * as React from \"react\";\n\nexport type BatteryManager = {\n  supported: boolean;\n  loading: boolean;\n  level: numbe"
  },
  {
    "path": "index.js",
    "chars": 32242,
    "preview": "import * as React from \"react\";\n\nfunction isShallowEqual(object1, object2) {\n  const keys1 = Object.keys(object1);\n  con"
  },
  {
    "path": "package.json",
    "chars": 649,
    "preview": "{\n  \"name\": \"@uidotdev/usehooks\",\n  \"version\": \"2.4.1\",\n  \"description\": \"A collection of modern, server-safe React hook"
  },
  {
    "path": "tsconfig.json",
    "chars": 284,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"esModuleInterop\": true,\n    \"forceConsiste"
  },
  {
    "path": "usehooks.com/.astro/types.d.ts",
    "chars": 15176,
    "preview": "declare module 'astro:content' {\n\tinterface Render {\n\t\t'.mdx': Promise<{\n\t\t\tContent: import('astro').MarkdownInstance<{}"
  },
  {
    "path": "usehooks.com/.eslintrc.json",
    "chars": 40,
    "preview": "{\n  \"extends\": \"next/core-web-vitals\"\n}\n"
  },
  {
    "path": "usehooks.com/.gitignore",
    "chars": 101,
    "preview": "node_modules\n/node_modules\n.DS_Store\n.env\n.env.*\n.next\ndist/*\n/dist\npublic/meta/*\n!public/meta/og.jpg"
  },
  {
    "path": "usehooks.com/.prettierrc",
    "chars": 2,
    "preview": "{}"
  },
  {
    "path": "usehooks.com/README.md",
    "chars": 1456,
    "preview": "# usehooks.com\n\n## 🚀 Project Structure\n\nInside this project, you'll see the following folders and files:\n\n```\n/\n├── publ"
  },
  {
    "path": "usehooks.com/astro.config.mjs",
    "chars": 998,
    "preview": "import { defineConfig } from \"astro/config\";\nimport react from \"@astrojs/react\";\nimport customTheme from \"./theme.json\";"
  },
  {
    "path": "usehooks.com/generate-og-images.mjs",
    "chars": 3613,
    "preview": "import { readdir, readFile, writeFile } from \"fs/promises\";\nimport { join, extname } from \"path\";\nimport matter from \"gr"
  },
  {
    "path": "usehooks.com/package.json",
    "chars": 856,
    "preview": "{\n  \"name\": \"reacthooks.com\",\n  \"type\": \"module\",\n  \"version\": \"2.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"as"
  },
  {
    "path": "usehooks.com/src/components/Button.astro",
    "chars": 1362,
    "preview": "---\nexport interface Props {\n\ttext: string;\n\tsize: string;\n\thref?: string;\n\ttype: \"link\" | \"button\" | \"submit\" | \"reset\""
  },
  {
    "path": "usehooks.com/src/components/CodePreview.astro",
    "chars": 367,
    "preview": "---\nimport CodePreview from \"./codepreview/CodePreview\";\nimport { getFiles } from \"./codepreview/utils\";\n\nconst { sandbo"
  },
  {
    "path": "usehooks.com/src/components/CountdownTimer.tsx",
    "chars": 2546,
    "preview": "import { Fragment, useEffect, useState } from \"react\";\n\ninterface CountdownProps {\n  targetDate: string; // YYYY-MM-DD f"
  },
  {
    "path": "usehooks.com/src/components/HookDescription.astro",
    "chars": 1689,
    "preview": "---\nimport Button from \"./Button.astro\";\nconst { name } = Astro.props;\n---\n\n<div class=\"description\">\n  <div>\n    <h2>De"
  },
  {
    "path": "usehooks.com/src/components/Install.astro",
    "chars": 2052,
    "preview": "---\nconst { text } = Astro.props;\nconst { class: className } = Astro.props;\n---\n\n<div class={`install ${className}`}>\n  "
  },
  {
    "path": "usehooks.com/src/components/Logo.astro",
    "chars": 405,
    "preview": "---\nconst { class: className } = Astro.props;\n---\n\n<a href=\"https://ui.dev\" class={`logo ui-logo image ${className}`}>\n\t"
  },
  {
    "path": "usehooks.com/src/components/LogoGithub.astro",
    "chars": 492,
    "preview": "---\nconst { class: className } = Astro.props;\n---\n\n<a href=\"https://github.com/uidotdev/usehooks\" class={`logo image git"
  },
  {
    "path": "usehooks.com/src/components/QueryGGBanner.astro",
    "chars": 3054,
    "preview": "---\nimport Button from \"./Button.astro\";\nimport CountdownTimer from \"./CountdownTimer\";\n---\n\n<aside class=\"reactgg-conta"
  },
  {
    "path": "usehooks.com/src/components/RelatedHook.astro",
    "chars": 261,
    "preview": "---\nimport { getEntry } from \"astro:content\";\nimport HookCard from \"./search/HookCard\";\n\nconst { slug } = Astro.props;\nc"
  },
  {
    "path": "usehooks.com/src/components/StaticCodeContainer.astro",
    "chars": 230,
    "preview": "---\nimport CodeWrapper from './codepreview/CodeWrapper'\n---\n<div>\n  <h2>Example:</h2>\n  <div class=\"min-h-[500px]\">\n    "
  },
  {
    "path": "usehooks.com/src/components/Svg.astro",
    "chars": 196,
    "preview": "---\nexport interface Props {\n  name: string;\n}\n\nconst { name } = Astro.props as Props;\nconst { default: innerHTML } = aw"
  },
  {
    "path": "usehooks.com/src/components/codepreview/CodePreview.tsx",
    "chars": 1150,
    "preview": "import {\n  SandpackProvider,\n  SandpackPreview,\n  SandpackStack,\n  SandpackFiles,\n} from \"@codesandbox/sandpack-react\";\n"
  },
  {
    "path": "usehooks.com/src/components/codepreview/CodeWrapper.tsx",
    "chars": 1730,
    "preview": "import { useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { set } from \"astro/zod\";\n\nconst spring"
  },
  {
    "path": "usehooks.com/src/components/codepreview/utils.ts",
    "chars": 1076,
    "preview": "import { SandpackFile } from \"@codesandbox/sandpack-react\";\n\nexport async function getFiles({ id }: { id: string }) {\n  "
  },
  {
    "path": "usehooks.com/src/components/search/Callout.module.css",
    "chars": 743,
    "preview": ".callout :global(a) {\n  height: 100%;\n  padding: var(--body-padding);\n  display: flex;\n  flex-direction: column;\n  align"
  },
  {
    "path": "usehooks.com/src/components/search/Callout.tsx",
    "chars": 758,
    "preview": "import styles from \"./Callout.module.css\";\n\ntype Props = {\n  image: string;\n  imageWidth: string;\n  imageHeight: string;"
  },
  {
    "path": "usehooks.com/src/components/search/HookCard.module.css",
    "chars": 731,
    "preview": ".hook > :global(a) {\n  height: 100%;\n  padding: var(--body-padding);\n  display: flex;\n  flex-direction: column;\n  gap: 0"
  },
  {
    "path": "usehooks.com/src/components/search/HookCard.tsx",
    "chars": 769,
    "preview": "import styles from './HookCard.module.css';\n\nexport default function HookCard({\n  name,\n  tagline,\n}: {\n  name: string;\n"
  },
  {
    "path": "usehooks.com/src/components/search/HookSearch.module.css",
    "chars": 852,
    "preview": ".hooks-search {\n  position: relative;\n  align-self: flex-start;\n}\n\n.input {\n  padding: 0.4rem 1rem;\n  padding-right: 2re"
  },
  {
    "path": "usehooks.com/src/components/search/HookSearch.tsx",
    "chars": 599,
    "preview": "import styles from \"./HookSearch.module.css\";\n\nexport default function HookSearch({\n  handleChange,\n  handleClear,\n  val"
  },
  {
    "path": "usehooks.com/src/components/search/HookSort.module.css",
    "chars": 392,
    "preview": ".toggle {\n  padding: 0.2rem 0.4rem;\n  border: var(--border-light);\n  border-radius: 0.3rem;\n  font-size: clamp(0.7rem, 2"
  },
  {
    "path": "usehooks.com/src/components/search/HookSort.tsx",
    "chars": 671,
    "preview": "import styles from \"./HookSort.module.css\";\n\nexport default function HookSort({\n  setSort,\n  value,\n}: {\n  setSort: (val"
  },
  {
    "path": "usehooks.com/src/components/search/HooksList.module.css",
    "chars": 297,
    "preview": ".hooks-grid {\n  max-width: 980px;\n  margin: 2rem auto;\n}\n\n.hooks-controls {\n  padding: 1rem var(--body-padding);\n  displ"
  },
  {
    "path": "usehooks.com/src/components/search/HooksList.tsx",
    "chars": 3049,
    "preview": "import { useState } from \"react\";\nimport Callout from \"./Callout\";\nimport HookCard from \"./HookCard\";\nimport HookSort fr"
  },
  {
    "path": "usehooks.com/src/content/config.ts",
    "chars": 530,
    "preview": "import { defineCollection, z, reference } from 'astro:content';\n\nexport const collections = {\n  hooks: defineCollection("
  },
  {
    "path": "usehooks.com/src/content/hooks/useBattery.mdx",
    "chars": 2708,
    "preview": "---\nname: useBattery\nrank: 32\ntagline: Track the battery status of a user’s device with useBattery.\nsandboxId: usebatter"
  },
  {
    "path": "usehooks.com/src/content/hooks/useClickAway.mdx",
    "chars": 2839,
    "preview": "---\nname: useClickAway\nrank: 47\ntagline: Detect clicks outside of specific component with useClickAway.\nsandboxId: usecl"
  },
  {
    "path": "usehooks.com/src/content/hooks/useContinuousRetry.mdx",
    "chars": 3119,
    "preview": "---\nname: useContinuousRetry\nexperimental: true\nrank: 12\ntagline: Automates retries of a callback function until it succ"
  },
  {
    "path": "usehooks.com/src/content/hooks/useCopyToClipboard.mdx",
    "chars": 2618,
    "preview": "---\nname: useCopyToClipboard\nrank: 31\ntagline: Copy text to the clipboard using useCopyToClipboard.\nsandboxId: usecopyto"
  },
  {
    "path": "usehooks.com/src/content/hooks/useCountdown.mdx",
    "chars": 3738,
    "preview": "---\nname: useCountdown\nexperimental: true\nrank: 21\ntagline: Create countdown timers using useCountdown.\nsandboxId: useco"
  },
  {
    "path": "usehooks.com/src/content/hooks/useCounter.mdx",
    "chars": 3241,
    "preview": "---\nname: useCounter\nrank: 49\ntagline: Manage a counter value with minimum and maximum limits with useCounter.\nsandboxId"
  },
  {
    "path": "usehooks.com/src/content/hooks/useDebounce.mdx",
    "chars": 3341,
    "preview": "---\nname: useDebounce\nrank: 1\ntagline: Delay the execution of function or state update with useDebounce.\nsandboxId: used"
  },
  {
    "path": "usehooks.com/src/content/hooks/useDefault.mdx",
    "chars": 2519,
    "preview": "---\nname: useDefault\nrank: 44\ntagline: Manage state with default values using useDefault.\nsandboxId: usedefault-5c0pyt\np"
  },
  {
    "path": "usehooks.com/src/content/hooks/useDocumentTitle.mdx",
    "chars": 1844,
    "preview": "---\nname: useDocumentTitle\nrank: 40\ntagline: Dynamically update the title of a webpage with useDocumentTitle.\nsandboxId:"
  },
  {
    "path": "usehooks.com/src/content/hooks/useEventListener.mdx",
    "chars": 3513,
    "preview": "---\nname: useEventListener\nexperimental: true\nrank: 26\ntagline: Listen for events on a target element with useEventListe"
  },
  {
    "path": "usehooks.com/src/content/hooks/useFavicon.mdx",
    "chars": 2280,
    "preview": "---\nname: useFavicon\nrank: 43\ntagline: Dynamically update the favicon with useFavicon.\nsandboxId: usefavicon-xckn4k\nprev"
  },
  {
    "path": "usehooks.com/src/content/hooks/useFetch.mdx",
    "chars": 3119,
    "preview": "---\nname: useFetch\nexperimental: true\nrank: 11\ntagline: Fetch data with accurate states, caching, and no stale responses"
  },
  {
    "path": "usehooks.com/src/content/hooks/useGeolocation.mdx",
    "chars": 3179,
    "preview": "---\nname: useGeolocation\nrank: 36\ntagline: Access and monitor a user's geolocation (after they give permission) with use"
  },
  {
    "path": "usehooks.com/src/content/hooks/useHistoryState.mdx",
    "chars": 3554,
    "preview": "---\nname: useHistoryState\nrank: 35\ntagline: Add undo / redo functionality with useHistoryState.\nsandboxId: usehistorysta"
  },
  {
    "path": "usehooks.com/src/content/hooks/useHover.mdx",
    "chars": 2049,
    "preview": "---\nname: useHover\nrank: 24\ntagline: Track whether an element is being hovered over with useHover.\nsandboxId: usehover-9"
  },
  {
    "path": "usehooks.com/src/content/hooks/useIdle.mdx",
    "chars": 2018,
    "preview": "---\nname: useIdle\nrank: 33\ntagline: Detect user inactivity with useIdle.\nsandboxId: useidle-myg19f\npreviewHeight: 200px\n"
  },
  {
    "path": "usehooks.com/src/content/hooks/useIntersectionObserver.mdx",
    "chars": 4550,
    "preview": "---\nname: useIntersectionObserver\nrank: 5\ntagline: Track and manage the visibility of your DOM elements within the viewp"
  },
  {
    "path": "usehooks.com/src/content/hooks/useInterval.mdx",
    "chars": 2682,
    "preview": "---\nname: useInterval\nexperimental: true\nrank: 19\ntagline: Schedule periodic actions like data polling or animations wit"
  },
  {
    "path": "usehooks.com/src/content/hooks/useIntervalWhen.mdx",
    "chars": 3310,
    "preview": "---\nname: useIntervalWhen\nexperimental: true\nrank: 18\ntagline: Create dynamic timers that can be started, paused, or res"
  },
  {
    "path": "usehooks.com/src/content/hooks/useIsClient.mdx",
    "chars": 1727,
    "preview": "---\nname: useIsClient\nrank: 22\ntagline: Determine whether the code is running on the client-side or server-side with use"
  },
  {
    "path": "usehooks.com/src/content/hooks/useIsFirstRender.mdx",
    "chars": 1844,
    "preview": "---\nname: useIsFirstRender\nrank: 41\ntagline: Differentiate between the first and subsequent renders with useIsFirstRende"
  },
  {
    "path": "usehooks.com/src/content/hooks/useKeyPress.mdx",
    "chars": 3470,
    "preview": "---\nname: useKeyPress\nexperimental: true\nrank: 27\ntagline: Detect and perform actions on key press events with useKeyPre"
  },
  {
    "path": "usehooks.com/src/content/hooks/useList.mdx",
    "chars": 3504,
    "preview": "---\nname: useList\nrank: 48\ntagline: Manage and manipulate lists with useList.\nsandboxId: uselist-5mmpw2\npreviewHeight: 5"
  },
  {
    "path": "usehooks.com/src/content/hooks/useLocalStorage.mdx",
    "chars": 2824,
    "preview": "---\nname: useLocalStorage\nrank: 2\ntagline: Store, retrieve, and synchronize data from the browser’s localStorage API wit"
  },
  {
    "path": "usehooks.com/src/content/hooks/useLockBodyScroll.mdx",
    "chars": 2304,
    "preview": "---\nname: useLockBodyScroll\nrank: 20\ntagline: Temporarily disable scrolling on the document body with useLockBodyScroll."
  },
  {
    "path": "usehooks.com/src/content/hooks/useLogger.mdx",
    "chars": 2357,
    "preview": "---\nname: useLogger\nexperimental: true\nrank: 39\ntagline: Debug lifecycle events with useLogger.\nsandboxId: uselogger-o2e"
  },
  {
    "path": "usehooks.com/src/content/hooks/useLongPress.mdx",
    "chars": 3392,
    "preview": "---\nname: useLongPress\nrank: 42\ntagline: Enable precise control of long-press interactions for both touch and mouse even"
  },
  {
    "path": "usehooks.com/src/content/hooks/useMap.mdx",
    "chars": 2638,
    "preview": "---\nname: useMap\nrank: 28\ntagline: Synchronize and update state based on the Map data structure with useMap.\nsandboxId: "
  },
  {
    "path": "usehooks.com/src/content/hooks/useMeasure.mdx",
    "chars": 2419,
    "preview": "---\nname: useMeasure\nrank: 46\ntagline: Effortlessly measure and track your component’s dimensions with useMeasure.\nsandb"
  },
  {
    "path": "usehooks.com/src/content/hooks/useMediaQuery.mdx",
    "chars": 3005,
    "preview": "---\nname: useMediaQuery\nrank: 7\ntagline: Subscribe and respond to media query changes with useMediaQuery.\nsandboxId: use"
  },
  {
    "path": "usehooks.com/src/content/hooks/useMouse.mdx",
    "chars": 3490,
    "preview": "---\nname: useMouse\nrank: 50\ntagline: Track and retrieve the position of the mouse cursor with useMouse.\nsandboxId: usemo"
  },
  {
    "path": "usehooks.com/src/content/hooks/useNetworkState.mdx",
    "chars": 3955,
    "preview": "---\nname: useNetworkState\nrank: 6\ntagline: Monitor and adapt to network conditions seamlessly with useNetworkState.\nsand"
  },
  {
    "path": "usehooks.com/src/content/hooks/useObjectState.mdx",
    "chars": 3069,
    "preview": "---\nname: useObjectState\nrank: 38\ntagline: Manage complex state objects with useObjectState.\nsandboxId: useobjectstate-7"
  },
  {
    "path": "usehooks.com/src/content/hooks/useOrientation.mdx",
    "chars": 2837,
    "preview": "---\nname: useOrientation\nrank: 8\ntagline: Manage and respond to changes in device orientation with useOrientation.\nsandb"
  },
  {
    "path": "usehooks.com/src/content/hooks/usePageLeave.mdx",
    "chars": 1936,
    "preview": "---\nname: usePageLeave\nexperimental: true\nrank: 37\ntagline: Track when a user navigates away from a webpage with usePage"
  },
  {
    "path": "usehooks.com/src/content/hooks/usePreferredLanguage.mdx",
    "chars": 1849,
    "preview": "---\nname: usePreferredLanguage\nrank: 10\ntagline: Adapt to user language preferences dynamically with usePreferredLanguag"
  },
  {
    "path": "usehooks.com/src/content/hooks/usePrevious.mdx",
    "chars": 2623,
    "preview": "---\nname: usePrevious\nrank: 4\ntagline: Track the previous value of a variable with usePrevious.\nsandboxId: useprevious-d"
  },
  {
    "path": "usehooks.com/src/content/hooks/useQueue.mdx",
    "chars": 3614,
    "preview": "---\nname: useQueue\nrank: 23\ntagline: Add, remove, and clear element from a queue data structure with useQueue.\nsandboxId"
  },
  {
    "path": "usehooks.com/src/content/hooks/useRandomInterval.mdx",
    "chars": 2812,
    "preview": "---\nname: useRandomInterval\nexperimental: true\nrank: 17\ntagline: Execute a callback function at a random interval with u"
  },
  {
    "path": "usehooks.com/src/content/hooks/useRenderCount.mdx",
    "chars": 1982,
    "preview": "---\nname: useRenderCount\nrank: 16\ntagline: Identify unnecessary re-renders and monitor update frequency with useRenderCo"
  },
  {
    "path": "usehooks.com/src/content/hooks/useRenderInfo.mdx",
    "chars": 2632,
    "preview": "---\nname: useRenderInfo\nrank: 15\ntagline: Debug renders and improve performance with useRenderInfo.\nsandboxId: userender"
  },
  {
    "path": "usehooks.com/src/content/hooks/useScript.mdx",
    "chars": 2845,
    "preview": "---\nname: useScript\nrank: 14\ntagline: Load and manage external JavaScript scripts with useScript.\nsandboxId: usescript-d"
  },
  {
    "path": "usehooks.com/src/content/hooks/useSessionStorage.mdx",
    "chars": 3999,
    "preview": "---\nname: useSessionStorage\nrank: 9\ntagline: Store, retrieve, and synchronize data from the browser’s session storage wi"
  },
  {
    "path": "usehooks.com/src/content/hooks/useSet.mdx",
    "chars": 3357,
    "preview": "---\nname: useSet\nrank: 30\ntagline: Synchronize and update state based on the Set data structure with useSet.\nsandboxId: "
  },
  {
    "path": "usehooks.com/src/content/hooks/useThrottle.mdx",
    "chars": 2333,
    "preview": "---\nname: useThrottle\nrank: 29\ntagline: Throttle computationally expensive operations with useThrottle.\nsandboxId: useth"
  },
  {
    "path": "usehooks.com/src/content/hooks/useTimeout.mdx",
    "chars": 3283,
    "preview": "---\nname: useTimeout\nexperimental: true\nrank: 25\ntagline: Create delayed actions or timed events using useTimeout.\nsandb"
  },
  {
    "path": "usehooks.com/src/content/hooks/useToggle.mdx",
    "chars": 2682,
    "preview": "---\nname: useToggle\nrank: 34\ntagline: A hook to toggle a boolean value with useToggle.\nsandboxId: usetoggle-tcj48q\nprevi"
  },
  {
    "path": "usehooks.com/src/content/hooks/useVisibilityChange.mdx",
    "chars": 2012,
    "preview": "---\nname: useVisibilityChange\nrank: 13\ntagline: Track document visibility and respond to changes with useVisibilityChang"
  },
  {
    "path": "usehooks.com/src/content/hooks/useWindowScroll.mdx",
    "chars": 2872,
    "preview": "---\nname: useWindowScroll\nrank: 45\ntagline: Track and manipulate the scroll position of a web page with useWindowScroll."
  },
  {
    "path": "usehooks.com/src/content/hooks/useWindowSize.mdx",
    "chars": 2304,
    "preview": "---\nname: useWindowSize\nrank: 3\ntagline: Track the dimensions of the browser window with useWindowSize.\nsandboxId: usewi"
  },
  {
    "path": "usehooks.com/src/env.d.ts",
    "chars": 85,
    "preview": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"astro/client\" />\n"
  },
  {
    "path": "usehooks.com/src/layouts/Layout.astro",
    "chars": 3144,
    "preview": "---\nimport \"../styles/globals.css\";\nexport interface Props {\n  title: string;\n  description: string;\n  ogImage?: URL;\n  "
  },
  {
    "path": "usehooks.com/src/pages/404.astro",
    "chars": 784,
    "preview": "---\nimport Layout from '../layouts/Layout.astro';\nimport NavInternal from '../sections/NavInternal.astro';\nimport Footer"
  },
  {
    "path": "usehooks.com/src/pages/[hook].astro",
    "chars": 5285,
    "preview": "---\nimport { CollectionEntry, getCollection } from \"astro:content\";\nimport Layout from \"../layouts/Layout.astro\";\nimport"
  },
  {
    "path": "usehooks.com/src/pages/index.astro",
    "chars": 586,
    "preview": "---\nimport { getCollection } from \"astro:content\";\nimport Layout from \"../layouts/Layout.astro\";\nimport NavMain from \".."
  },
  {
    "path": "usehooks.com/src/sections/Footer.astro",
    "chars": 1292,
    "preview": "---\n---\n\n<footer class=\"main-footer\">\n  <a href=\"/\" class=\"logo image\">\n    <img src=\"/img/logo-useHooks.svg\" width=\"546"
  },
  {
    "path": "usehooks.com/src/sections/HomeHero.astro",
    "chars": 2713,
    "preview": "---\nimport Install from '../components/Install.astro';\nimport { getCollection } from 'astro:content';\nconst hooksMarquee"
  },
  {
    "path": "usehooks.com/src/sections/NavInternal.astro",
    "chars": 731,
    "preview": "---\nimport LogoGithub from '../components/LogoGithub.astro';\n---\n\n<nav class=\"nav-internal\">\n\t<a href=\"/\" class=\"back im"
  },
  {
    "path": "usehooks.com/src/sections/NavMain.astro",
    "chars": 304,
    "preview": "---\nimport Logo from '../components/Logo.astro';\nimport LogoGithub from '../components/LogoGithub.astro';\n---\n\n<nav clas"
  },
  {
    "path": "usehooks.com/src/styles/globals.css",
    "chars": 5041,
    "preview": "*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\nhtml,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,"
  },
  {
    "path": "usehooks.com/tailwind.config.cjs",
    "chars": 678,
    "preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: [\"./src/**/*.{astro,html,js,jsx,md,mdx,svelte,"
  },
  {
    "path": "usehooks.com/theme.json",
    "chars": 10509,
    "preview": "{\n  \"name\": \"uidotdev\",\n  \"type\": \"dark\",\n  \"semanticHighlighting\": true,\n  \"semanticTokenColors\": {\n    \"enumMember\": {"
  },
  {
    "path": "usehooks.com/tsconfig.json",
    "chars": 201,
    "preview": "{\n  \"extends\": \"astro/tsconfigs/base\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"react\",\n  "
  },
  {
    "path": "usehooks.com/vercel.json",
    "chars": 2469,
    "preview": "{\n  \"trailingSlash\": false,\n  \"rewrites\": [\n    {\n      \"source\": \"/stats/js/script.js\",\n      \"destination\": \"https://p"
  }
]

About this extraction

This page contains the full source code of the uidotdev/usehooks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 104 files (262.1 KB), approximately 68.7k tokens, and a symbol index with 95 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!