Repository: researchgate/react-intersection-list Branch: main Commit: e873ee45e215 Files: 40 Total size: 70.9 KB Directory structure: gitextract_h1ndfqvp/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── build.yml ├── .gitignore ├── .prettierignore ├── .spire/ │ └── spire-plugin-tslint.js ├── .storybook/ │ ├── addons.js │ ├── config.js │ └── preview-head.html ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs/ │ ├── README.md │ ├── docs/ │ │ ├── components/ │ │ │ ├── AsyncList/ │ │ │ │ └── index.js │ │ │ ├── Axis/ │ │ │ │ └── index.js │ │ │ ├── InfiniteList/ │ │ │ │ └── index.js │ │ │ └── style.css │ │ └── index.js │ └── recipes/ │ └── README.md ├── package.json ├── renovate.json ├── src/ │ ├── List.js │ ├── Sentinel.js │ ├── __mocks__/ │ │ └── Sentinel.js │ ├── __tests__/ │ │ ├── List.spec.js │ │ ├── Sentinel.spec.js │ │ ├── __snapshots__/ │ │ │ └── List.spec.js.snap │ │ └── mockSentinel.js │ ├── index.js │ └── utils.js └── types/ ├── index.d.ts ├── test.tsx └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["@researchgate/babel-preset"] } ================================================ FILE: .editorconfig ================================================ # http://EditorConfig.org # top-most EditorConfig file root = true [*] end_of_line = lf trim_trailing_whitespace = true insert_final_newline = false [*.md] trim_trailing_whitespace = false [*.{js,scss}] charset = utf-8 indent_style = space insert_final_newline = true [*.js] indent_size = 4 [*.scss] indent_size = 2 [{*.json,.travis.yml,.*rc}] indent_style = space indent_size = 2 ================================================ FILE: .eslintignore ================================================ node_modules lib .docs types ================================================ FILE: .eslintrc.js ================================================ 'use strict'; // This file was created by spire-plugin-eslint for editor support module.exports = require('@researchgate/spire-config/eslint/react-typescript'); ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Our Pledge In the interest of fostering an open, inclusive, and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, nationality, personal appearance, race, religion, sexual identity and orientation, level of experience, or technical ability. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of different viewpoints and experiences * Gracefully accepting constructive criticism * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Sustained disruption of discussion * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting The ResearchGate community prioritizes (marginalized) people's safety over (privileged) people's comfort. ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instance of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behavior that they deem inappropriate, threatening, offensive, or harmful. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [researchgate@github.com](mailto:researchgate@github.com). All complaints will be reviewed and investigated, and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regards to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is based on [Contributor Covenant][homepage]. [homepage]: http://contributor-covenant.org ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing Guide Thanks for taking the time to contribute! ### Table of contents [Code of Conduct](#code-of-conduct) [Contribution prerequisites](#contribution-prerequisites) [Project Setup](#project-setup) * [How to run the project](#how-to-run-the-project) * [How to run tests](#how-to-run-tests) [How can you contribute](#how-can-you-contribute) * [Reporting bugs](#reporting-bugs) * [Suggesting enhancements](#suggesting-enhancements) * [Writing documentation](#writing-documentation) * [Your first code contribution](#your-first-code-contribution) [Additional Notes](#additional-notes) * [Issue and Pull Request Templates](#issue-and-pull-request-templates) * [Need help?](#need-help?) ## Code of Conduct This project and everyone contributing to it adheres to the [ResearchGate Code of Conduct](CODE_OF_CONDUCT.md). By participating you are expected to uphold this code. Please report any behavior you find unacceptable to [researchgate@github.com](mailto:researchgate@github.com). ## Contribution prerequisites Before you start your work, make sure that you: * have `node` installed at v6.10.0 * have `npm` installed at 5.3.0 * have `yarn` installed at v0.21.3 * are familiar with `git` * are familiar with [conventional commits](http://conventionalcommits.org) * have read and agree to abide by the [ResearchGate Code of Conduct](CODE_OF_CONDUCT.md) ## Project setup ### How to run the project Create a clone of the repository: ``` git clone https://github.com/researchgate/react-intersection-list.git cd react-intersection-list ``` Install the dependencies with: ``` npm install ``` Now run storybook: ``` npm run storybook ``` ### How to run tests This project uses [jest](http://facebook.github.io/jest/) for JavaScript testing. To run tests, execute: ``` yarn test ``` Since coverage is not collected by default when running yarn test, run: ``` yarn coverage ``` To run linters use: ``` yarn lint ``` ## How you can contribute ### Reporting bugs This section guides you through the steps to follow when you submit a bug report for a ResearchGate project. Following these guidelines makes it easy for the maintainers and community to understand your report, reproduce the behavior, and find related reports. Before creating a bug report, please check Open Issues as you may find that there is already an issue open for the bug you’ve found. When you create a bug report, be sure to include as much detail as possible and fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster. After you have submitted the issue, we'll try to get back to you as soon as possible. ### Suggesting enhancements This section guides you through submitting a feature suggestion. All enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/) and, when approved by a core team member or project maintainer, are given the green light to then be turned into a Pull Request. For the best possible experience, please provide us with: * **A clear and descriptive title** * **A step-by-step description of the suggested enhancement** and what it should do * **Specific examples to demonstrate the steps**. Ideally, you should support it with code snippets, screenshots, and/or animated GIFs * **Explanation of why this feature would be useful** to this project * **Your development environment** and context for creating the feature After you have submitted your Pull Request, we'll try to get back to you as soon as possible. ### Writing documentation All great projects require good documentation. There is __always__ room for (better) docs, so why not to contribute to the project by enhancing them? Please do so via Pull Request. ### Your first code contribution Unsure where you can start contributing? We strive to make all our projects easy for beginners to contribute to. Just look out for issues labeled `help-wanted` and `beginner-friendly`, then get stuck in! If you still need some guidance, consider [this resource](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) and/or contact us. ## Additional notes ### Issue and pull request templates When filing an issue or pull request, please take the time to fill out the templates we provide in as much detail as you can. This helps ensure that we have all the information we need to provide you with the right support so that your experience contributing to our open-source project runs as smoothly and easily as you would like it to. ### Need help? If you need any help or require additional information, don't hesitate to contact the project maintainer or any of the contributors. Thank you for contributing! ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ Thanks for taking the time to file an issue with us. Please note that this issue template is used **ONLY** for reporting bugs. If you have an issue that isn’t a bug, please follow the steps listed in the [Contributing](CONTRIBUTING.md). Thanks! ## Expected behavior ## Current behavior ## Steps to reproduce 1. 2. 3. 4. ## Context (environment) * **Version**: * **Platform**: ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Thanks for taking the time to file a pull request with us. Please take a moment to provide the following details: ## Description ## Motivation and context ## How has this been tested? ## Screenshots (if relevant): ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: - [ ] My code follows the code style of this project - [ ] My change requires a change to the documentation - [ ] I have updated the documentation accordingly - [ ] I have read the [CONTRIBUTING](CONTRIBUTING.md) document - [ ] I have added tests to cover my changes - [ ] All new and existing tests passed ================================================ FILE: .github/workflows/build.yml ================================================ name: CI on: push: branches: [ main ] pull_request: branches: [ main ] env: NODE_VERSION: 14 jobs: tests: name: Unit tests runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Cache node_modules uses: actions/cache@v3 id: cache-nodemodules with: path: node_modules key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} - name: Install dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' run: yarn install --frozen-lockfile --non-interactive - name: Unit tests run: yarn test --ci --coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: files: ./src/coverage/coverage-final.json fail_ci_if_error: true lint: name: Lint runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Cache node_modules uses: actions/cache@v3 id: cache-nodemodules with: path: node_modules key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} - name: Install dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' run: yarn install --frozen-lockfile --non-interactive - name: Lint run: yarn lint docs: needs: [tests, lint] if: github.ref == 'refs/heads/main' name: Update docs runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Cache node_modules uses: actions/cache@v3 id: cache-nodemodules with: path: node_modules key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} - name: Install dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' run: yarn install --frozen-lockfile --non-interactive - name: Build latest app run: yarn build - name: Generate storybook documentation run: yarn build:storybook - name: Deploy storybook documentation to gh-pages uses: JamesIves/github-pages-deploy-action@v4.2.5 with: branch: gh-pages # The branch the action should deploy to. folder: .docs # The folder the action should deploy. release: needs: [tests, lint, docs] if: github.ref == 'refs/heads/main' name: Release runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Cache node_modules uses: actions/cache@v3 id: cache-nodemodules with: path: node_modules key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} - name: Install dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' run: yarn install --frozen-lockfile --non-interactive - name: Release run: yarn release env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ npm-debug.log* yarn-error.log /node_modules /lib /src/coverage /.docs .DS_Store ================================================ FILE: .prettierignore ================================================ node_modules/ yarn.lock ================================================ FILE: .spire/spire-plugin-tslint.js ================================================ function tslint({ setState }) { return { name: 'spire-plugin-tslint', async precommit() { setState(state => ({ linters: [ ...state.linters, { '**/*.tsx?': ['tsc', '--project', 'types'] }, ], })); }, }; } module.exports = tslint; ================================================ FILE: .storybook/addons.js ================================================ import '@storybook/addon-options/register'; ================================================ FILE: .storybook/config.js ================================================ import { addParameters, configure } from '@storybook/react'; import { withOptions } from '@storybook/addon-options'; withOptions({ name: 'React Intersection List', url: 'https://github.com/researchgate/react-intersection-list', showDownPanel: false, }); addParameters({ options: { theme: {} } }); configure(() => require('../docs/docs/index.js'), module); ================================================ FILE: .storybook/preview-head.html ================================================ ================================================ FILE: AUTHORS ================================================ Luis Merino Daniel Tschinder Sergey Tatarintsev ================================================ FILE: CHANGELOG.md ================================================ ## [3.0.12](https://github.com/researchgate/react-intersection-list/compare/v3.0.11...v3.0.12) (2021-05-11) ### Bug Fixes * **renovate:** Force default branch to be main ([74fa774](https://github.com/researchgate/react-intersection-list/commit/74fa77474de0c95d94f4bfcf24d7b198bfda98e6)) ## [3.0.11](https://github.com/researchgate/react-intersection-list/compare/v3.0.10...v3.0.11) (2020-08-14) ### Bug Fixes * **security:** Fix security issue with serialize-javascript ([8c3265e](https://github.com/researchgate/react-intersection-list/commit/8c3265e10a4de29a7717edc53f6be7158f41b77d)) ## [3.0.10](https://github.com/researchgate/react-intersection-list/compare/v3.0.9...v3.0.10) (2020-05-04) ### Bug Fixes * fix import path of intersection-observer dependency in sentinel ([#124](https://github.com/researchgate/react-intersection-list/issues/124)) ([659afa8](https://github.com/researchgate/react-intersection-list/commit/659afa8835ede0b07bd49d3d23f8e652312c1dff)) ## [3.0.9](https://github.com/researchgate/react-intersection-list/compare/v3.0.8...v3.0.9) (2020-03-26) ### Bug Fixes * **intersection-observer:** upgrade to latest version and adapt ([1a5fa7c](https://github.com/researchgate/react-intersection-list/commit/1a5fa7c60e1aaab3a4056b4bd1e5049fbb3c7294)) ## [3.0.8](https://github.com/researchgate/react-intersection-list/compare/v3.0.7...v3.0.8) (2019-12-01) ### Bug Fixes * **deps:** lock file maintenance ([#79](https://github.com/researchgate/react-intersection-list/issues/79)) ([fd19604](https://github.com/researchgate/react-intersection-list/commit/fd1960478c2dadb8518c45fc113216168325061a)) ## [3.0.7](https://github.com/researchgate/react-intersection-list/compare/v3.0.6...v3.0.7) (2019-11-23) ### Bug Fixes * **publish:** Do not publish tests ([ef19403](https://github.com/researchgate/react-intersection-list/commit/ef19403259e91b9fd27109d3c56cae5f84679167)) ## [3.0.6](https://github.com/researchgate/react-intersection-list/compare/v3.0.5...v3.0.6) (2019-11-23) ### Bug Fixes * **spire:** change tooling and build system to spire ([36bdcd7](https://github.com/researchgate/react-intersection-list/commit/36bdcd7bc9f61d5c3f33e0e73f0afbdba6cc353f)) # Change Log All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. ## [3.0.5](https://github.com/researchgate/react-intersection-list/compare/v3.0.4...v3.0.5) (2019-05-16) ## [3.0.4](https://github.com/researchgate/react-intersection-list/compare/v3.0.3...v3.0.4) (2019-04-25) ### Bug Fixes * **reobserve:** using externalUnobserver to operate on the right target ([f6d9dc2](https://github.com/researchgate/react-intersection-list/commit/f6d9dc2)) ## [3.0.3](https://github.com/researchgate/react-intersection-list/compare/v3.0.2...v3.0.3) (2019-04-23) ## [3.0.2](https://github.com/researchgate/react-intersection-list/compare/v3.0.1...v3.0.2) (2018-06-18) ## [3.0.1](https://github.com/researchgate/react-intersection-list/compare/v3.0.0...v3.0.1) (2018-05-28) # [3.0.0](https://github.com/researchgate/react-intersection-list/compare/v1.0.2...v3.0.0) (2018-05-15) ### Features * **prop_api_update:** introducing API for 2.0.0 ([276ca7b](https://github.com/researchgate/react-intersection-list/commit/276ca7b)) * **React16.4:** Migrated codebase to stop using legacy lifecycles ([ab7fc85](https://github.com/researchgate/react-intersection-list/commit/ab7fc85)) ### BREAKING CHANGES * **prop_api_update:** * Replace `currentLength` with more descriptive `itemCount` prop * New render prop `renderItem` to completement `children` as render prop * New prop `items` as a unique identifier/symbol to provide an easier integration of reusable lists * TypeScript Module definitions # [2.0.0](https://github.com/researchgate/react-intersection-list/compare/v1.0.2...v2.0.0) (2018-05-09) ### Features * **prop_api_update:** introducing API for 2.0.0 ### BREAKING CHANGES * **prop_api_update:** * Replace `currentLength` with more descriptive `itemCount` prop * New render prop `renderItem` to completement `children` as render prop * New prop `items` as a unique identifier/symbol to provide an easier integration of reusable lists * TypeScript Module definitions ## [1.0.2](https://github.com/researchgate/react-intersection-list/compare/v1.0.1...v1.0.2) (2018-04-12) ## [1.0.1](https://github.com/researchgate/react-intersection-list/compare/v1.0.0...v1.0.1) (2018-02-26) # [1.0.0](https://github.com/researchgate/react-intersection-list/compare/v0.4.1...v1.0.0) (2017-11-24) ### Features * **React16:** support for React 16 ([12ba423](https://github.com/researchgate/react-intersection-list/commit/12ba423)) * **setRootNode:** save call to getComputedStyle if root node is unchanged ([80659dd](https://github.com/researchgate/react-intersection-list/commit/80659dd)) ### BREAKING CHANGES * **React16:** tag replaced by a tag. * **React16:** deprecation warning will not longer appear for itemsLength prop. ## [0.4.1](https://github.com/researchgate/react-intersection-list/compare/v0.4.0...v0.4.1) (2017-10-11) * Update @researchgate/react-intersection-observer dependency to the latest version # [0.4.0](https://github.com/researchgate/react-intersection-list/compare/v0.3.2...v0.4.0) (2017-10-10) ### Features * **currentLength:** replace itemsLength with more explicit currentLength prop name ([de19504](https://github.com/researchgate/react-intersection-list/commit/de19504)) ## [0.3.2](https://github.com/researchgate/react-intersection-list/compare/v0.3.1...v0.3.2) (2017-09-23) ### Bug Fixes * **dependencies:** Remove rimraf from dependencies ([b78adde](https://github.com/researchgate/react-intersection-list/commit/b78adde)) ## [0.3.1](https://github.com/researchgate/react-intersection-list/compare/v0.3.0...v0.3.1) (2017-09-20) # 0.3.0 (2017-09-14) ### Features * **core_dep_update:** update react-intersection-observer to latest version ([201c2d1](https://github.com/researchgate/react-intersection-list/commit/201c2d1)) ## [0.1.1](https://github.com/researchgate/react-intersection-list/compare/v0.1.0...v0.1.1) (2017-09-12) ### Bug Fixes * **sentinel:** ensure sentinel re-observes after items render ([e6d0fa5](https://github.com/researchgate/react-intersection-list/commit/e6d0fa5)) # 0.1.0 (2017-09-11) ### Features * **awaitMore:** improved API with awaitMore ([6c58e95](https://github.com/researchgate/react-intersection-list/commit/6c58e95)) * **hasMore:** prop that allows to bypass the length-size check ([f67ab3d](https://github.com/researchgate/react-intersection-list/commit/f67ab3d)) ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) @researchgate/react-intersection-list authors 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 ================================================ ⚠️ This repository is not as actively maintained as we wish it to be. Feel free to fork this project and fix any outstanding issues you might have, and we'll try to merge relevant changes eventually. We apologize for the inconvenience.

