RecyclerListView

repository·master·Indexed 26 days ago

https://github.com/flipkart/recyclerlistview

A high-performance listview library for React Native and Web (v4.2.3) that uses a cell recycling mechanism to efficiently render large or infinite lists. It minimizes memory overhead and prevents frame drops by reusing view objects. The library requires the implementation of a DataProvider, LayoutProvider, and rowRenderer, and supports advanced features such as sticky headers and footers via StickyContainer, stable IDs for data identification, and support for staggered grids and variable height items.

Tokens
7.9K
Snippets
13
Records
36
Agent score
88%

What's inside recyclerlistview

  1. Core Concepts of RecyclerListView

    master

    RecyclerListView is a high-performance listview for React Native and Web that uses "cell recycling" to reuse views that are no longer visible. This prevents the memory overhead and performance degradation associated with creating and destroying large numbers of view objects during scrolling.

    Key features include:

    • Cross Platform: Works on Web (tested with React Native Web).
    • Layout Support: Supports staggered grids, variable height items, and horizontal mode.
    • Performance: Designed for deterministic heights to allow single-pass layout computation.
    • Advanced UI: Supports sticky items (top/bottom), item animations via ItemAnimator, and stable IDs for optimized re-renders.
  2. Configure `renderAheadOffset` for scroll buffering

    master

    The renderAheadOffset prop specifies how much ahead of the current scroll position RLV renders items to prevent blank spaces. This buffer is applied to both the top and bottom of the list.

    • Recommendation: Use the smallest value possible that still prevents blank spaces during scrolling.
    • Lower values: Ensure fewer views are created and are available for recycling more quickly.
    • Higher values: Mount extra views and increase the offset before views are available for recycling, which may help with faster scrolling in specific use cases.
  3. Optimize performance with the `rowHasChanged` method

    master

    When creating a DataProvider, you must provide a rowHasChanged method. This method allows the ViewRenderer to detect if data has changed and skip re-rendering unchanged rows, which is critical for preventing JS thread overload and blank spaces during scrolling.

    Instead of relying solely on object references, it is recommended to use unique item IDs if available.

    this.state = {
        dataProvider: new DataProvider((r1, r2)=> {
            // This is the important part
            return r1 !== r2;
        })
    }
  4. Use RecyclerListView on Web (ReactJS)

    master

    RecyclerListView works with React Native Web out of the box. If you are using ReactJS, you must import the component from the recyclerlistview/web entry point. To avoid path issues, it is recommended to use aliases. The build only includes platform-specific code to minimize bundle size.

    import { RecyclerListView } from "recyclerlistview/web"
  5. Use Stable Ids for data identification

    master

    When performing full page refreshes or switching between cache and network data, use stable IDs to identify data. This helps RLV use the most optimal existing views to render new data instead of relying on default indexes. Stable IDs also support add/remove animations.

    Note: The stableId feature requires version 1.4.0 or higher.

  6. Provide accurate estimated heights for non-deterministic rendering

    master

    If using forceNonDeterministicRendering, ensure the heights and widths provided to the layout manager are as close to the actual values as possible.

    • Mismatch issues: Incorrect estimates lead to relayout cycles, visual glitches, and increased blank areas.
    • Low estimates: May cause unnecessary extra mounts.
    • High estimates: May cause unexpected mounts when RLV realizes there aren't enough views to fill the screen.
  7. Implement Sticky Headers and Footers

    master

    To enable sticky items in RecyclerListView, wrap the component with StickyContainer and provide the indices of the items you want to stick using stickyHeaderIndices and stickyFooterIndices.

    Requirements:

    • stickyHeaderIndices and stickyFooterIndices must be sorted arrays (ascending order).
    • StickyContainer must have exactly one child of type RecyclerListView (or an extension of it).
    • You must pass the ref to RecyclerListView as a function rather than a string.

    Important Note on overrideRowRenderer: If you use overrideRowRenderer, sticky items will revert to their original view (not the overridden view) when scrolling to the very top or very bottom of the content.

    <RecyclerListView ref={this._setRef}/>
    
    _setRef(recycler) {
        this._recyclerRef = recycler;
    }
  8. Implement RecyclerListView using DataProvider, LayoutProvider, and rowRenderer

    master

    To use RecyclerListView, you must implement three core building blocks:

    1. DataProvider: Manages your data and determines if rows have changed. The constructor takes a comparison function (r1, r2) => boolean. For optimal performance, ensure this function correctly identifies if two rows are different.
    2. LayoutProvider: Defines the layout of your list. It requires two functions:
      • An index-to-type function: index => type (e.g., determining if a row is a 'FULL' width or 'HALF' width item).
      • A type-to-dimension function: (type, dim) => void (sets dim.width and dim.height for a given type).
    3. rowRenderer: A function that returns the React component for a specific type and data: (type, data) => ReactElement.

    Note: For grid layouts, it is recommended to use gridlayoutprovider (an export of RLV) starting from v3.0.

    import React, { Component } from "react";
    import { View, Text, Dimensions } from "react-native";
    import { RecyclerListView, DataProvider, LayoutProvider } from "recyclerlistview";
    
    // 1. Define ViewTypes
    const ViewTypes = {
        FULL: 0,
        HALF_LEFT: 1,
        HALF_RIGHT: 2
    };
    
    // 2. Setup DataProvider
    let dataProvider = new DataProvider((r1, r2) => {
        return r1 !== r2;
    });
    
    // 3. Setup LayoutProvider
    let { width } = Dimensions.get("window");
    let layoutProvider = new LayoutProvider(
        index => {
            if (index % 3 === 0) return ViewTypes.FULL;
            return ViewTypes.HALF_LEFT;
        },
        (type, dim) => {
            switch (type) {
                case ViewTypes.FULL: 
                    dim.width = width; 
                    dim.height = 140; 
                    break;
                case ViewTypes.HALF_LEFT: 
                    dim.width = width / 2; 
                    dim.height = 160; 
                    break;
                // ... other types
            }
        }
    );
    
    // 4. Setup rowRenderer
    const rowRenderer = (type, data) => {
        switch (type) {
            case ViewTypes.FULL: 
                return <View><Text>{data}</Text></View>;
            // ... other types
            default: return null;
        }
    };
    
    // 5. Render
    // <RecyclerListView layoutProvider={layoutProvider} dataProvider={dataProvider.cloneWithRows(data)} rowRenderer={rowRenderer} />
  9. Correctly use the `extendedState` prop

    master

    The extendedState prop defines values that rows depend on but reside outside the row data. Changing the extendedState object triggers a re-render of all rows.

    Crucial: Do not pass a new object literal directly to the prop, as this will cause unnecessary re-renders on every parent render cycle. Pass a stable reference from your state or props.

  10. Use forceNonDeterministicRendering for variable item heights

    master

    RecyclerListView performs best when item heights are deterministic and can be computed upfront. If you cannot determine item heights in advance, set the forceNonDeterministicRendering prop to true.

    When enabled, the dimensions provided in the layoutProvider are treated as estimates, and items are allowed to resize. It is recommended to provide good estimates to maintain a smooth user experience.

  11. Implement `shouldComponentUpdate` in row components

    master

    Even with a correct rowHasChanged method, RLV may need to re-render cells to reposition them (e.g., if a row shifts by 1px). To reduce the load on the JS thread, ensure that the components returned by your rowRenderer implement a valid shouldComponentUpdate method.

    rowRenderer(type, data) => {
        return <MyComponent content={data}/>;
    }
    // MyComponent must implement shouldComponentUpdate