Giter Site home page Giter Site logo

vdmrgv / react-easy-infinite-scroll-hook Goto Github PK

View Code? Open in Web Editor NEW
89.0 1.0 5.0 18.6 MB

♾️ A react hook that makes it easy to add infinite scroll in any components. It is very simple to integrate and supports any direction.

Home Page: https://vdmrgv.github.io/react-easy-infinite-scroll-hook/

License: MIT License

JavaScript 2.64% TypeScript 93.09% HTML 0.74% CSS 3.52%
fetch fetching infinite infinite-scroll infinite-scrolling list react react-hooks react-virtualized hooks

react-easy-infinite-scroll-hook's Introduction

react-easy-infinite-scroll-hook

Version Badge NPM license Stars Test GZipped size PRs Welcome Downloads NPM total downloads

A hook that will save you from endless scrolling problems! Infinite scrolling that really works and is very easy to integrate!
This hook allows you to create simple, lightweight components with infinite scrolling in all directions, supporting both windowed and scrollable elements.
Works with any third-party libraries, you just need to specify the correct container for the ref object.

Check out the demo for some examples.

Features

  • Universal - Ability to use all types of HTML elements or any UI or virtualized libraries
  • 📦 Support for all loading directions - You can infinitely scroll a component in any direction or in all directions at once (up, down, left, right)
  • 📏 No need to specify heights - No need to pass the dimensions of the component, scrollbar or element
  • 💬 Support for "chat history" - Reverse mode includes
  • 👫 Cross-browser - Works out-of-the-box for most browsers, regardless of version.
  • ⚙️ Matches native API - Intuitive to use
  • 🛠 Written in TypeScript - It'll fit right into your existing TypeScript project
  • 📲 Mobile-friendly - Supports mobile devices and touch screens.
  • Fully unit tested - 100% test coverage
  • 🌳 Tree-shakeable - Only include the parts you use
  • 💥 Lightweight - Around ~2.4kB
  • 💨 No dependencies

Install

  # with npm
  npm install --save react-easy-infinite-scroll-hook
  # with yarn
  yarn add react-easy-infinite-scroll-hook

Usage

You can create infinite scrolling in any direction and in any pair, for example: up-down, down-right, etc. and even all at once.

Simple Example

Edit useInfiniteScroll

import useInfiniteScroll from 'react-easy-infinite-scroll-hook';

const InfiniteListComponent = ({ isLoading, items, canLoadMore, next }) => {
  // TypeScript example:
  // const ref = useInfiniteScroll<YourElemntType>(...props);
  const ref = useInfiniteScroll({
    // Function to fetch more items
    next,
    // The number of items loaded if you use the "Y-scroll" axis ("up" and "down")
    // if you are using the "X-scroll" axis ("left" and "right") use "columnCount" instead
    // you can also use "rowCount" and "columnCount" if you use "Y-scroll" and "X-scroll" at the same time 
    rowCount: items.length,
    // Whether there are more items to load
    // if marked "true" in the specified direction, it will try to load more items if the threshold is reached
    // support for all directions "up", "down", "left", "right", both individually and in all directions at the same time
    hasMore: { down: canLoadMore },
  });

  return (
    <div
      ref={ref}
      style={{
        height: 500,
        overflowY: 'auto',
      }}
    >
      {items.map((item) => (
        <div key={item.key}>{item.value}</div>
      ))}
      {isLoading && <div>Loading...</div>}
    </div>
  );
};

Virtualized Example (react-virtualized)

This hook supports all react-virtualized components (Collection, Grid, MultiGrid, List, Masonry, Table). Check out the demo for more examples.

Try it live:

Component Description Link
List Virtualized List component with infinite scroll Edit useInfiniteScroll
Grid Virtualized Grid component with infinite scroll down and right Edit useInfiniteScroll

import useInfiniteScroll from 'react-easy-infinite-scroll-hook';
import { List } from 'react-virtualized';

const VirtualizedInfiniteListComponent = ({ isLoading, items, canLoadMore, next }) => {
  const ref = useInfiniteScroll({
    next,
    rowCount: items.length,
    hasMore: { down: canLoadMore },
  });

  return (
    <div>
      <List
        ref={ref}
        width={500}
        height={500}
        rowHeight={60}
        rowCount={items.length}
        rowRenderer={({ key, index, style }) => {
          const item = data[index];

          return (
            <div key={key} style={style}>
              {item}
            </div>
          );
        }}
      />
      {isLoading && <div>Loading...</div>}
    </div>
  );
};

