master 2df0ee8dfad4 cached
10 files
20.9 KB
5.7k tokens
16 symbols
1 requests
Download .txt
Repository: react-spring/react-use-measure
Branch: master
Commit: 2df0ee8dfad4
Files: 10
Total size: 20.9 KB

Directory structure:
gitextract_0msf38bd/

├── .github/
│   └── workflows/
│       └── CI.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── package.json
├── readme.md
├── src/
│   └── index.ts
├── tests/
│   └── index.test.tsx
├── tsconfig.json
└── vite.config.ts

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

================================================
FILE: .github/workflows/CI.yml
================================================
name: CI
on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2

      - name: Install dependencies
        run: yarn install

      - name: Check build health
        run: yarn build

      - name: Run tests
        run: yarn test


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
dist

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: .prettierrc
================================================
{
  "semi": false,
  "trailingComma": "all",
  "singleQuote": true,
  "tabWidth": 2,
  "printWidth": 120
}


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

Copyright (c) 2019-2025 Poimandres

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: package.json
================================================
{
  "name": "react-use-measure",
  "version": "2.1.7",
  "description": "Utility to measure view bounds",
  "keywords": [
    "react",
    "use",
    "measure",
    "bounds",
    "hooks"
  ],
  "author": "Paul Henschel",
  "homepage": "https://github.com/pmndrs/react-use-measure",
  "repository": "https://github.com/pmndrs/react-use-measure",
  "license": "MIT",
  "files": [
    "dist/*",
    "src/*"
  ],
  "type": "module",
  "types": "./dist/index.d.ts",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "exports": {
    "require": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.cjs"
    },
    "import": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  },
  "sideEffects": false,
  "devDependencies": {
    "@testing-library/dom": "^10.4.0",
    "@testing-library/react": "^16.2.0",
    "@types/node": "^22.12.0",
    "@types/react": "^19.0.8",
    "@types/react-dom": "^19.0.3",
    "@vitest/browser": "^3.0.4",
    "playwright": "^1.50.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "resize-observer-polyfill": "^1.5.1",
    "rimraf": "^6.0.1",
    "typescript": "^5.7.3",
    "vite": "^6.0.11",
    "vitest": "^3.0.4"
  },
  "peerDependencies": {
    "react": ">=16.13",
    "react-dom": ">=16.13"
  },
  "peerDependenciesMeta": {
    "react-dom": {
      "optional": true
    }
  },
  "scripts": {
    "dev": "vite",
    "build": "rimraf dist && vite build && tsc",
    "test": "npx playwright install && vitest run"
  }
}


================================================
FILE: readme.md
================================================
<p align="center">
  <img height="400" src="https://i.imgur.com/eMYYMla.jpg" />
</p>

    yarn add react-use-measure

This small tool will measure the boundaries (for instance width, height, top, left) of a view you reference. It is reactive and responds to changes in size, window-scroll and nested-area-scroll.

### Why do we need this hook?

