0.1.0 • Published 5 months ago

react-granular-infinite-scroll v0.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

react granular infinite scroll

Build Status License Version All Contributors

A React hook for implementing infinite scroll with granular control over the trigger point and fallback mechanism for fast scrolling scenarios.

Features

  • šŸŽÆ Multiple ways to control scroll trigger points:
    • Preset percentage-based triggers (50%, 75%, 100%)
    • Automatic adaptation to growing list sizes
  • šŸ”„ Built-in fallback mechanism for fast scrolling scenarios
  • šŸ“ Precise control over scroll trigger positioning
  • āš™ļø Customizable Intersection Observer options
  • šŸŽšļø Dynamic trigger points that adapt to list growth
  • šŸ·ļø Robust index tracking system
  • šŸ“± Works with any scrollable container
  • šŸŽØ Clean and simple API

Usage

import useFetchOnScroll from 'react-granular-infinite-scroll';;

const YourComponent = () => {
  const fetchMore = () => {
    // Your fetch logic here
  };

  const { ItemRef, fallbackRef, itemRefIndex, fallBackRefIndex } = useFetchOnScroll({
    fetchNext: fetchMore,
    numberOfItems: items.length,
    triggerPoint: "75%",  // Will be overridden if triggerIndex is provided
    triggerIndex: Math.floor(items.length / 7)  // Dynamic trigger point
  });

  return (
    <div>
      {items.map((item, index) => (
        <div 
          key={item.id}
          // data-id must be set to index for proper scroll tracking
          data-id={index}  
          ref={index === itemRefIndex ? ItemRef : index === fallBackRefIndex ? fallbackRef : null}
        >
          {item.content}
        </div>
      ))}
    </div>
  );
};

Important Implementation Notes

Required Data Attribute

The hook relies on the data-id attribute to track item indexes during scrolling. When mapping through your items:

// āœ… Correct implementation
{items.map((item, index) => (
  <div 
    key={item.id}
    data-id={index}  // Required: Must be set to the item's index
    ref={index === itemRefIndex ? ItemRef : index === fallBackRefIndex ? fallbackRef : null}
  >
    {item.content}
  </div>
))}

// āŒ Incorrect - missing data-id
{items.map((item, index) => (
  <div 
    key={item.id}
    ref={index === itemRefIndex ? ItemRef : index === fallBackRefIndex ? fallbackRef : null}
    
  >
    {item.content}
  </div>
))}

Usage

import useFetchOnScroll from 'react-granular-infinite-scroll';

const YourComponent = () => {
  const fetchMore = () => {
    // Your fetch logic here
  };

  const { ItemRef, fallbackRef, itemRefIndex, fallBackRefIndex } = useFetchOnScroll({
    fetchNext: fetchMore,
    numberOfItems: items.length,
    triggerPoint: "75%",  // Will be overridden if triggerIndex is provided
    triggerIndex: Math.floor(items.length / 7)  // Dynamic trigger point
  });

  return (
    <div>
      {items.map((item, index) => (
        <div 
          key={item.id}
          ref={index === itemRefIndex ? ItemRef : index === fallBackRefIndex ? fallbackRef : null}
          data-id={index}
        >
          {item.content}
        </div>
      ))}
    </div>
  );
};

Trigger Controls

Dynamic Index-based Triggers

The triggerIndex prop offers superior granular control over scroll triggering by allowing dynamic index calculations. This enables you to:

  1. Create custom percentage-based triggers beyond the standard options:
// Trigger at exactly 33% of the list
useFetchOnScroll({
  numberOfItems: items.length,
  triggerIndex: Math.floor(items.length * 0.33)
});
  1. Implement dynamic trigger points that adapt to list size:
// Trigger point moves further down as list grows
useFetchOnScroll({
  numberOfItems: items.length,
  triggerIndex: Math.floor(items.length / 7)  // Every 7th portion of the list
});

// Example behavior:
// - With 14 items: triggers at index 2
// - With 35 items: triggers at index 5
// - With 70 items: triggers at index 10
  1. Set precise trigger points based on business logic:
// Combining multiple factors
useFetchOnScroll({
  numberOfItems: items.length,
  triggerIndex: calculateTriggerIndex({  // Example of a business logic function
    listLength: items.length,
    viewportHeight: window.innerHeight,
    itemHeight: 50
  })
});

Priority and Override Behavior

āš ļø Important: When both triggerIndex and triggerPoint are provided, triggerIndex always takes precedence and completely overrides the percentage-based triggerPoint. This means:

// triggerPoint will be ignored, fetch triggers at dynamic index
useFetchOnScroll({
  fetchNext: fetchMore,
  numberOfItems: items.length,
  triggerPoint: "75%",    // Ignored
  triggerIndex: Math.floor(items.length / 7)  // Takes effect
});

Standard Percentage-based Triggers

If no triggerIndex is provided, the hook uses percentage-based triggers through the triggerPoint prop:

  • "50%": Triggers fetch when scrolling reaches halfway through the current items
  • "75%": Triggers fetch at three-quarters through the list
  • "100%": Traditional bottom-of-list trigger (default)

Why Fallback Ref?

The fallback reference system is a crucial feature that handles edge cases where users scroll very rapidly through the content. In such scenarios:

  1. The primary observer might miss the intersection event if the user scrolls too quickly past the trigger point
  2. The fallback ref, always placed at the last item, ensures that the fetch trigger isn't missed
  3. The hook maintains a session storage check to prevent duplicate fetches if both observers trigger

Performance Recommendations

Virtualization for Large Lists

When dealing with large lists (500+ items), it's strongly recommended to use this hook in combination with virtualization libraries:

import useFetchOnScroll from 'react-granular-infinite-scroll';
import { FixedSizeList } from 'react-window';

const VirtualizedList = () => {
  const { ItemRef, fallbackRef, itemRefIndex, fallBackRefIndex } = useFetchOnScroll({
    fetchNext: fetchMore,
    numberOfItems: items.length,
    triggerIndex: Math.floor(items.length / 7)
  });

  const Row = ({ index, style }) => (
    <div 
      style={style}
      data-id={index}
      ref={index === itemRefIndex ? ItemRef : index === fallBackRefIndex ? fallbackRef : null}
    >
      {items[index].content}
    </div>
  );

  return (
    <FixedSizeList
      height={500}
      width="100%"
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
};

Recommended virtualization libraries:

  • react-window: Lightweight, modern virtualization library
  • react-virtualized: Full-featured virtualization library with additional components

API

Props

PropTypeDefaultDescription
fetchNext() => voidRequiredFunction to call when more items need to be fetched
fetchMorebooleantrueFlag to enable/disable fetch trigger
numberOfItemsnumberRequiredTotal number of current items
thresholdnumberundefinedIntersection Observer threshold
rootMarginstringundefinedIntersection Observer root margin
triggerPoint"50%" \| "75%" \| "100%""100%"Point at which to trigger the fetch (ignored if triggerIndex is set)
triggerIndexnumberundefinedSpecific item index to trigger fetch. Supports dynamic calculations and overrides triggerPoint when set

Returns

ValueTypeDescription
ItemRefRefCallbackReference for the trigger point item
fallbackRefRefCallbackReference for the fallback (last) item
itemRefIndexnumberIndex where the ItemRef should be placed
fallBackRefIndexnumberIndex where the fallbackRef should be placed

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

If you encounter any issues or have questions, please file an issue on the GitHub repository.

0.1.0

5 months ago