API

After initialization, this hook returns a React ref object, which you must pass to your element ref.

Props

Name Required Description Type Default Value
next Yes A callback when more items are requested by the user. Receives a single parameter specifying the direction to load e.g. (direction) => Promise<void> Function
hasMore Yes Whether there are more items to load. If marked true in the specified direction, it will try to load more items if the threshold is reached. Expect object with directions to load { up: false, down: false, left: false, right: false } object
rowCount Condition Number of items in a vertical list (scroll axis Y). Required if you are using vertical scroll. number
columnCount Condition Number of items in a horizontal list (scroll axis X). Required if you are using horizontal scroll. number
onScroll The callback is called when the container is scrolled: ({ clientHeight: number, scrollHeight: number, scrollTop: number, clientWidth: number, scrollWidth: number, scrollLeft: number }) => void Function
initialScroll The initial scroll position of the element, which is applied after the ref has been initialized object
reverse The direction of the scroll axis is used to create scrolling in the opposite direction, for example when using the CSS style flex-direction: 'row-reverse' object
scrollThreshold The threshold at which the next function is called. It can be specified in pixels from the scrollbar value, for example '200px' and as a percentage of the container size from 0.1 to 1 (1 is 100%) number or string 1
windowScroll When set to true, uses a window as the scroll element. If you are using a scroll window, then anything you pass to the ref will be ignored boolean false

Supported Browsers

react-easy-infinite-scroll-hook aims to support all evergreen browsers and recent mobile browsers for iOS and Android. IE 9+ is also supported.

If you find a browser-specific problem, please report it.

Friends

FAQ

Can I use it with other virtualized or components libraries?

Yes you can! To use it with other libraries you must specify the correct DOM element for the ref object.

Can I use it with flex-direction: 'column-reverse'?

Yes, just pass reverse: { column: true } to props for flex-direction: 'column-reverse' or reverse: { row: true } for flex-direction: 'row-reverse'.

How to use it with react-virtualized MultiGrid component?

MultiGrid is a complex component with a lot of scrollable containers, and to use it you must specify the correct container for the ref object:

import React, { useCallback } from 'react';
import useInfiniteScroll from 'react-easy-infinite-scroll-hook';
import { MultiGrid } from 'react-virtualized';

const VirtualizedInfiniteMultiGridComponent = ({ isLoading, items, canLoadMore, next }) => {
  const ref = useInfiniteScroll({
    next,
    columnCount: items.length,
    hasMore: { right: canLoadMore },
  });

  // Use `useCallback` so we don't recreate the function on each render - Could result in infinite loop
  const selectRef = useCallback(
    (node) => {
      ref.current = node?._bottomRightGrid;
    },
    [ref]
  );

  return (
    <div>
      <MultiGrid ref={selectRef} {...props} />
      {isLoading && <div>Loading...</div>}
    </div>
  );
};

How to use it with react-window?

react-easy-infinite-scroll-hook works with any DOM element, so just specify the correct container for the ref object:

import React, { useCallback } from 'react';
import useInfiniteScroll from 'react-easy-infinite-scroll-hook';
import { FixedSizeList } from 'react-window';

const Component = ({ isLoading, items, canLoadMore, next }) => {
  const ref = useInfiniteScroll({
    next,
    columnCount: items.length,
    hasMore: { right: canLoadMore },
  });

  // Use `useCallback` so we don't recreate the function on each render - Could result in infinite loop
  const setRef = useCallback((node) => {
    if(node)
      ref.current=node._outerRef
  }, []);
  
  return (
    <FixedSizeList
      ref={setRef}
      className="List"
      width={600}
      height={500}
      itemSize={60}
      itemCount={items.length}
    >
      {Row}
    </FixedSizeList>);
};

Contributing

Please read through our contributing guidelines. Included are directions for opening issues, coding standards, and notes on development.

License

Code released under the MIT License.

react-easy-infinite-scroll-hook's People

Contributors

dependabot[bot] avatar vdmrgv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

react-easy-infinite-scroll-hook's Issues

Reverse mode - scroll position jump after first data load

Describe the bug
Scroll position jumps after first setData execution.

Animation

To Reproduce

minimal reproducible demo:

import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react'
import useInfiniteScroll from 'react-easy-infinite-scroll-hook'
import { FixedSizeList } from 'react-window'

export const loadMore = async (length = 50): Promise<string[]> => new Promise((res) => setTimeout(() => res(createItems(length)), 1000))

export const createItems = (length = 100): string[] => {
    return Array.from({ length }).map((_e, i) => `item ${i}`)
}

export const createNext =
    ({ setLoading, setData, offset }: { setData: (v: SetStateAction<string[]>) => void; setLoading: (v: SetStateAction<boolean>) => void; offset: number }) =>
    async () => {
        try {
            setLoading(true)
            const rows = await loadMore(offset)

            setData((prev) => [...rows, ...prev])
        } finally {
            setLoading(false)
        }
    }
const ReversedVerticalList = () => {
    const [data, setData] = useState<string[]>(() => createItems())
    const listRef = useRef<FixedSizeList>()

    const ref = useInfiniteScroll<HTMLDivElement>({
        next: createNext({ setData, setLoading: () => ({}), offset: 50 }),
        rowCount: data.length,
        hasMore: {
            up: true,
            down: false,
        },
        scrollThreshold: '50px',
    })

    const setRVRef = useCallback((RVnode) => {
        if (RVnode) {
            ref.current = RVnode._outerRef
            listRef.current = RVnode
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    useEffect(() => {
        listRef.current?.scrollTo(Number.MAX_SAFE_INTEGER)
    }, [])

    return (
        <>
            <FixedSizeList ref={setRVRef} width={500} height={500} itemSize={20} itemCount={data.length}>
                {({ index, style }) => {
                    const item = data[index]

                    return <div style={style}>{item}</div>
                }}
            </FixedSizeList>
        </>
    )
}

export default ReversedVerticalList

Possible hackfix
problem can be hackfixed by skipping first setData call:

const ReversedVerticalList = () => {
    const [data, _setData] = useState<string[]>(() => createItems())
    const listRef = useRef<FixedSizeList>()
    const [initialLoad, setInitialLoad] = useState(true)

    const setData = (v) => {
        !initialLoad && _setData(v)
    }

    const ref = useInfiniteScroll<HTMLDivElement>({
        next: createNext({ setData, setLoading: () => ({}), offset: 50 }),
        rowCount: data.length,
        hasMore: {
            up: true,
            down: false,
        },
        scrollThreshold: '50px',
    })

    const setRVRef = useCallback((RVnode) => {
        if (RVnode) {
            ref.current = RVnode._outerRef
            listRef.current = RVnode
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    useEffect(() => {
        listRef.current?.scrollTo(Number.MAX_SAFE_INTEGER)
        setInitialLoad(false)
    }, [])

    return (
        <>
            <FixedSizeList ref={setRVRef} width={500} height={500} itemSize={20} itemCount={data.length}>
                {({ index, style }) => {
                    const item = data[index]

                    return <div style={style}>{item}</div>
                }}
            </FixedSizeList>
        </>
    )
}

@vdmrgv could you think of some nicer solution 😊? Thanks.

Infinite loading breaks when scrollbar is dragged

Infinite loading breaks when scrollbar is dragged to the end. It will not load more rows, even after scroll is changed. This bug happens in all examples, but it is most easy to capture in example with prepending items.

Animation

  • OS: Windows 10
  • Browser: Chrome
  • Version: 108.0.5359.95 (64bit)

Add support for react-window

It would be nice, if you could add support for react-window virtualization library. It's slightly more modern alternative for react-virtualized, from the same author. Currently, I only get console error:
Sorry I can't use this container - try using a different DOM element.

Would it be possible to add support?
Thanks.

Can't drag scrollbar on reversed scroll in Safari

It is not possible to drag the scrollbar when scrolling back in Safari

To Reproduce
Steps to reproduce the behavior:

  1. Go to examples page
  2. Click on reverse scroll example
  3. Drag the scrollbar
  4. See error

If initial viewport is not scrollable, next function wouldnt be triggered

I have an api that I fetch exactly 7 items per request, and if the container is not overflowing (the scrollbar isn't present) the next function would not be called. I tried setting the scrollThreshold to 0px and it does work to some degree, but this is not a solution I would like to use. However, when I reduce the viewport width (ex. I resize the window), I would get a scrollbar and the next function would be called as expected. Am I missing something ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.