Because there is [no simple way](https://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element) to just get relative view coordinates. Yes, there is getBoundingClientRect, but it does not work when your content sits inside scroll areas whose offsets are simply neglected (as well as page scroll). Worse, mouse coordinates are relative to the viewport (the visible rect that contains the page). There is no easy way, for instance, to know that the mouse hovers over the upper/left corner of an element. This hook solves it for you.

You can try a live demo here: https://codesandbox.io/s/musing-kare-4fblz

# Usage

```jsx
import useMeasure from 'react-use-measure'

function App() {
  const [ref, bounds] = useMeasure()

  // consider that knowing bounds is only possible *after* the view renders
  // so you'll get zero values on the first run and be informed later

  return <div ref={ref} />
}
```

# Api

```jsx
interface RectReadOnly {
  readonly x: number
  readonly y: number
  readonly width: number
  readonly height: number
  readonly top: number
  readonly right: number
  readonly bottom: number
  readonly left: number
}

type Options = {
  // Debounce events in milliseconds
  debounce?: number | { scroll: number; resize: number }
  // React to nested scroll changes, don't use this if you know your view is static
  scroll?: boolean
  // You can optionally inject a resize-observer polyfill
  polyfill?: { new (cb: ResizeObserverCallback): ResizeObserver }
  // Measure size using offsetHeight and offsetWidth to ignore parent scale transforms
  offsetSize?: boolean
}

useMeasure(
  options: Options = { debounce: 0, scroll: false }
): [React.MutableRefObject<HTMLElement | SVGElement>, RectReadOnly]
```

# ⚠️ Notes

### Resize-observer polyfills

This lib relies on resize-observers. If you need a polyfill you can either polute the `window` object or inject it cleanly using the config options. We recommend [@juggle/resize-observer](https://github.com/juggle/resize-observer).

```jsx
import { ResizeObserver } from '@juggle/resize-observer'

function App() {
  const [ref, bounds] = useMeasure({ polyfill: ResizeObserver })
```

### Multiple refs

useMeasure currently returns its own ref. We do this because we are using functional refs for unmount tracking. If you need to have a ref of your own on the same element, use [react-merge-refs](https://github.com/smooth-code/react-merge-refs).


================================================
FILE: src/index.ts
================================================
import { useEffect, useState, useRef, useMemo } from 'react'

function createDebounce<T extends (...args: any[]) => void>(callback: T, ms: number) {
  let timeoutId: number

  return (...args: Parameters<T>): void => {
    window.clearTimeout(timeoutId)
    timeoutId = window.setTimeout(() => callback(...args), ms)
  }
}

declare type ResizeObserverCallback = (entries: any[], observer: ResizeObserver) => void
declare class ResizeObserver {
  constructor(callback: ResizeObserverCallback)
  observe(target: Element, options?: any): void
  unobserve(target: Element): void
  disconnect(): void
  static toString(): string
}

export interface RectReadOnly {
  readonly x: number
  readonly y: number
  readonly width: number
  readonly height: number
  readonly top: number
  readonly right: number
  readonly bottom: number
  readonly left: number
  [key: string]: number
}

type HTMLOrSVGElement = HTMLElement | SVGElement

type Result = [(element: HTMLOrSVGElement | null) => void, RectReadOnly, () => void]

type State = {
  element: HTMLOrSVGElement | null
  scrollContainers: HTMLOrSVGElement[] | null
  resizeObserver: ResizeObserver | null
  lastBounds: RectReadOnly
  orientationHandler: null | (() => void)
}

export type Options = {
  debounce?: number | { scroll: number; resize: number }
  scroll?: boolean
  polyfill?: { new (cb: ResizeObserverCallback): ResizeObserver }
  offsetSize?: boolean
}

function useMeasure(
  { debounce, scroll, polyfill, offsetSize }: Options = { debounce: 0, scroll: false, offsetSize: false },
): Result {
  const ResizeObserver =
    polyfill || (typeof window === 'undefined' ? class ResizeObserver {} : (window as any).ResizeObserver)

  if (!ResizeObserver) {
    throw new Error(
      'This browser does not support ResizeObserver out of the box. See: https://github.com/react-spring/react-use-measure/#resize-observer-polyfills',
    )
  }

  const [bounds, set] = useState<RectReadOnly>({
    left: 0,
    top: 0,
    width: 0,
    height: 0,
    bottom: 0,
    right: 0,
    x: 0,
    y: 0,
  })

  // keep all state in a ref
  const state = useRef<State>({
    element: null,
    scrollContainers: null,
    resizeObserver: null,
    lastBounds: bounds,
    orientationHandler: null,
  })

  // set actual debounce values early, so effects know if they should react accordingly
  const scrollDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.scroll) : null
  const resizeDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.resize) : null

  // make sure to update state only as long as the component is truly mounted
  const mounted = useRef(false)
  useEffect(() => {
    mounted.current = true
    return () => void (mounted.current = false)
  })

  // memoize handlers, so event-listeners know when they should update
  const [forceRefresh, resizeChange, scrollChange] = useMemo(() => {
    const callback = () => {
      if (!state.current.element) return
      const { left, top, width, height, bottom, right, x, y } =
        state.current.element.getBoundingClientRect() as unknown as RectReadOnly

      const size = {
        left,
        top,
        width,
        height,
        bottom,
        right,
        x,
        y,
      }

      if (state.current.element instanceof HTMLElement && offsetSize) {
        size.height = state.current.element.offsetHeight
        size.width = state.current.element.offsetWidth
      }

      Object.freeze(size)
      if (mounted.current && !areBoundsEqual(state.current.lastBounds, size)) set((state.current.lastBounds = size))
    }
    return [
      callback,
      resizeDebounce ? createDebounce(callback, resizeDebounce) : callback,
      scrollDebounce ? createDebounce(callback, scrollDebounce) : callback,
    ]
  }, [set, offsetSize, scrollDebounce, resizeDebounce])

  // cleanup current scroll-listeners / observers
  function removeListeners() {
    if (state.current.scrollContainers) {
      state.current.scrollContainers.forEach((element) => element.removeEventListener('scroll', scrollChange, true))
      state.current.scrollContainers = null
    }

    if (state.current.resizeObserver) {
      state.current.resizeObserver.disconnect()
      state.current.resizeObserver = null
    }

    if (state.current.orientationHandler) {
      if ('orientation' in screen && 'removeEventListener' in screen.orientation) {
        screen.orientation.removeEventListener('change', state.current.orientationHandler)
      } else if ('onorientationchange' in window) {
        window.removeEventListener('orientationchange', state.current.orientationHandler)
      }
    }
  }

  // add scroll-listeners / observers
  function addListeners() {
    if (!state.current.element) return
    state.current.resizeObserver = new ResizeObserver(scrollChange)
    state.current.resizeObserver!.observe(state.current.element)
    if (scroll && state.current.scrollContainers) {
      state.current.scrollContainers.forEach((scrollContainer) =>
        scrollContainer.addEventListener('scroll', scrollChange, { capture: true, passive: true }),
      )
    }

    // Handle orientation changes
    state.current.orientationHandler = () => {
      scrollChange()
    }

    // Use screen.orientation if available
    if ('orientation' in screen && 'addEventListener' in screen.orientation) {
      screen.orientation.addEventListener('change', state.current.orientationHandler)
    } else if ('onorientationchange' in window) {
      // Fallback to orientationchange event
      window.addEventListener('orientationchange', state.current.orientationHandler)
    }
  }

  // the ref we expose to the user
  const ref = (node: HTMLOrSVGElement | null) => {
    if (!node || node === state.current.element) return
    removeListeners()
    state.current.element = node
    state.current.scrollContainers = findScrollContainers(node)
    addListeners()
  }

  // add general event listeners
  useOnWindowScroll(scrollChange, Boolean(scroll))
  useOnWindowResize(resizeChange)

  // respond to changes that are relevant for the listeners
  useEffect(() => {
    removeListeners()
    addListeners()
  }, [scroll, scrollChange, resizeChange])

  // remove all listeners when the components unmounts
  useEffect(() => removeListeners, [])
  return [ref, bounds, forceRefresh]
}

// Adds native resize listener to window
function useOnWindowResize(onWindowResize: (event: Event) => void) {
  useEffect(() => {
    const cb = onWindowResize
    window.addEventListener('resize', cb)
    return () => void window.removeEventListener('resize', cb)
  }, [onWindowResize])
}
function useOnWindowScroll(onScroll: () => void, enabled: boolean) {
  useEffect(() => {
    if (enabled) {
      const cb = onScroll
      window.addEventListener('scroll', cb, { capture: true, passive: true })
      return () => void window.removeEventListener('scroll', cb, true)
    }
  }, [onScroll, enabled])
}

// Returns a list of scroll offsets
function findScrollContainers(element: HTMLOrSVGElement | null): HTMLOrSVGElement[] {
  const result: HTMLOrSVGElement[] = []
  if (!element || element === document.body) return result
  const { overflow, overflowX, overflowY } = window.getComputedStyle(element)
  if ([overflow, overflowX, overflowY].some((prop) => prop === 'auto' || prop === 'scroll')) result.push(element)
  return [...result, ...findScrollContainers(element.parentElement)]
}

// Checks if element boundaries are equal
const keys: (keyof RectReadOnly)[] = ['x', 'y', 'top', 'bottom', 'left', 'right', 'width', 'height']
const areBoundsEqual = (a: RectReadOnly, b: RectReadOnly): boolean => keys.every((key) => a[key] === b[key])

export default useMeasure


================================================
FILE: tests/index.test.tsx
================================================
import * as React from 'react'
import { render, cleanup, RenderResult, fireEvent } from '@testing-library/react'
import Polyfill from 'resize-observer-polyfill'
import { afterEach, describe, it, expect } from 'vitest'

import useMeasure, { Options } from '../src/index'

/**
 * Helpers
 */

const getBounds = (tools: RenderResult): DOMRect => JSON.parse(tools.getByTestId('box').innerHTML)
const nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

function ignoreWindowErrors(test: () => void) {
  const onErrorBackup = window.onerror
  window.onerror = () => null
  const consoleError = console.error
  console.error = () => null

  test()

  window.onerror = onErrorBackup
  console.error = consoleError
}

/**
 * Tests
 */

afterEach(() => {
  cleanup()
  window.scrollTo({ top: 0, left: 0 })
})

describe('useMeasure', () => {
  type Props = {
    switchRef?: boolean
    scale?: number
    onRender?: () => void
    options?: Options
    polyfill?: boolean
    offsetSize?: boolean
  }

  function Test({ switchRef, options, onRender, polyfill, scale = 1, offsetSize = false }: Props) {
    const [ref, bounds] = useMeasure({ ...options, polyfill: polyfill ? Polyfill : undefined, offsetSize })
    const [big, setBig] = React.useState(false)

    if (onRender) {
      onRender()
    }

    return (
      <>
        <style>{'body, html { margin: 0; } body { height: 200vh; }'}</style>
        <div
          data-testid="wrapper"
          style={{
            transform: `scale(${scale})`,
            width: '500px',
            height: '500px',
            overflow: 'auto',
          }}
        >
          <div
            ref={!switchRef ? ref : undefined}
            data-testid="box"
            onClick={() => setBig(!big)}
            style={{
              width: `${big ? 400 : 200}px`,
              height: `${big ? 400 : 200}px`,
              overflow: 'hidden',
              fontSize: '8px',
            }}
          >
            {JSON.stringify(bounds)}
          </div>
          <div style={{ width: 2000, height: 2000 }} />
        </div>
        <div ref={switchRef ? ref : null}>Dummy</div>
      </>
    )
  }

  it('gives empty initial bounds on first render', async () => {
    const tools = render(<Test />)

    expect(getBounds(tools).width).toBe(0)
    expect(getBounds(tools).height).toBe(0)
    expect(getBounds(tools).top).toBe(0)
    expect(getBounds(tools).left).toBe(0)
  })

  it('renders 1 additional time after first render', async () => {
    let count = 0

    render(<Test onRender={() => count++} />)

    await nextFrame()

    expect(count).toBe(2)
  })

  it('gives correct dimensions and positions after initial render', async () => {
    const tools = render(<Test />)

    await nextFrame()

    expect(getBounds(tools).width).toBe(200)
    expect(getBounds(tools).height).toBe(200)
    expect(getBounds(tools).top).toBe(0)
    expect(getBounds(tools).left).toBe(0)
  })

  it('gives correct dimensions and positions when the tracked elements changes in size', async () => {
    const tools = render(<Test />)

    fireEvent.click(tools.getByTestId('box'))

    await nextFrame()

    expect(getBounds(tools).width).toBe(400)
    expect(getBounds(tools).height).toBe(400)
    expect(getBounds(tools).top).toBe(0)
    expect(getBounds(tools).left).toBe(0)
  })

  it('gives correct dimensions and positions when the page is scrolled', async () => {
    const tools = render(<Test options={{ scroll: true }} />)

    window.scrollTo({ top: 200 })

    await nextFrame()

    expect(getBounds(tools).top).toBe(-200)
    expect(getBounds(tools).left).toBe(0)
  })
  it('gives correct dimensions and positions when the wrapper is scrolled', async () => {
    const tools = render(<Test options={{ scroll: true }} />)

    tools.getByTestId('wrapper').scrollTo({ top: 200 })

    await nextFrame()

    expect(getBounds(tools).top).toBe(-200)
    expect(getBounds(tools).left).toBe(0)
  })

  it('gives correct size when offsetSize: true and parent is scaled', async () => {
    const tools = render(<Test offsetSize scale={0.8} />)

    await nextFrame()

    expect(getBounds(tools).width).toBe(200)
    expect(getBounds(tools).height).toBe(200)
  })

  it('gives correct size when offsetSize: false and parent is scaled', async () => {
    const tools = render(<Test scale={0.8} />)

    await nextFrame()

    expect(getBounds(tools).width).toBe(200 * 0.8)
    expect(getBounds(tools).height).toBe(200 * 0.8)
  })

  it('debounces the scroll events', async () => {
    const tools = render(<Test options={{ scroll: true, debounce: { scroll: 50, resize: 0 } }} />)

    const wrapper = tools.getByTestId('wrapper')

    wrapper.scrollTo({ top: 200 })
    await nextFrame()
    wrapper.scrollTo({ top: 201 })
    await nextFrame()
    wrapper.scrollTo({ top: 202 })
    await nextFrame()

    expect(getBounds(tools).top).toBe(0)

    await wait(100)
    expect(getBounds(tools).top).toBe(-202)
  })

  // this one fails and needs to be fixed
  it('detects changes in ref', async () => {
    const tools = render(<Test />)

    await wait(100)

    tools.rerender(<Test switchRef />)

    await nextFrame()

    expect(getBounds(tools).top).toBe(500)
  })

  it('throws an descriptive error when the browser does not support ResizeObserver', () => {
    const RO = (window as any).ResizeObserver
    ;(window as any).ResizeObserver = null

    ignoreWindowErrors(() => {
      expect(() => render(<Test />)).toThrow(
        'This browser does not support ResizeObserver out of the box. See: https://github.com/react-spring/react-use-measure/#resize-observer-polyfills',
      )
    })
    ;(window as any).ResizeObserver = RO
  })

  it('does not throw when a ResizeObserver polyfill was provided', () => {
    const RO = (window as any).ResizeObserver
    ;(window as any).ResizeObserver = null

    ignoreWindowErrors(() => {
      expect(() => render(<Test polyfill />)).not.toThrow(
        'This browser does not support ResizeObserver out of the box. See: https://github.com/react-spring/react-use-measure/#resize-observer-polyfills',
      )
    })
    ;(window as any).ResizeObserver = RO
  })
})


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "jsx": "react",
    "pretty": true,
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "removeComments": true,
    "emitDeclarationOnly": true,
    "outDir": "dist",
    "resolveJsonModule": true
  },
  "include": ["./src"],
  "exclude": ["./node_modules/**/*"]
}