React Intersection List

Build Status Codecov NPM version styled with prettier


> **Agent Smith:** ...we have no choice but to continue as planned. Deploy the > sentinels. Immediately. **React Intersection List** builds on top of **[React Intersection Observer](https://github.com/researchgate/react-intersection-observer)**, using a [sentinel](https://en.wikipedia.org/wiki/Sentinel_value) in the DOM to deliver a high-performance and smooth scrolling experience, even on low-end devices.
Table of Contents - [Getting Started](#getting-started) - [Why React Intersection List?](#why-react-intersection-list) - [Documentation](#documentation) - [How to](#how-to) - [FAQ](#faq) - [Props](#props) - [Examples](#examples) - [Contributing](#contributing)
## Getting Started ``` $ npm install --save @researchgate/react-intersection-list ``` And optionally the [polyfill](https://github.com/w3c/IntersectionObserver/tree/gh-pages/polyfill): ``` $ npm install --save intersection-observer ``` Next create a `` and two instance methods as props `children` and `itemRenderer`: ```jsx import React, { Component } from 'react'; import List from '@researchgate/react-intersection-list'; export default class InfiniteList extends Component { itemsRenderer = (items, ref) => (
    {items}
); itemRenderer = (index, key) =>
  • {index}
  • ; render() { return ( ); } } ``` Note that `` is a `PureComponent` so it can keep itself from re-rendering. It's highly recommended to avoid creating new functions for `renderItem` and `itemsRenderer` so that it can successfully shallow compare props on re-render. ## Why React Intersection List? The approach to infinite scrolling was commonly done by devs implementing throttled `scroll` event callbacks. This keeps the main thread unnecessarily busy... No more! `IntersectionObservers` invoke callbacks in a **low-priority and asynchronous** way by design. > **Agent Smith:** Never send a human to do a machine's job. The implementation follows these steps: 1. Add a sentinel close to the last item in the list 2. Update the list moving the internal cursor 3. Trigger a callback when the sentinel comes into view 4. Reposition the recycled sentinel at the end 5. Repeat (∞) ? ## Documentation ### How to Provided an `itemsRenderer` prop you must attach the `ref` argument to your scrollable DOM element: ```jsx
    {items}
    ``` This element specifies `overflow: auto|scroll` and it'll become the `IntersectionObserver root`. If the `overflow` property isn't found, then `window` will be used as the `root` instead. The `sentinel` element is by default detached from the list when the current size reaches the available length, unless you're using `awaitMore`. In case your list is in memory and you rely on the list for incremental rendering only, the default detaching behavior suffices. If you're loading more items in an asynchoronous way, make sure you switch `awaitMore` once you reach the total length (bottom of the list). The prop `itemCount` must be used if the prop `items` is not provided, and viceversa. Calculating the list size is done by adding the current size and the page size until the items' length is reached. ### FAQ
    Why am I receiving too many `onIntersection` callbacks We extend `PureComponent`. That means, if the parent component re-renders and the _props_ passed to your `` don't hold the same reference anymore, the list re-renders and we accidentally restart the `IntersectionObserver` of the `Sentinel`.

    Do I always need to assign the `ref`? Yes, the ref callback will be used as the `root` and is forwarded to the `IntersectionObserver` within the `Sentinel`.

    What's the `threshold` value, and why does it need a *unit*? The `threshold` value is the amount of space needed before the `sentinel` intersects with the root. The prop is transformed into a valid `rootMargin` property for the `IntersectionObserver`, depending on the `axis` you select. As a sidenote, we believe that a percentage unit works best for responsive layouts.

    I am getting a console warning when I first load the list
    The sentinel detected a viewport with a bigger size than the size of its items...
    The prop `pageSize` is `10` by default, so make sure you're not falling short on items when you first render the component. The idea of an infinite scrolling list is that items overflow the viewport, so that users have the impression that there're always more items available.

    Why doesn't the list render my updated list element(s)? The list renders items based on its props. An update somewhere else in your app (or within your list item) might update your list element(s), but if your list's `currentLength` prop for instance, remains unchanged, the list prevents a re-render. Updating the entire infinite list when one of its items has changed is far from optimal. Instead, update each item individually with some form of `connect()` function or observables.

    Are you planning to implement a "virtual list mode" like react-virtualized? Yes, there's already an [open issue](https://github.com/researchgate/react-intersection-list/issues/2) to implement a mode using occlusion culling. It will be implemented in a future release. If you can't wait, you could help us out by opening a Pull Request :)
    ### Props | property | type | default | description | | --------------------- | ------------------------------------------------------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `renderItem/children` | `(index: number, key: number) => React.Element` | `(index, key) =>
    {index}
    ` | render function as children or render props;
    gets call once for each item. | | `itemsRenderer` | `(items: Array(React.Element), ref: HTMLElement) => React.Element` | `(items, ref) =>
    {items}
    ` | render function for the list's
    root element, often returning a scrollable element. | | `itemCount/items` | `number/Array (or Iterable Object)` | `0` | item count to render. | | `awaitMore` | `boolean` | | if true keeps the sentinel from detaching. | | `onIntersection` | `(size: number, pageSize: number) => void` | | invoked when the sentinel comes into view. | | `threshold` | `string` | `100px` | value in absolute `px` or `%`
    as spacing before the sentinel hits the edge of the list's viewport. | | `axis` | `string` | `y` | scroll direction: `y` == vertical and `x` == horizontal | | `pageSize` | `number` | `10` | number of items to render each hit. | | `initialIndex` | `number` | `0` | start position of iterator of items. | ### Examples Find multiple examples under: [https://researchgate.github.io/react-intersection-list/](https://researchgate.github.io/react-intersection-list/) ## Contributing We'd love your help on creating React Intersection List! Before you do, please read our [Code of Conduct](.github/CODE_OF_CONDUCT.md) so you know what we expect when you contribute to our projects. Our [Contributing Guide](.github/CONTRIBUTING.md) tells you about our development process and what we're looking for, gives you instructions on how to issue bugs and suggest features, and explains how you can build and test your changes. **Haven't contributed to an open source project before?** No problem! [Contributing Guide](.github/CONTRIBUTING.md) has you covered as well. ================================================ FILE: docs/README.md ================================================ ## Documentation Welcome to the code docs section! Here you'll find some implementation use cases: * Visit our [examples](https://researchgate.github.io/react-intersection-list/) page * Take a look at the [recipes](./recipes) page ================================================ FILE: docs/docs/components/AsyncList/index.js ================================================ import React, { Component } from 'react'; import List from '../../../../src'; const PAGE_SIZE = 20; export default class AsyncList extends Component { state = { awaitMore: true, isLoading: false, currentPage: 0, repos: [], }; feedList(repos) { this.setState({ awaitMore: repos.length > 0, isLoading: false, repos: [...this.state.repos, ...repos], }); } handleLoadMore = () => { const currentPage = this.state.currentPage + 1; this.setState({ isLoading: true, currentPage, }); const url = 'https://api.github.com/users/researchgate/repos'; const qs = `?type=public&per_page=${PAGE_SIZE}&page=${currentPage}`; const headers = { 'Accept-Encoding': '', }; const ifNoneMatch = sessionStorage.getItem(`etag_${currentPage}`); if (ifNoneMatch) { headers['If-None-Match'] = ifNoneMatch.match(/(?:\d|[a-z])+/)[0]; } let hasError = false; fetch(url + qs, { headers }) .then((response) => { if (!(response.status !== 200)) { const etag = response.headers.get('etag'); if (etag) { sessionStorage.setItem(`etag_${currentPage}`, etag); } } else { hasError = true; } return response.json(); }) .then((repos) => { if (hasError) { throw new Error(repos.message); } this.feedList( repos.filter((repo) => repo.fork === false && repo.language) ); }) .catch((err) => { console.error(err); // eslint-disable-line this.feedList([]); }); }; renderItems = (items, ref) => (
    {items}
    ); renderItem = (index, key) => { const repo = this.state.repos[index]; return (
    {repo.name} <{repo.language}>
    ); }; render() { return (
    {this.state.isLoading &&
    Loading
    }
    ); } } ================================================ FILE: docs/docs/components/Axis/index.js ================================================ import React from 'react'; import List from '../../../../src'; const itemsRenderer = (items, ref) => (
    {items}
    ); // eslint-disable-next-line react/no-multi-comp const Axis = () => ( ); export default Axis; ================================================ FILE: docs/docs/components/InfiniteList/index.js ================================================ import React from 'react'; import List from '../../../../src'; const itemsRenderer = (items, ref) => (
    {items}
    ); // eslint-disable-next-line react/no-multi-comp const InfiniteList = () => ( ); export default InfiniteList; ================================================ FILE: docs/docs/components/style.css ================================================ body { font-family: -apple-system, '.SFNSText-Regular', 'San Francisco', Roboto, 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; margin: 0; padding: 0; color: #555; -webkit-font-smoothing: antialiased; min-height: 100%; } .loading { background: #0080ff; color: #fff; height: 40px; font-size: 20px; line-height: 40px; text-transform: capitalize; font-weight: bold; text-align: center; position: absolute; top: 0; left: 0; right: 0; z-index: 1; pointer-events: none; } .list { box-sizing: border-box; height: 100vh; background-color: #b2d2ed; overflow: auto; position: relative; } .list--horizontal { width: 100%; display: flex; overflow-y: visible; flex-direction: row; } .list--horizontal > div { min-width: 100px; text-align: center; } .list--compact { height: 333px; } .list > div { background-color: #4b9deb; color: #fff; margin: 15px; padding: 15px; font-size: 16px; text-transform: capitalize; cursor: default; white-space: nowrap; } .list > div > a { color: white; text-decoration: none; margin-right: 8px; } .list > div > a:hover { text-decoration: underline; } ================================================ FILE: docs/docs/index.js ================================================ import 'intersection-observer'; // eslint-disable-line import/no-extraneous-dependencies import 'whatwg-fetch'; // eslint-disable-line import/no-extraneous-dependencies import React from 'react'; import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies import InfiniteList from './components/InfiniteList'; import AsyncList from './components/AsyncList'; import Axis from './components/Axis'; import './components/style.css'; storiesOf('Examples', module) .add('Synchoronous', InfiniteList) .add('Asynchoronous', () => ) .add('X-Axis', Axis); ================================================ FILE: docs/recipes/README.md ================================================ ## Recipes ### Asynchonous Repo List When the sentinel comes into view, you can use the callback to load data, create the next items, and attach them. For this case we're loading Github repositories with pagination. We assume that we don't know the total length and we'll want to keep fetching until the (unknown) end of the list. The solution here is to pass the prop `awaitMore:bool = true`, so that the sentinel awaits for more items. ```jsx import React from 'react'; import List from '@researchgate/react-intersection-list'; const PAGE_SIZE = 20; export default class extends React.Component { state = { awaitMore: true, isLoading: false, currentPage: 0, repos: [], }; feedList = repos => { this.setState({ awaitMore: repos.length > 0, isLoading: false, repos: [...this.state.repos, ...repos], }); }; handleLoadMore = () => { const currentPage = this.state.currentPage + 1; this.setState({ isLoading: true, currentPage, }); const url = 'https://api.github.com/users/researchgate/repos'; const qs = `?type=public&per_page=${PAGE_SIZE}&page=${currentPage}`; fetch(url + qs) .then(response => response.json()) .then(this.feedList) .catch(err => { throw err; }); }; renderItems = (items, ref) => (
    {items}
    ); renderItem = (index, key) => { const repo = this.state.repos[index]; return (
    {repo.name}
    ); }; render() { return (
    {this.state.isLoading &&
    Loading
    }
    ); } } ``` If the total amount of items are prefetched and available, we won't need `awaitMore` and the `pageSize` will be used to paginate results until we reach the bottom of the list. ### Infinite Synchronous List ```jsx import React from 'react'; import List from '@researchgate/react-intersection-list'; export default () => {(index, key) =>
    {index}
    }
    ; ``` ### Can I submit a new recipe? Yes, of course! 1. Fork the code repo. 2. Create your new recipe in the correct subfolder within `./docs/docs/components/` (create a new folder if it doesn't already exist). 3. Make sure you have included a README as well as your source file. 4. Submit a PR. _If you haven't yet, please read our [contribution guidelines](https://github.com/researchgate/react-intersection-list/blob/master/.github/CONTRIBUTING.md)._ ### What license are the recipes released under? By default, all newly submitted code is licensed under the MIT license. ### How else can I contribute? Recipes don't always have to be code - great documentation, tutorials, general tips and even general improvements to our examples folder are greatly appreciated. ================================================ FILE: package.json ================================================ { "name": "@researchgate/react-intersection-list", "description": "React List Component using the Intersection Observer API", "version": "3.0.12", "author": "Luis Merino ", "engines": { "node": ">=10.0.0" }, "bugs": { "url": "https://github.com/researchgate/react-intersection-list/issues" }, "dependencies": { "@researchgate/react-intersection-observer": "^1.1.2", "prop-types": "^15.7.2", "warning": "^4.0.3" }, "devDependencies": { "@babel/cli": "7.20.7", "@babel/core": "7.20.12", "@researchgate/babel-preset": "2.0.14", "@researchgate/spire-config": "7.0.0", "@storybook/addon-options": "5.3.21", "@storybook/react": "6.3.12", "@types/react": "17.0.53", "babel-loader": "8.3.0", "cross-env": "7.0.3", "intersection-observer": "0.12.2", "react": "16.14.0", "react-dom": "16.14.0", "react-test-renderer": "16.14.0", "spire": "4.1.2", "spire-plugin-semantic-release": "4.1.0", "typescript": "4.9.5", "whatwg-fetch": "3.6.2" }, "files": [ "lib", "types/index.d.ts" ], "types": "types/index.d.ts", "homepage": "https://github.com/researchgate/react-intersection-list#readme", "keywords": [ "Intersection", "Observer", "react", "component", "list", "infinite", "scrollable", "researchgate" ], "license": "MIT", "main": "lib/js/index.js", "module": "lib/es/index.js", "peerDependencies": { "react": "^16.3.2", "react-dom": "^16.3.2" }, "repository": { "type": "git", "url": "https://github.com/researchgate/react-intersection-list.git" }, "jest": { "rootDir": "src", "testMatch": [ "**/__tests__/**/*.spec.js" ], "testURL": "http://localhost/" }, "prettier": "@researchgate/prettier-config", "spire": { "extends": [ [ "@researchgate/spire-config", { "eslint": "react-typescript" } ] ], "plugins": [ "spire-plugin-semantic-release", "/.spire/spire-plugin-tslint" ] }, "scripts": { "build": "yarn build:js && yarn build:es", "build:js": "cross-env BABEL_ENV=production BABEL_OUTPUT=cjs babel src --out-dir lib/js --ignore **/__tests__,**/__mocks__", "build:es": "cross-env BABEL_ENV=production BABEL_OUTPUT=esm babel src --out-dir lib/es --ignore **/__tests__,**/__mocks__", "build:storybook": "build-storybook -o .docs", "lint": "spire lint", "prepublishOnly": "yarn build", "release": "spire release --branches main", "storybook": "start-storybook -p 9001 -c .storybook", "test": "spire test", "ts:check": "tsc --project types" } } ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["@researchgate:lib"], "automergeType": "branch", "labels": ["dependencies"], "baseBranches": ["main"] } ================================================ FILE: src/List.js ================================================ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import warning from 'warning'; import Sentinel from './Sentinel'; import { getItemCount, computeSize } from './utils'; const AXIS_CSS_MAP = { x: 'overflowX', y: 'overflowY' }; class List extends PureComponent { static propTypes = { awaitMore: PropTypes.bool, axis: PropTypes.oneOf(['x', 'y']), children: PropTypes.func, initialIndex: PropTypes.number, items(props, propName) { const object = props[propName]; if ( object != null && !( Array.isArray(object) || typeof object[Symbol.iterator] === 'function' ) ) { return new Error( `\`${propName}\` must be of type Array or a native type implementing the iterable interface` ); } return undefined; }, itemCount: PropTypes.number, itemsRenderer: PropTypes.func, onIntersection: PropTypes.func, pageSize: PropTypes.number, renderItem: PropTypes.func, threshold: PropTypes.string, }; static defaultProps = { axis: 'y', initialIndex: 0, itemsRenderer: (items, ref) =>
    {items}
    , pageSize: 10, threshold: '100px', }; state = { size: 0, }; static getDerivedStateFromProps({ pageSize, ...props }, prevState) { const itemCount = getItemCount(props, true); let newSize; if (prevState.size === 0) { newSize = pageSize; } else { if (prevState.itemCount < itemCount) { newSize = prevState.size + pageSize; } else { newSize = prevState.size + -(prevState.pageSize - pageSize); } } return { pageSize, itemCount, size: computeSize(newSize, itemCount), }; } componentDidMount() { this.prematureIntersectionChecked = this.state.size === 0; } setRef = (callback) => { let prevRootNode; this.setRootNode = (node) => { if (node !== prevRootNode) { prevRootNode = node; const overflow = window.getComputedStyle(node)[ AXIS_CSS_MAP[this.props.axis] ]; callback( ['auto', 'scroll', 'overlay'].indexOf(overflow) !== -1 ? node : null ); } }; }; handleUpdate = ({ isIntersecting }) => { const { awaitMore, onIntersection } = this.props; const { size, itemCount, pageSize } = this.state; if (!this.prematureIntersectionChecked) { this.prematureIntersectionChecked = true; warning( !isIntersecting, 'ReactIntersectionList: the sentinel detected a viewport with a bigger size than the size of its items. ' + 'This could lead to detrimental behavior, e.g.: triggering more than one onIntersection callback at the start.\n' + 'To prevent this, use either a bigger `pageSize` value or avoid using the prop awaitMore initially.' ); } if (isIntersecting) { const nextSize = computeSize(size + pageSize, itemCount); if (size !== nextSize) { this.setState({ size: nextSize }); } if (onIntersection && (!awaitMore || this.awaitIntersection)) { if (this.awaitIntersection) { this.awaitIntersection = false; } onIntersection(nextSize, pageSize); } } }; getItemRenderer() { const { children, renderItem } = this.props; const hasChildren = typeof children !== 'undefined'; const hasRender = typeof renderItem !== 'undefined'; warning( !(hasChildren && hasRender), 'ReactIntersectionList: cannot use children and renderItem props as render function at the same time.' ); if (hasChildren) { return children; } return hasRender ? renderItem : (index, key) =>
    {index}
    ; } renderItems() { const { awaitMore, axis, initialIndex, itemsRenderer, threshold, } = this.props; const { size, itemCount } = this.state; const itemRenderer = this.getItemRenderer(); const items = []; for (let i = 0; i < size; ++i) { items.push(itemRenderer(initialIndex + i, i)); } let sentinel; if (size < itemCount || awaitMore) { sentinel = ( ); items.push(sentinel); if (awaitMore) { this.awaitIntersection = true; } } return itemsRenderer(items, (node) => { if (node && sentinel) { this.setRootNode(node); } }); } render() { return this.renderItems(); } } export default List; ================================================ FILE: src/Sentinel.js ================================================ import React, { Component } from 'react'; import Observer from '@researchgate/react-intersection-observer'; import PropTypes from 'prop-types'; import { computeRootMargin } from './utils'; class Sentinel extends Component { static propTypes = { axis: PropTypes.oneOf(['x', 'y']).isRequired, threshold: PropTypes.string.isRequired, setRef: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, }; constructor(props) { super(props); this.state = { rootElement: undefined, rootMargin: computeRootMargin(props), }; this.target = (