npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-infinite-scroll-loader-y

v2.0.0

Published

React component for fetching new data on vertical scroll

Downloads

181

Readme

react-infinite-scroll-loader-y

React component for fetching new data on vertical scroll

NPM JavaScript Style Guide

Install

npm install --save react-infinite-scroll-loader-y

Demo

https://codesandbox.io/s/react-infinite-scroll-loader-y-1g7d0

Usage

  • Using this component looks basically like this:
<InfiniteScroll dataLength={items.length}
                loadMore={page => loadMoreItems(page)}
                hasMore={hasMoreItems}
>
  {
    items.map(item => (
      <div>
        { item }
      </div>
    ))
  }
</InfiniteScroll>

Docs

| Property | Required | Type | Default | Description | | --- | --- | --- | --- | --- | | dataLength | Yes | number | | The length of items. Needed for loading next page. | | batchSize | Yes | number | | The amount of items to load per each request. | | hasMore | Yes | boolean | | Boolean to indicate whether there are more items to load. Setting it to false disables loadMore() function and won't load next page of items. | | loadMore | Yes | (page, { offset, limit }) => void | | Function for loading next page of items. | | threshold | No | number | 250 | Defines minimum space from bottom of your page when new items need to be loaded. | | manualLoadFirstSet | No | boolean | false | Will not load first set of items automatically. Will proceed loading items automatically when first batch is loaded. | | loader | No | React.ReactNode | | Loading component. Can be a simple text, animated icon or more sophisticated React component. | | parentRef | No | RefObject<any> | | Pass ref of parent HTML element if you want scroll-loading to happen inside that HTML Element. Useful for applying scroll loader for example inside modals and specific DIVs. | | resetDependencies | No | any or Array<any> | | Dependencies that will trigger reset of everything | | disabled | No | boolean | false | Disables current component | | beforeEachLoad | No | (reset: fn) => boolean/void | | Function that runs before each render. If it returns true then the next render will not be triggered.

Example

  • Complete example:
import React, { useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-loader-y'

// Mock GET request
function request ({ offset, limit }: { offset: number, limit: number }): Promise<{ data: string[], total: number }> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        data: Array(500).fill(null).map((_, i) => `item-${i + 1}`).slice(offset, offset + limit),
        total: 500,
      });
    }, 500)
  });
}

const App = () => {
  const [items, setItems] = useState<string[]>([]);
  const [hasMoreItems, setHasMoreItems] = useState(true);

  const ITEMS_PER_PAGE = 20;

  // Load next page of items
  const loadMoreItems = async (page: number) => {
    const { data: nextItems, total } = await request({
      offset: ITEMS_PER_PAGE * page,
      limit: ITEMS_PER_PAGE,
    });

    // Combine items
    const combinedItems = items.concat(nextItems || []);

    // Check for more
    setHasMoreItems(!!nextItems?.length && total > combinedItems.length);

    // Save items to state
    setItems(combinedItems);
  };

  return (
    <InfiniteScroll dataLength={items.length}
                    loadMore={page => loadMoreItems(page)}
                    hasMore={hasMoreItems}
                    loader={<div>Loading...</div>}
    >
      {
        items.map(item => (
          <div>
            { item }
          </div>
        ))
      }
    </InfiniteScroll>
  )
};

export default App

Advanced usage

Following example shows complete possible use case of InfiniteScroll together with caching items in react context. It can keep data when browser back button is clicked and query params match for the page. Otherwise cache is invalidated.

/**
 * globalContext.tsx
 * 
 * Global context for keeping data that is used in multiple pages/components.
 * 
 * Use should wrap your code on root level with <GlobalContextProvider>, for example in next.js in _app.tsx file.
 */

import React, { ReactNode, useRef, useState } from 'react';

type Props = {
  children: ReactNode | ReactNode[];
};

type GlobalContextKey = string | number;
type GlobalContextValue = unknown;

type GlobalContextInnerState = {
  set: (key: GlobalContextState) => void;
  get: (key: GlobalContextKey) => GlobalContextValue;
};

// Add keys here for typescript and better suggestion.
type GlobalContextState = Record<GlobalContextKey, GlobalContextValue> & {
  items?: unknown[];
  hasMoreItems?: boolean;
};