================================================
FILE: vite.config.ts
================================================
import * as path from 'node:path'
import * as vite from 'vite'

export default vite.defineConfig({
  root: process.argv[2] ? undefined : 'demo',
  resolve: {
    alias: {
      'use-measure': path.resolve(__dirname, './src'),
    },
  },
  test: {
    browser: {
      provider: 'playwright',
      enabled: true,
      headless: true,
      screenshotFailures: false,
      instances: [{ browser: 'chromium' }],
    },
  },
  build: {
    target: 'es2018',
    sourcemap: true,
    lib: {
      formats: ['es', 'cjs'],
      entry: 'src/index.ts',
      fileName: '[name]',
    },
    rollupOptions: {
      external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),
      output: {
        sourcemapExcludeSources: true,
      },
    },
  },
  plugins: [
    {
      name: 'vite-minify',
      renderChunk: {
        order: 'post',
        handler(code, { fileName }) {
          return vite.transformWithEsbuild(code, fileName, { minify: true, target: 'es2018' })
        },
      },
    },
  ],
})
Download .txt
gitextract_0msf38bd/

├── .github/
│   └── workflows/
│       └── CI.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── package.json
├── readme.md
├── src/
│   └── index.ts
├── tests/
│   └── index.test.tsx
├── tsconfig.json
└── vite.config.ts
Download .txt
SYMBOL INDEX (16 symbols across 3 files)

