{"id":13450376,"url":"https://github.com/wellyshen/react-cool-virtual","last_synced_at":"2025-05-15T01:07:54.206Z","repository":{"id":37069513,"uuid":"364574839","full_name":"wellyshen/react-cool-virtual","owner":"wellyshen","description":"😎 ♻️ A tiny React hook for rendering large datasets like a breeze.","archived":false,"fork":false,"pushed_at":"2023-08-12T16:38:49.000Z","size":4448,"stargazers_count":1226,"open_issues_count":37,"forks_count":39,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-08T21:08:31.097Z","etag":null,"topics":["carousel","chatroom","dom-recycle","dynamic-size","feeds","hook","infinite-scroll","lazy-loading","list","memory","performance","react","resize","server-side-rendering","smooth-scrolling","sticky-headers","table","typescript","virtualization","virtualized"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wellyshen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null},"funding":{"github":null,"patreon":null,"open_collective":"react-cool-virtual","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2021-05-05T12:50:15.000Z","updated_at":"2025-05-02T14:55:18.000Z","dependencies_parsed_at":"2023-09-23T14:39:17.161Z","dependency_job_id":null,"html_url":"https://github.com/wellyshen/react-cool-virtual","commit_stats":{"total_commits":978,"total_committers":8,"mean_commits":122.25,"dds":0.06646216768916158,"last_synced_commit":"67d4fc4b8407126b0c82164a308a9e84c058568f"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-virtual","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-virtual/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-virtual/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-virtual/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wellyshen","download_url":"https://codeload.github.com/wellyshen/react-cool-virtual/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254141059,"owners_count":22021270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["carousel","chatroom","dom-recycle","dynamic-size","feeds","hook","infinite-scroll","lazy-loading","list","memory","performance","react","resize","server-side-rendering","smooth-scrolling","sticky-headers","table","typescript","virtualization","virtualized"],"created_at":"2024-07-31T07:00:34.090Z","updated_at":"2025-05-15T01:07:54.159Z","avatar_url":"https://github.com/wellyshen.png","language":"TypeScript","funding_links":["https://opencollective.com/react-cool-virtual"],"categories":["Packages","TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/wellyshen/react-cool-virtual/blob/master/README.md\" title=\"React Cool Virtual\"\u003e\u003cimg src=\"https://github.com/wellyshen/react-cool-virtual/blob/master/images/logo.svg\" alt=\"React Cool Virtual\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eA tiny React hook for rendering large datasets like a breeze.\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![npm version](https://img.shields.io/npm/v/react-cool-virtual?style=flat-square)](https://www.npmjs.com/package/react-cool-virtual)\n[![npm downloads](https://img.shields.io/npm/dt/react-cool-virtual?style=flat-square)](https://www.npmtrends.com/react-cool-virtual)\n[![coverage status](https://img.shields.io/coveralls/github/wellyshen/react-cool-virtual?style=flat-square)](https://coveralls.io/github/wellyshen/react-cool-virtual?branch=master)\n[![gzip size](https://badgen.net/bundlephobia/minzip/react-cool-virtual?label=gzip%20size\u0026style=flat-square)](https://bundlephobia.com/result?p=react-cool-virtual)\n[![best of js](https://img.shields.io/endpoint?style=flat-square\u0026url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=wellyshen%2Freact-cool-virtual)](https://bestofjs.org/projects/react-cool-virtual)\n[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)\n\n\u003c/div\u003e\n\n## Features \u003c!-- omit in toc --\u003e\n\n- ♻️ Renders millions of items with highly performant way, using [DOM recycling](https://developers.google.com/web/updates/2016/07/infinite-scroller).\n- 🎣 Easy to use, based on React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook).\n- 💅🏼 Apply styles without hassle, just [few setups](#basic-usage).\n- 🧱 Supports [fixed](#fixed-size), [variable](#variable-size), [dynamic](#dynamic-size), and [real-time](#real-time-resize) heights/widths.\n- 🖥 Supports [responsive web design (RWD)](#responsive-web-design-rwd) for better UX.\n- 📌 Supports [sticky headers](#sticky-headers) for building on-trend lists.\n- 🚚 Built-ins [load more callback](#infinite-scroll) for you to deal with infinite scroll + [skeleton screens](https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a).\n- 🖱 Imperative [scroll-to methods](#scroll-to-offset--items) for offset, items, and alignment.\n- 🛹 Out-of-the-box [smooth scrolling](#smooth-scrolling) and the effect is DIY-able.\n- 💬 It's possible to implement [stick to bottom](#sticking-to-bottom) and [pre-pending items](#pre-pending-items) for chat, feeds, etc.\n- ⛳ Provides `isScrolling` indicator to you for UI placeholders or [performance optimization](#use-isscrolling-indicator).\n- 🗄️ Supports [server-side rendering (SSR)](#server-side-rendering-ssr) for a fast [FP + FCP](https://developers.google.com/web/updates/2019/02/rendering-on-the-web#server-rendering) and better [SEO](https://developers.google.com/web/updates/2019/02/rendering-on-the-web#server-rendering).\n- 📜 Supports [TypeScript](#working-in-typescript) type definition.\n- 🎛 Super flexible [API](#api) design, built with DX in mind.\n- 🦔 Tiny size ([~ 3.1kB gzipped](https://bundlephobia.com/result?p=react-cool-virtual)). No external dependencies, aside from the `react`.\n\n## Why? \u003c!-- omit in toc --\u003e\n\nWhen rendering a large set of data (e.g. list, table, etc.) in React, we all face performance/memory troubles. There're [some great libraries](https://www.npmjs.com/search?q=react%20virtualized) already available but most of them are component-based solutions that provide well-defineded way of using but increase a lot of bundle size. However, [a library](https://github.com/tannerlinsley/react-virtual) comes out as a hook-based solution that is flexible and `headless` but using and styling it can be verbose (because it's a low-level hook). Furthermore, it lacks many of the [useful features](#features).\n\nReact Cool Virtual is a [tiny](https://bundlephobia.com/result?p=react-cool-virtual) React hook that gives you a **better DX** and **modern way** for virtualizing a large amount of data without struggle 🤯.\n\n## Docs \u003c!-- omit in toc --\u003e\n\n- [Getting Started](#getting-started)\n  - [Requirement](#requirement)\n  - [Installation](#installation)\n  - [CDN](#cdn)\n  - [Basic Usage](#basic-usage)\n- [Examples](#examples)\n  - [Fixed Size](#fixed-size)\n  - [Variable Size](#variable-size)\n  - [Dynamic Size](#dynamic-size)\n  - [Real-time Resize](#real-time-resize)\n  - [Responsive Web Design (RWD)](#responsive-web-design-rwd)\n  - [Sticky Headers](#sticky-headers)\n  - [Scroll to Offset / Items](#scroll-to-offset--items)\n  - [Smooth Scrolling](#smooth-scrolling)\n  - [Infinite Scroll](#infinite-scroll)\n  - [Pre-pending Items](#pre-pending-items)\n  - [Filtering Items](#filtering-items)\n  - [Sticking to Bottom](#sticking-to-bottom)\n  - [Working with Input Elements](#working-with-input-elements)\n  - [Dealing with Dynamic Items](#dealing-with-dynamic-items)\n  - [Server-side Rendering (SSR)](#server-side-rendering-ssr)\n- [API](#api)\n  - [Options](#options)\n  - [Return Values](#return-values)\n- [Others](#others)\n  - [Performance Optimization](#performance-optimization)\n  - [How to Share A `ref`?](#how-to-share-a-ref)\n  - [Layout Items](#layout-items)\n  - [Working in TypeScript](#working-in-typescript)\n  - [ResizeObserver Polyfill](#resizeobserver-polyfill)\n\n## Getting Started\n\n### Requirement\n\nTo use React Cool Virtual, you must use `react@16.8.0` or greater which includes hooks.\n\n### Installation\n\nThis package is distributed via [npm](https://www.npmjs.com/package/react-cool-virtual).\n\n```sh\n$ yarn add react-cool-virtual\n# or\n$ npm install --save react-cool-virtual\n```\n\n\u003e ⚠️ This package using [ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API) under the hook. [Most modern browsers support it natively](https://caniuse.com/?search=ResizeObserver), you can also add [polyfill](#resizeobserver-polyfill) for full browser support.\n\n### CDN\n\nIf you're not using a module bundler or package manager. We also provide a [UMD](https://github.com/umdjs/umd) build which is available over the [unpkg.com](https://unpkg.com) CDN. Simply use a `\u003cscript\u003e` tag to add it after [React CDN links](https://reactjs.org/docs/cdn-links.html) as below:\n\n\u003c!-- prettier-ignore-start --\u003e\n```html\n\u003cscript crossorigin src=\"https://unpkg.com/react/umd/react.production.min.js\"\u003e\u003c/script\u003e\n\u003cscript crossorigin src=\"https://unpkg.com/react-dom/umd/react-dom.production.min.js\"\u003e\u003c/script\u003e\n\u003c!-- react-cool-virtual comes here --\u003e\n\u003cscript crossorigin src=\"https://unpkg.com/react-cool-virtual/dist/index.umd.production.min.js\"\u003e\u003c/script\u003e\n```\n\u003c!-- prettier-ignore-end --\u003e\n\nOnce you've added this you will have access to the `window.ReactCoolVirtual.useVirtual` variable.\n\n### Basic Usage\n\nHere's the basic concept of how it rocks:\n\n```js\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 10000, // Provide the total number for the list items\n    itemSize: 50, // The size of each item (default = 50)\n  });\n\n  return (\n    \u003cdiv\n      ref={outerRef} // Attach the `outerRef` to the scroll container\n      style={{ width: \"300px\", height: \"500px\", overflow: \"auto\" }}\n    \u003e\n      {/* Attach the `innerRef` to the wrapper of the items */}\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size }) =\u003e (\n          // You can set the item's height with the `size` property\n          \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n            ⭐️ {index}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n✨ Pretty easy right? React Cool Virtual is more powerful than you think. Let's explore more use cases through the examples!\n\n## Examples\n\n### Fixed Size\n\nThis example demonstrates how to create a fixed size row. For column or grid, please refer to CodeSandbox.\n\n[![Edit RCV - Fixed Size](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-fixed-size-bowcu?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size }) =\u003e (\n          \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n            ⭐️ {index}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Variable Size\n\nThis example demonstrates how to create a variable size row. For column or grid, please refer to CodeSandbox.\n\n[![Edit RCV - Variable Size](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-variable-size-8vu3u?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    itemSize: (idx) =\u003e (idx % 2 ? 100 : 50),\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size }) =\u003e (\n          \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n            ⭐️ {index}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Dynamic Size\n\nThis example demonstrates how to create a dynamic (unknown) size row. For column or grid, please refer to CodeSandbox.\n\n[![Edit RCV - Dynamic Size](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-dynamic-size-0wurg?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    itemSize: 75, // The unmeasured item sizes will refer to this value (default = 50)\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, measureRef }) =\u003e (\n          // Use the `measureRef` to measure the item size\n          \u003cdiv key={index} ref={measureRef}\u003e\n            {/* Some data... */}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e 💡 The scrollbar is jumping (or unexpected position)? It's because the total size of the items is gradually corrected along with an item that has been measured. You can tweak the `itemSize` to reduce the phenomenon.\n\n### Real-time Resize\n\nThis example demonstrates how to create a real-time resize row (e.g. accordion, collapse, etc.). For column or grid, please refer to CodeSandbox.\n\n[![Edit RCV - Real-time Resize](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-real-time-resize-fixvr?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport { useState, forwardRef } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst AccordionItem = forwardRef(({ children, height, ...rest }, ref) =\u003e {\n  const [h, setH] = useState(height);\n\n  return (\n    \u003cdiv\n      {...rest}\n      style={{ height: `${h}px` }}\n      ref={ref}\n      onClick={() =\u003e setH((prevH) =\u003e (prevH === 50 ? 100 : 50))}\n    \u003e\n      {children}\n    \u003c/div\u003e\n  );\n});\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 50,\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size, measureRef }) =\u003e (\n          // Use the `measureRef` to measure the item size\n          \u003cAccordionItem key={index} height={size} ref={measureRef}\u003e\n            👋🏻 Click Me\n          \u003c/AccordionItem\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Responsive Web Design (RWD)\n\nThis example demonstrates how to create a list with RWD to provide a better UX for the user.\n\n[![Edit RCV - RWD](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-rwd-x6lvc?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    // Use the outer's width (2nd parameter) to adjust the item's size\n    itemSize: (_, width) =\u003e (width \u003e 400 ? 50 : 100),\n    // The event will be triggered on outer's size changes\n    onResize: (size) =\u003e console.log(\"Outer's size: \", size),\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"100%\", height: \"400px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {/* We can also access the outer's width here */}\n        {items.map(({ index, size, width }) =\u003e (\n          \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n            ⭐️ {index} ({width})\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e 💡 If the item size is specified through the function of `itemSize`, please ensure there's no the [measureRef](#items) on the item element. Otherwise, the hook will use the measured (cached) size for the item. When working with RWD, we can only use either of the two.\n\n### Sticky Headers\n\nThis example demonstrates how to make sticky headers with React Cool Virtual.\n\n[![Edit RCV - Sticky Headers](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-sticky-headers-pm79x?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    itemSize: 75,\n    stickyIndices: [0, 10, 20, 30, 40, 50], // The values must be provided in ascending order\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size, isSticky }) =\u003e {\n          let style = { height: `${size}px` };\n          // Use the `isSticky` property to style the sticky item, that's it ✨\n          style = isSticky ? { ...style, position: \"sticky\", top: \"0\" } : style;\n\n          return (\n            \u003cdiv key={someData[index].id} style={style}\u003e\n              {someData[index].content}\n            \u003c/div\u003e\n          );\n        })}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e 💡 For [better performance \u0026 accessibility](https://developer.mozilla.org/en-US/docs/Web/CSS/position#performance_accessibility). We encourage you to add `will-change:transform` to the positioned elements to render the element in its own layer, improving repaint speed and therefore improving performance and accessibility.\n\n\u003e 💡 The scrollbar disappears when using Chrome in Mac? If you encounter [this issue](https://bugs.chromium.org/p/chromium/issues/detail?id=1033712), you can add `will-change:transform` to the outer element to workaround this problem.\n\n### Scroll to Offset / Items\n\nYou can imperatively scroll to offset or items as follows:\n\n[![Edit RCV - Scroll-to Methods](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-scroll-to-methods-cs9x9?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nconst { scrollTo, scrollToItem } = useVirtual();\n\nconst scrollToOffset = () =\u003e {\n  // Scrolls to 500px\n  scrollTo(500, () =\u003e {\n    // 🤙🏼 Do whatever you want through the callback\n  });\n};\n\nconst scrollToItem = () =\u003e {\n  // Scrolls to the 500th item\n  scrollToItem(500, () =\u003e {\n    // 🤙🏼 Do whatever you want through the callback\n  });\n\n  // We can control the alignment of the item with the `align` option\n  // Acceptable values are: \"auto\" (default) | \"start\" | \"center\" | \"end\"\n  // Using \"auto\" will scroll the item into the view at the start or end, depending on which is closer\n  scrollToItem({ index: 10, align: \"auto\" });\n};\n```\n\n### Smooth Scrolling\n\nReact Cool Virtual provides the smooth scrolling feature out of the box, all you need to do is turn the `smooth` option on.\n\n[![Edit RCV - Smooth Scrolling](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-smooth-scrolling-noy4d?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nconst { scrollTo, scrollToItem } = useVirtual();\n\n// Smoothly scroll to 500px\nconst scrollToOffset = () =\u003e scrollTo({ offset: 500, smooth: true });\n\n// Smoothly scroll to the 500th item\nconst scrollToItem = () =\u003e scrollToItem({ index: 10, smooth: true });\n```\n\n\u003e 💡 When working with [dynamic size](#dynamic-size), the scroll position will be automatically corrected along with the items are measured. To optimize it, we can provide an estimated item size to the [itemSize](#itemsize) option.\n\nThe default easing effect is [easeInOutSine](https://easings.net/#easeInOutSine), and the duration is `100ms \u003c= distance * 0.075 \u003c= 500ms`. You can easily customize your own effect as follows:\n\n```js\nconst { scrollTo } = useVirtual({\n  // For 500 milliseconds\n  scrollDuration: 500,\n  // Or whatever duration you want based on the scroll distance\n  scrollDuration: (distance) =\u003e distance * 0.05,\n  // Using \"easeInOutBack\" effect (default = easeInOutSine), see: https://easings.net/#easeInOutSine\n  scrollEasingFunction: (t) =\u003e {\n    const c1 = 1.70158;\n    const c2 = c1 * 1.525;\n\n    return t \u003c 0.5\n      ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2\n      : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;\n  },\n});\n\nconst scrollToOffset = () =\u003e scrollTo({ offset: 500, smooth: true });\n```\n\n\u003e 💡 For more cool easing effects, please [check it out](https://easings.net).\n\n### Infinite Scroll\n\nIt's possible to make a complicated infinite scroll logic simple by just using a hook, no kidding! Let's see how possible 🤔.\n\n[![Edit RCV - Infinite Scroll](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-infinite-scroll-3y351?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n#### Working with [Skeleton Screens](https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a)\n\n```js\nimport { useState } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\nimport axios from \"axios\";\n\nconst TOTAL_COMMENTS = 500;\nconst BATCH_COMMENTS = 5;\nconst isItemLoadedArr = [];\n\nconst loadData = async ({ loadIndex }, setComments) =\u003e {\n  // Set the state of a batch items as `true`\n  // to avoid the callback from being invoked repeatedly\n  isItemLoadedArr[loadIndex] = true;\n\n  try {\n    const { data: comments } = await axios(`/comments?postId=${loadIndex + 1}`);\n\n    setComments((prevComments) =\u003e {\n      const nextComments = [...prevComments];\n\n      comments.forEach((comment) =\u003e {\n        nextComments[comment.id - 1] = comment;\n      });\n\n      return nextComments;\n    });\n  } catch (err) {\n    // If there's an error set the state back to `false`\n    isItemLoadedArr[loadIndex] = false;\n    // Then try again\n    loadData({ loadIndex }, setComments);\n  }\n};\n\nconst List = () =\u003e {\n  const [comments, setComments] = useState([]);\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: TOTAL_COMMENTS,\n    // Estimated item size (with padding)\n    itemSize: 122,\n    // The number of items that you want to load/or pre-load, it will trigger the `loadMore` callback\n    // when the user scrolls within every items, e.g. 1 - 5, 6 - 10, and so on (default = 15)\n    loadMoreCount: BATCH_COMMENTS,\n    // Provide the loaded state of a batch items to the callback for telling the hook\n    // whether the `loadMore` should be triggered or not\n    isItemLoaded: (loadIndex) =\u003e isItemLoadedArr[loadIndex],\n    // We can fetch the data through the callback, it's invoked when more items need to be loaded\n    loadMore: (e) =\u003e loadData(e, setComments),\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"500px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, measureRef }) =\u003e (\n          \u003cdiv\n            key={comments[index]?.id || `fb-${index}`}\n            style={{ padding: \"16px\", minHeight: \"122px\" }}\n            ref={measureRef} // Used to measure the unknown item size\n          \u003e\n            {comments[index]?.body || \"⏳ Loading...\"}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### Working with A Loading Indicator\n\n```js\nimport { Fragment, useState } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\nimport axios from \"axios\";\n\nconst TOTAL_COMMENTS = 500;\nconst BATCH_COMMENTS = 5;\nconst isItemLoadedArr = [];\n// We only have 50 (500 / 5) batches of items, so set the 51th (index = 50) batch as `true`\n// to avoid the `loadMore` callback from being invoked, yep it's a trick 😉\nisItemLoadedArr[50] = true;\n\nconst loadData = async ({ loadIndex }, setComments) =\u003e {\n  isItemLoadedArr[loadIndex] = true;\n\n  try {\n    const { data: comments } = await axios(`/comments?postId=${loadIndex + 1}`);\n\n    setComments((prevComments) =\u003e [...prevComments, ...comments]);\n  } catch (err) {\n    isItemLoadedArr[loadIndex] = false;\n    loadData({ loadIndex }, setComments);\n  }\n};\n\nconst Loading = () =\u003e \u003cdiv\u003e⏳ Loading...\u003c/div\u003e;\n\nconst List = () =\u003e {\n  const [comments, setComments] = useState([]);\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: comments.length, // Provide the number of comments\n    loadMoreCount: BATCH_COMMENTS,\n    isItemLoaded: (loadIndex) =\u003e isItemLoadedArr[loadIndex],\n    loadMore: (e) =\u003e loadData(e, setComments),\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"500px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.length ? (\n          items.map(({ index, measureRef }) =\u003e {\n            const showLoading =\n              index === comments.length - 1 \u0026\u0026 comments.length \u003c TOTAL_COMMENTS;\n\n            return (\n              \u003cFragment key={comments[index].id}\u003e\n                \u003cdiv ref={measureRef}\u003e{comments[index].body}\u003c/div\u003e\n                {showLoading \u0026\u0026 \u003cLoading /\u003e}\n              \u003c/Fragment\u003e\n            );\n          })\n        ) : (\n          \u003cLoading /\u003e\n        )}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Pre-pending Items\n\nThis example demonstrates how to pre-pend items and maintain scroll position for the user.\n\n[![Edit RCV - Prepend Items](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-prepend-items-ui06h?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport { useEffect, useLayoutEffect, useState } from \"react\";\n\nimport useVirtual from \"react-cool-virtual\";\nimport axios from \"axios\";\n\nconst TOTAL_COMMENTS = 500;\nconst BATCH_COMMENTS = 5;\nlet shouldFetchData = true;\nlet postId = 100;\n\nconst fetchData = async (postId, setComments) =\u003e {\n  try {\n    const { data: comments } = await axios(`/comments?postId=${postId}`);\n\n    // Pre-pend new items\n    setComments((prevComments) =\u003e [...comments, ...prevComments]);\n  } catch (err) {\n    // Try again\n    fetchData(postId, setComments);\n  }\n};\n\nconst List = () =\u003e {\n  const [comments, setComments] = useState([]);\n  const { outerRef, innerRef, items, startItem } = useVirtual({\n    // Provide the number of comments\n    itemCount: comments.length,\n    onScroll: ({ scrollForward, scrollOffset }) =\u003e {\n      // Tweak the threshold of data fetching that you want\n      if (!scrollForward \u0026\u0026 scrollOffset \u003c 50 \u0026\u0026 shouldFetchData) {\n        fetchData(--postId, setComments);\n        shouldFetchData = false;\n      }\n    },\n  });\n\n  useEffect(() =\u003e fetchData(postId, setComments), []);\n\n  // Execute the `startItem` through `useLayoutEffect` before the browser to paint\n  // See https://reactjs.org/docs/hooks-reference.html#uselayouteffect to learn more\n  useLayoutEffect(() =\u003e {\n    // After the list updated, maintain the previous scroll position for the user\n    startItem(BATCH_COMMENTS, () =\u003e {\n      // After the scroll position updated, re-allow data fetching\n      if (comments.length \u003c TOTAL_COMMENTS) shouldFetchData = true;\n    });\n  }, [comments.length, startItem]);\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"500px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.length ? (\n          items.map(({ index, measureRef }) =\u003e (\n            // Used to measure the unknown item size\n            \u003cdiv key={comments[index].id} ref={measureRef}\u003e\n              {comments[index].body}\n            \u003c/div\u003e\n          ))\n        ) : (\n          \u003cdiv className=\"item\"\u003e⏳ Loading...\u003c/div\u003e\n        )}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Filtering Items\n\nWhen working with filtering items, we can reset the scroll position when the `itemCount` is changed by enabling the [resetScroll](#resetscroll) option.\n\n[![Edit RCV - Filter Items](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-filter-items-clno9?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport { useState } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst List = () =\u003e {\n  const [itemCount, setItemCount] = useState(100);\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount,\n    // Resets the scroll position when the `itemCount` is changed (default = false)\n    resetScroll: true,\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size }) =\u003e (\n          \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n            ⭐️ {index}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Sticking to Bottom\n\nThis example demonstrates the scenario of sticking/unsticking the scroll position to the bottom for a chatroom.\n\n[![Edit RCV - Stick to Bottom](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-stick-to-bottom-cxsgw?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport { useState, useEffect } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\nimport axios from \"axios\";\n\nconst TOTAL_MESSAGES = 200;\nlet isScrolling = false; // Used to prevent UX conflict\nlet id = 0;\n\nconst loadData = async (id, setMessages) =\u003e {\n  try {\n    const { data: messages } = await axios(`/messages/${id}`);\n\n    setMessages((prevMessages) =\u003e [...prevMessages, messages]);\n  } catch (err) {\n    loadData(id, setMessages);\n  }\n};\n\nconst Chatroom = () =\u003e {\n  const [shouldSticky, setShouldSticky] = useState(true);\n  const [messages, setMessages] = useState([]);\n  const { outerRef, innerRef, items, scrollToItem } = useVirtual({\n    // Provide the number of messages\n    itemCount: messages.length,\n    // You can speed up smooth scrolling\n    scrollDuration: 50,\n    onScroll: ({ userScroll }) =\u003e {\n      // If the user scrolls and isn't automatically scrolling, cancel stick to bottom\n      if (userScroll \u0026\u0026 !isScrolling) setShouldSticky(false);\n    },\n  });\n\n  useEffect(() =\u003e {\n    // Mock messages service\n    if (id \u003c= TOTAL_MESSAGES)\n      setTimeout(\n        () =\u003e loadData(++id, setMessages),\n        Math.floor(500 + Math.random() * 2000)\n      );\n  }, [messages.length]);\n\n  useEffect(() =\u003e {\n    // Automatically stick to bottom, using smooth scrolling for better UX\n    if (shouldSticky) {\n      isScrolling = true;\n      scrollToItem({ index: messages.length - 1, smooth: true }, () =\u003e {\n        isScrolling = false;\n      });\n    }\n  }, [messages.length, shouldSticky, scrollToItem]);\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\n        style={{ width: \"300px\", height: \"400px\", overflow: \"auto\" }}\n        ref={outerRef}\n      \u003e\n        \u003cdiv ref={innerRef}\u003e\n          {items.map(({ index, measureRef }) =\u003e (\n            // Used to measure the unknown item size\n            \u003cdiv key={`${messages[index].id}`} ref={measureRef}\u003e\n              \u003cdiv\u003e{messages[index].content}\u003c/div\u003e\n            \u003c/div\u003e\n          ))}\n        \u003c/div\u003e\n      \u003c/div\u003e\n      {!shouldSticky \u0026\u0026 (\n        \u003cbutton onClick={() =\u003e setShouldSticky(true)}\u003eStick to Bottom\u003c/button\u003e\n      )}\n    \u003c/div\u003e\n  );\n};\n```\n\n### Working with Input Elements\n\nThis example demonstrates how to handle input elements (or form fields) in a virtualized list.\n\n[![Edit RCV - Input Elements](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-input-elements-9p6ot?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport { useState } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst defaultValues = new Array(20).fill(false);\n\nconst Form = () =\u003e {\n  const [formData, setFormData] = useState({ todo: defaultValues });\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: defaultValues.length,\n  });\n\n  const handleInputChange = ({ target }, index) =\u003e {\n    // Store the input values in React state\n    setFormData((prevData) =\u003e {\n      const todo = [...prevData.todo];\n      todo[index] = target.checked;\n      return { todo };\n    });\n  };\n\n  const handleSubmit = (e) =\u003e {\n    e.preventDefault();\n    alert(JSON.stringify(formData, undefined, 2));\n  };\n\n  return (\n    \u003cform onSubmit={handleSubmit}\u003e\n      \u003cdiv\n        style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n        ref={outerRef}\n      \u003e\n        \u003cdiv ref={innerRef}\u003e\n          {items.map(({ index, size }) =\u003e (\n            \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n              \u003cinput\n                id={`todo-${index}`}\n                type=\"checkbox\"\n                // Populate the corresponding state to the default value\n                defaultChecked={formData.todo[index]}\n                onChange={(e) =\u003e handleInputChange(e, index)}\n              /\u003e\n              \u003clabel htmlFor={`todo-${index}`}\u003e{index}. I'd like to...\u003c/label\u003e\n            \u003c/div\u003e\n          ))}\n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003cinput type=\"submit\" /\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\nWhen dealing with forms, we can use [React Cool Form](react-cool-form.netlify.app) to handle the form state and boost performance for use.\n\n[![Edit RCV - RCF](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rcv-rcf-y6wiq?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport useVirtual from \"react-cool-virtual\";\nimport { useForm } from \"react-cool-form\";\n\nconst defaultValues = new Array(20).fill(false);\n\nconst Form = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: defaultValues.length,\n  });\n  const { form } = useForm({\n    defaultValues: { todo: defaultValues },\n    removeOnUnmounted: false, // To keep the value of unmounted fields\n    onSubmit: (formData) =\u003e alert(JSON.stringify(formData, undefined, 2)),\n  });\n\n  return (\n    \u003cform ref={form}\u003e\n      \u003cdiv\n        style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n        ref={outerRef}\n      \u003e\n        \u003cdiv ref={innerRef}\u003e\n          {items.map(({ index, size }) =\u003e (\n            \u003cdiv key={index} style={{ height: `${size}px` }}\u003e\n              \u003cinput\n                id={`todo-${index}`}\n                name={`todo[${index}]`}\n                type=\"checkbox\"\n              /\u003e\n              \u003clabel htmlFor={`todo-${index}`}\u003e{index}. I'd like to...\u003c/label\u003e\n            \u003c/div\u003e\n          ))}\n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003cinput type=\"submit\" /\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\n### Dealing with Dynamic Items\n\nReact requires [keys](https://reactjs.org/docs/lists-and-keys.html#keys) for array items. I'd recommend using an unique id as the key as possible as we can, especially when working with reordering, filtering, etc. Refer to [this article](https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318) to learn more.\n\n```js\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual();\n\n  return (\n    \u003cdiv\n      ref={outerRef}\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size }) =\u003e (\n          // Use IDs from your data as keys\n          \u003cdiv key={someData[index].id} style={{ height: `${size}px` }}\u003e\n            {someData[index].content}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Server-side Rendering (SSR)\n\nServer-side rendering allows us to provide a fast [FP and FCP](https://developers.google.com/web/updates/2019/02/rendering-on-the-web#server-rendering), it also benefits for [SEO](https://developers.google.com/web/updates/2019/02/rendering-on-the-web#seo). React Cool Virtual supplies you a seamless DX between SSR and CSR.\n\n```js\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    ssrItemCount: 30, // Renders 0th - 30th items on SSR\n    // Or\n    ssrItemCount: [50, 80], // Renders 50th - 80th items on SSR\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {/* The items will be rendered both on SSR and CSR, depending on our settings */}\n        {items.map(({ index, size }) =\u003e (\n          \u003cdiv key={someData[index].id} style={{ height: `${size}px` }}\u003e\n            {someData[index].content}\n          \u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e 💡 Please note, when using the `ssrItemCount`, the initial items will be the SSR items but it has no impact to the UX. In addition, you might notice that some styles (i.e. width, start) of the SSR items are `0`. It's by design, because there's no way to know the outer's size on SSR. However, you can make up these styles based on the environments if you need.\n\n## API\n\nReact Cool Virtual is a custom React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook) that supplies you with [all the features](#features) for building highly performant virtualized datasets easily 🚀. It takes `options` parameters and returns useful methods as follows.\n\n```js\nconst returnValues = useVirtual(options);\n```\n\n### Options\n\nAn `object` with the following options:\n\n#### itemCount (Required)\n\n`number`\n\nThe total number of items. It can be an arbitrary number if actual number is unknown, see the [example](#working-with-a-loading-indicator) to learn more.\n\n#### ssrItemCount\n\n`number | [number, number]`\n\nThe number of items that are rendered on server-side, see the [example](#server-side-rendering-ssr) to learn more.\n\n#### itemSize\n\n`number | (index: number, width: number) =\u003e number`\n\nThe size of an item (default = 50). When working with **dynamic size**, it will be the default/or estimated size of the unmeasured items.\n\n- For `number` use case, please refer to the [fixed size example](#fixed-size).\n- For `index` callback use case, please refer to the [variable size example](#variable-size).\n- For `width` callback use case, please refer to the [RWD example](#responsive-web-design-rwd).\n\n#### horizontal\n\n`boolean`\n\nThe layout/orientation of the list (default = false). When `true` means left/right scrolling, so the hook will use `width` as the [item size](#itemsize) and use the `left` as the [start](#items) position.\n\n#### resetScroll\n\n`boolean`\n\nIt's used to tell the hook to reset the scroll position when the [itemCount](#itemcount-required) is changed (default = false). It's useful for [filtering items](#filtering-items).\n\n#### overscanCount\n\n`number`\n\nThe number of items to render behind and ahead of the visible area (default = 1). That can be used for two reasons:\n\n- To slightly reduce/prevent a flash of empty screen while the user is scrolling. Please note, too many can negatively impact performance.\n- To allow the tab key to focus on the next (invisible) item for better accessibility.\n\n#### useIsScrolling\n\n`boolean`\n\nTo enable/disable the [isScrolling](#items) indicator of an item (default = false). It's useful for UI placeholders or [performance optimization](#use-isscrolling-indicator) when the list is being scrolled. Please note, using it will result in an additional render after scrolling has stopped.\n\n#### stickyIndices\n\n`number[]`\n\nAn array of indexes to make certain items in the list sticky. See the [example](#sticky-headers) to learn more.\n\n- The values must be provided **in ascending order**, i.e. `[0, 10, 20, 30, ...]`.\n\n#### scrollDuration\n\n`number | (distance: number) =\u003e number`\n\nThe duration of [smooth scrolling](#smooth-scrolling), the unit is milliseconds (default = `100ms \u003c= distance * 0.075 \u003c= 500ms`).\n\n#### scrollEasingFunction\n\n`(time: number) =\u003e number`\n\nA function that allows us to customize the easing effect of [smooth scrolling](#smooth-scrolling) (default = [easeInOutSine](https://easings.net/#easeInOutSine)).\n\n#### loadMoreCount\n\n`number`\n\nHow many number of items that you want to load/or pre-load (default = 15), it's used for [infinite scroll](#infinite-scroll). A number 15 means the [loadMore](#loadmore) callback will be invoked when the user scrolls within every 15 items, e.g. 1 - 15, 16 - 30, and so on.\n\n#### isItemLoaded\n\n`(index: number) =\u003e boolean`\n\nA callback for us to provide the loaded state of a batch items, it's used for [infinite scroll](#infinite-scroll). It tells the hook whether the [loadMore](#loadmore) should be triggered or not.\n\n#### loadMore\n\n`(event: Object) =\u003e void`\n\nA callback for us to fetch (more) data, it's used for [infinite scroll](#infinite-scroll). It's invoked when more items need to be loaded, which based on the mechanism of [loadMoreCount](#loadmorecount) and [isItemLoaded](#isitemloaded).\n\n```js\nconst loadMore = ({\n  startIndex, // (number) The index of the first batch item\n  stopIndex, // (number) The index of the last batch item\n  loadIndex, // (number) The index of the current batch items (e.g. 1 - 15 as `0`, 16 - 30 as `1`, and so on)\n  scrollOffset, // (number) The scroll offset from top/left, depending on the `horizontal` option\n  userScroll, // (boolean) Tells you the scrolling is through the user or not\n}) =\u003e {\n  // Fetch data...\n};\n\nconst props = useVirtual({ loadMore });\n```\n\n#### onScroll\n\n`(event: Object) =\u003e void`\n\nThis event will be triggered when scroll position is being changed by the user scrolls or [scrollTo](#scrollto)/[scrollToItem](#scrolltoitem) methods.\n\n```js\nconst onScroll = ({\n  overscanStartIndex, // (number) The index of the first overscan item\n  overscanStopIndex, // (number) The index of the last overscan item\n  visibleStartIndex, // (number) The index of the first visible item\n  visibleStopIndex, // (number) The index of the last visible item\n  scrollOffset, // (number) The scroll offset from top/left, depending on the `horizontal` option\n  scrollForward, // (boolean) The scroll direction of up/down or left/right, depending on the `horizontal` option\n  userScroll, // (boolean) Tells you the scrolling is through the user or not\n}) =\u003e {\n  // Do something...\n};\n\nconst props = useVirtual({ onScroll });\n```\n\n#### onResize\n\n`(event: Object) =\u003e void`\n\nThis event will be triggered when the size of the outer element changes.\n\n```js\nconst onResize = ({\n  width, // (number) The content width of the outer element\n  height, // (number) The content height of the outer element\n}) =\u003e {\n  // Do something...\n};\n\nconst props = useVirtual({ onResize });\n```\n\n### Return Values\n\nAn `object` with the following properties:\n\n#### outerRef\n\n`React.useRef\u003cHTMLElement\u003e`\n\nA [ref](https://reactjs.org/docs/hooks-reference.html#useref) to attach to the outer element. We must [apply it](#basic-usage) for using this hook.\n\n#### innerRef\n\n`React.useRef\u003cHTMLElement\u003e`\n\nA [ref](https://reactjs.org/docs/hooks-reference.html#useref) to attach to the inner element. We must [apply it](#basic-usage) for using this hook.\n\n#### items\n\n`Object[]`\n\nThe virtualized items for rendering rows/columns. Each item is an `object` that contains the following properties:\n\n| Name        | Type              | Description                                                                                                     |\n| ----------- | ----------------- | --------------------------------------------------------------------------------------------------------------- |\n| index       | number            | The index of the item.                                                                                          |\n| size        | number            | The fixed/variable/measured size of the item.                                                                   |\n| width       | number            | The current content width of the outer element. It's useful for a [RWD row/column](#responsive-web-design-rwd). |\n| start       | number            | The starting position of the item. We might only need this when [working with grids](#layout-items).            |\n| isScrolling | true \\| undefined | An indicator to show a placeholder or [optimize performance](#use-isscrolling-indicator) for the item.          |\n| isSticky    | true \\| undefined | An indicator to make certain items become [sticky in the list](#sticky-headers).                                |\n| measureRef  | Function          | It's used to measure an item with [dynamic](#dynamic-size) or [real-time](#real-time-resize) heights/widths.    |\n\n#### scrollTo\n\n`(offsetOrOptions: number | Object, callback?: () =\u003e void) =\u003e void`\n\nThis method allows us to scroll to the specified offset from top/left, depending on the [horizontal](#horizontal) option.\n\n```js\n// Basic usage\nscrollTo(500);\n\n// Using options\nscrollTo({\n  offset: 500,\n  smooth: true, // Enable/disable smooth scrolling (default = false)\n});\n```\n\n\u003e 💡 It's possible to customize the easing effect of the smoothly scrolling, see the [example](#smooth-scrolling) to learn more.\n\n#### scrollToItem\n\n`(indexOrOptions: number | Object, callback?: () =\u003e void) =\u003e void`\n\nThis method allows us to scroll to the specified item.\n\n```js\n// Basic usage\nscrollToItem(10);\n\n// Using options\nscrollToItem({\n  index: 10,\n  // Control the alignment of the item, acceptable values are: \"auto\" (default) | \"start\" | \"center\" | \"end\"\n  // Using \"auto\" will scroll the item into the view at the start or end, depending on which is closer\n  align: \"auto\",\n  // Enable/disable smooth scrolling (default = false)\n  smooth: true,\n});\n```\n\n\u003e 💡 It's possible to customize the easing effect of the smoothly scrolling, see the [example](#smooth-scrolling) to learn more.\n\n#### startItem\n\n`(index: number, callback?: () =\u003e void) =\u003e void`\n\nThis method is used to work with [pre-pending items](#pre-pending-items). It allows us to main the previous scroll position for the user.\n\n## Others\n\n### Performance Optimization\n\nItems are re-rendered whenever the user scrolls. If your item is a **heavy data component**, there're two strategies for performance optimization.\n\n#### Use [React.memo](https://reactjs.org/docs/react-api.html#reactmemo)\n\nWhen working with **non-dynamic size**, we can extract the item to it's own component and wrap it with `React.memo`. It shallowly compares the current props and the next props to avoid unnecessary re-renders.\n\n```js\nimport { memo } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst MemoizedItem = memo(({ height, ...rest }) =\u003e {\n  // A lot of heavy computing here... 🤪\n\n  return (\n    \u003cdiv {...rest} style={{ height: `${height}px` }}\u003e\n      🐳 Am I heavy?\n    \u003c/div\u003e\n  );\n});\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    itemSize: 75,\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, size }) =\u003e (\n          \u003cMemoizedItem key={index} height={size} /\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n#### Use `isScrolling` Indicator\n\nIf the above solution can't meet your case or you're working with **dynamic size**. React Cool Virtual supplies you an `isScrolling` indicator that allows you to replace the heavy component with a light one while the user is scrolling.\n\n```js\nimport { forwardRef } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst HeavyItem = forwardRef((props, ref) =\u003e {\n  // A lot of heavy computing here... 🤪\n\n  return (\n    \u003cdiv {...props} ref={ref}\u003e\n      🐳 Am I heavy?\n    \u003c/div\u003e\n  );\n});\n\nconst LightItem = (props) =\u003e \u003cdiv {...props}\u003e🦐 I believe I can fly...\u003c/div\u003e;\n\nconst List = () =\u003e {\n  const { outerRef, innerRef, items } = useVirtual({\n    itemCount: 1000,\n    useIsScrolling: true, // Just use it (default = false)\n    // Or\n    useIsScrolling: (speed) =\u003e speed \u003e 50, // Use it based on the scroll speed (more user friendly)\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"300px\", height: \"300px\", overflow: \"auto\" }}\n      ref={outerRef}\n    \u003e\n      \u003cdiv ref={innerRef}\u003e\n        {items.map(({ index, isScrolling, measureRef }) =\u003e\n          isScrolling ? (\n            \u003cLightItem key={index} /\u003e\n          ) : (\n            \u003cHeavyItem key={index} ref={measureRef} /\u003e\n          )\n        )}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e 💡 Well... the `isScrolling` can also be used in many other ways, please use your imagination 🤗.\n\n### How to Share A `ref`?\n\nYou can share a `ref` as follows, here we take the `outerRef` as the example:\n\n```js\nimport { useRef } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst App = () =\u003e {\n  const ref = useRef();\n  const { outerRef } = useVirtual();\n\n  return (\n    \u003cdiv\n      ref={(el) =\u003e {\n        outerRef.current = el; // Set the element to the `outerRef`\n        ref.current = el; // Share the element for other purposes\n      }}\n    /\u003e\n  );\n};\n```\n\n### Layout Items\n\nReact Cool Virtual is designed to [simplify the styling and keep all the items in the document flow for rows/columns](#basic-usage). However, when working with grids, we need to layout the items in two-dimensional. For that reason, we also provide the [start](#items) property for you to achieve it.\n\n```js\nimport { Fragment } from \"react\";\nimport useVirtual from \"react-cool-virtual\";\n\nconst Grid = () =\u003e {\n  const row = useVirtual({\n    itemCount: 1000,\n  });\n  const col = useVirtual({\n    horizontal: true,\n    itemCount: 1000,\n    itemSize: 100,\n  });\n\n  return (\n    \u003cdiv\n      style={{ width: \"400px\", height: \"400px\", overflow: \"auto\" }}\n      ref={(el) =\u003e {\n        row.outerRef.current = el;\n        col.outerRef.current = el;\n      }}\n    \u003e\n      \u003cdiv\n        style={{ position: \"relative\" }}\n        ref={(el) =\u003e {\n          row.innerRef.current = el;\n          col.innerRef.current = el;\n        }}\n      \u003e\n        {row.items.map((rowItem) =\u003e (\n          \u003cFragment key={rowItem.index}\u003e\n            {col.items.map((colItem) =\u003e (\n              \u003cdiv\n                key={colItem.index}\n                style={{\n                  position: \"absolute\",\n                  height: `${rowItem.size}px`,\n                  width: `${colItem.size}px`,\n                  // The `start` property can be used for positioning the items\n                  transform: `translateX(${colItem.start}px) translateY(${rowItem.start}px)`,\n                }}\n              \u003e\n                ⭐️ {rowItem.index}, {colItem.index}\n              \u003c/div\u003e\n            ))}\n          \u003c/Fragment\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n### Working in TypeScript\n\nReact Cool Virtual is built with [TypeScript](https://www.typescriptlang.org), you can tell the hook what type of your **outer** and **inner** elements are as follows.\n\nIf the outer element and inner element are the different types:\n\n```tsx\nconst App = () =\u003e {\n  // 1st is the `outerRef`, 2nd is the `innerRef`\n  const { outerRef, innerRef } = useVirtual\u003cHTMLDivElement, HTMLUListElement\u003e();\n\n  return (\n    \u003cdiv ref={outerRef}\u003e\n      \u003cul ref={innerRef}\u003e{/* Rendering items... */}\u003c/ul\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nIf the outer element and inner element are the same types:\n\n```tsx\nconst App = () =\u003e {\n  // By default, the `innerRef` will refer to the type of the `outerRef`\n  const { outerRef, innerRef } = useVirtual\u003cHTMLDivElement\u003e();\n\n  return (\n    \u003cdiv ref={outerRef}\u003e\n      \u003cdiv ref={innerRef}\u003e{/* Rendering items... */}\u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n💡 For more available types, please [check it out](src/types/react-cool-virtual.d.ts).\n\n### ResizeObserver Polyfill\n\n[ResizeObserver has good support amongst browsers](https://caniuse.com/?search=ResizeObserver), but it's not universal. You'll need to use polyfill for browsers that don't support it. Polyfills is something you should do consciously at the application level. Therefore React Cool Virtual doesn't include it.\n\nWe recommend using [@juggle/resize-observer](https://github.com/juggle/resize-observer):\n\n```sh\n$ yarn add @juggle/resize-observer\n# or\n$ npm install --save @juggle/resize-observer\n```\n\nThen pollute the `window` object:\n\n```js\nimport { ResizeObserver } from \"@juggle/resize-observer\";\n\nif (!(\"ResizeObserver\" in window)) window.ResizeObserver = ResizeObserver;\n```\n\nYou could use dynamic imports to only load the file when the polyfill is required:\n\n```js\n(async () =\u003e {\n  if (!(\"ResizeObserver\" in window)) {\n    const module = await import(\"@juggle/resize-observer\");\n    window.ResizeObserver = module.ResizeObserver;\n  }\n})();\n```\n\n## To Do... \u003c!-- omit in toc --\u003e\n\n- [ ] Support window scrolling\n- [ ] Leverage the power of [Offscreen API](https://github.com/reactwg/react-18/discussions/19) (maybe...)\n\n## Articles / Blog Posts \u003c!-- omit in toc --\u003e\n\n\u003e 💡 If you have written any blog post or article about React Cool Virtual, please open a PR to add it here.\n\n- Featured on [React Status #243](https://react.statuscode.com/issues/243).\n- Featured on [React Newsletter #270](https://reactnewsletter.com/issues/270).\n\n## Contributors ✨ \u003c!-- omit in toc --\u003e\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"http://wellyshen.com\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/21308003?v=4?s=100\" width=\"100px;\" alt=\"Welly\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eWelly\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#ideas-wellyshen\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e \u003ca href=\"https://github.com/wellyshen/react-cool-virtual/commits?author=wellyshen\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/wellyshen/react-cool-virtual/commits?author=wellyshen\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"#infra-wellyshen\" title=\"Infrastructure (Hosting, Build-Tools, etc)\"\u003e🚇\u003c/a\u003e \u003ca href=\"#maintenance-wellyshen\" title=\"Maintenance\"\u003e🚧\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"http://postalchemy.now.sh\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/49089372?v=4?s=100\" width=\"100px;\" alt=\"Nikita Pilgrim\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eNikita Pilgrim\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-virtual/commits?author=nikitapilgrim\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"http://jiepeng.me\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/10325111?v=4?s=100\" width=\"100px;\" alt=\"Jie Peng\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJie Peng\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-virtual/commits?author=neighborhood999\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://github.com/qister\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/62909771?v=4?s=100\" width=\"100px;\" alt=\"Alex Lyakhnitskiy\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAlex Lyakhnitskiy\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-virtual/commits?author=qister\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"http://adampash.com\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/64131?v=4?s=100\" width=\"100px;\" alt=\"Adam Pash\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAdam Pash\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-virtual/commits?author=adampash\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwellyshen%2Freact-cool-virtual","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwellyshen%2Freact-cool-virtual","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwellyshen%2Freact-cool-virtual/lists"}