export const GlobalContext = React.createContext({} as GlobalContextInnerState & GlobalContextState);

const GlobalContextProvider = ({ children }: Props) => {
  const [state, setState] = useState<GlobalContextState>({
    items: [],
    hasMoreItems: true,
  });
  const lastState = useRef<GlobalContextState>(state);

  const set = (addState: GlobalContextState) => {
    lastState.current = {
      ...lastState.current,
      ...(addState || {}),
    };

    setState(lastState.current);

    return lastState.current;
  };

  const get = (key: string | number) => state[key];

  return <GlobalContext.Provider value={{ set, get, ...state }}>{children}</GlobalContext.Provider>;
};

export default GlobalContextProvider;
/**
 * _app.tsx
 * 
 * Make sure to wrap your code with <GlobalContextProvider> on root level of the app.
 */
import GlobalContextProvider from './globalContext';

...
const MyApp = () => {
  ...
  return (
    <>
      <GlobalContextProvider>
        ...your rest code
      </GlobalContextProvider>
    </>
  )
}

export default MyApp;
/**
 * useGlobalContext.tsx
 * 
 * Hook for using global context.
 *
 * Usage:
 *
 * const globalContext = useGlobalContext();
 * globalContext.set({'key': 'value', 'key2': 'value2', ...});
 */
import { GlobalContext } from './globalContext';
import { useContext } from 'react';

const useGlobalContext = () => {
  const context = useContext(GlobalContext);

  if (!context) {
    throw new Error('useGlobalContext must be used within a GlobalContextProvider');
  }

  return context;
};

export default useGlobalContext;
// Page component that uses InfiniteScroll.

export const Page = () => {
  const setCacheKey = (cacheKey) => set({ cacheKey });
  const cacheKey = get('cacheKey') as string;
  const isCacheApplied = getNewCacheKey() === cacheKey;

  const items = isCacheApplied ? get('items') as unknown[] : [];
  const setItems = (items) => set({ items });

  const hasMoreItems = isCacheApplied ? get('hasMoreItems') as boolean : true;
  const setHasMoreItems = (hasMoreItems) => set({ hasMoreItems });

  const [wasCachedOnInit, setWasCachedOnInit] = useState(isCacheApplied);

  const { query } = useRouter(); // Getting query params in Next.js

  function getNewCacheKey (): string {
    return 'some-generated-cache-key' + JSON.stringify(query);
  }

  const loadMoreItems = async (page: number, initialItems: unknown[] = items) => {
    const { data: nextItems, total } = await request({
      offset: ITEMS_PER_PAGE * page,
      limit: ITEMS_PER_PAGE,
    });

    // Combine items
    const combinedItems = initialItems.concat(nextItems || []);

    // Check for more
    setHasMoreItems(!!nextItems?.length && total > combinedItems.length);

    // Save items to state
    setItems(combinedItems);
  };

  // Set cache on load.
  const infiniteScrollBeforeEachLoad = ({ reset }) => {
    const newItemCacheKey = getNewCacheKey();

    // If url changed (cache key), make new request.
    if (newItemCacheKey !== cacheKey) {
      setCacheKey(newItemCacheKey);
      reset(); // This will reset everything internally in InfiniteScroll component.
      setItems([]);
      loadMoreItems(0, []);
      setHasMoreItems(true);
      window.scrollTo(0, 0);

      // Stop loading next page this time.
      return true;
    }
  };

  return (
    <InfiniteScroll
      dataLength={items.length}
      batchSize={ITEMS_PER_PAGE}
      loadMore={(page) => loadMoreItems(page)}
      hasMore={hasMoreItems}
      loader={<div>Loading...</div>}
      manualLoadFirstSet
      beforeEachLoad={infiniteScrollBeforeEachLoad}
    >
      {
        items.map(item => (
          <div>
            { item }
          </div>
        ))
      }
    </InfiniteScroll>
  )
}

Changelog

  • v1.0.6 - Add height check to container to stop loading more data if containers height is 0.
  • v2.0.0 - Re-implemented. Breaking changes. Removes excess renders and duplicate request on some rare cases.

License

MIT © https://github.com/AlexSapoznikov/react-infinite-scroll-loader-y