FILE: src/index.ts
  function createDebounce (line 3) | function createDebounce<T extends (...args: any[]) => void>(callback: T,...
  type ResizeObserverCallback (line 12) | type ResizeObserverCallback = (entries: any[], observer: ResizeObserver)...
  class ResizeObserver (line 13) | class ResizeObserver {
  type RectReadOnly (line 21) | interface RectReadOnly {
  type HTMLOrSVGElement (line 33) | type HTMLOrSVGElement = HTMLElement | SVGElement
  type Result (line 35) | type Result = [(element: HTMLOrSVGElement | null) => void, RectReadOnly,...
  type State (line 37) | type State = {
  type Options (line 45) | type Options = {
  function useMeasure (line 52) | function useMeasure(
  function useOnWindowResize (line 199) | function useOnWindowResize(onWindowResize: (event: Event) => void) {
  function useOnWindowScroll (line 206) | function useOnWindowScroll(onScroll: () => void, enabled: boolean) {
  function findScrollContainers (line 217) | function findScrollContainers(element: HTMLOrSVGElement | null): HTMLOrS...

FILE: tests/index.test.tsx
  function ignoreWindowErrors (line 16) | function ignoreWindowErrors(test: () => void) {
  type Props (line 38) | type Props = {
  function Test (line 47) | function Test({ switchRef, options, onRender, polyfill, scale = 1, offse...

FILE: vite.config.ts
  method handler (line 40) | handler(code, { fileName }) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (23K chars).
[
  {
    "path": ".github/workflows/CI.yml",
    "chars": 380,
    "preview": "name: CI\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  build-test:\n    runs-on: ubu"
  },
  {
    "path": ".gitignore",
    "chars": 110,
    "preview": ".DS_Store\nnode_modules\ndist\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": ".prettierrc",
    "chars": 107,
    "preview": "{\n  \"semi\": false,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"printWidth\": 120\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2019-2025 Poimandres\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "package.json",
    "chars": 1515,
    "preview": "{\n  \"name\": \"react-use-measure\",\n  \"version\": \"2.1.7\",\n  \"description\": \"Utility to measure view bounds\",\n  \"keywords\": "
  },
  {
    "path": "readme.md",
    "chars": 2785,
    "preview": "<p align=\"center\">\n  <img height=\"400\" src=\"https://i.imgur.com/eMYYMla.jpg\" />\n</p>\n\n    yarn add react-use-measure\n\nTh"
  },
  {
    "path": "src/index.ts",
    "chars": 7698,
    "preview": "import { useEffect, useState, useRef, useMemo } from 'react'\n\nfunction createDebounce<T extends (...args: any[]) => void"
  },
  {
    "path": "tests/index.test.tsx",
    "chars": 6293,
    "preview": "import * as React from 'react'\nimport { render, cleanup, RenderResult, fireEvent } from '@testing-library/react'\nimport "
  },
  {
    "path": "tsconfig.json",
    "chars": 425,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInt"
  },
  {
    "path": "vite.config.ts",
    "chars": 1014,
    "preview": "import * as path from 'node:path'\nimport * as vite from 'vite'\n\nexport default vite.defineConfig({\n  root: process.argv["
  }
]

About this extraction

This page contains the full source code of the react-spring/react-use-measure GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (20.9 KB), approximately 5.7k tokens, and a symbol index with 16 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!