question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Dynamic added data eg RecyclerListView with pagination

See original GitHub issue

Ok after working for hours im not able to find a solution yet.

I created a custom component where i used RecyclerListView to add items when onEndReached performed.

The problem is that the RecyclerListView work the first time but on the second time eg when onEndReached is triggered and i append the data to the current itemlist. RecyclerListView simple dose not render and i get this warning.

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in RecyclerListView (at itemList.tsx:103)
    in RCTView (at View.js:34)
    in View (at itemList.tsx:102)
    in ItemList (at home.tsx:142)

This is the custom controller

import {
  RecyclerListView,
  LayoutProvider,
  DataProvider,
  BaseItemAnimator,
} from 'recyclerlistview/dist/reactnative';

import {
  Dimensions,
  StyleSheet,
  View,
  Text,
  LayoutAnimation,
  TouchableHighlight,
} from 'react-native';
import React, {useState, useEffect, useRef} from 'react';

export type RenderItem = (type: any, item: any, index: any) => JSX.Element;
export type ItemPress = (item: any, index: number) => void;

const ItemList = ({
  items,
  renderItem,
  keyName,
  onItemPress,
  seperator,
  onEndReached,
  itemHeight,
  columnPerRaw,
  onIni,
}: {
  items: any;
  renderItem: RenderItem;
  keyName: string;
  onItemPress: ItemPress;
  seperator?: Boolean;
  onEndReached?: Function;
  itemHeight?: number;
  columnPerRaw?: number;
  onIni?: Function;
}) => {
  const {height, width} = Dimensions.get('window');
  var recyclerListViewRef = React.createRef();
  const [dataSource, setDataSource] = useState(
    new DataProvider((r1, r2) => {
      return r1 !== r2;
    }).cloneWithRows(items),
  );


  // when items update create new provider
  useEffect(() => {
    if (items && items.length > 0)
        setDataSource(dataSource.cloneWithRows(items));
  }, [items]);

  //The layout provider must be provided with two methods. The first method is the get layout based on index which determines the layout type
  //based on the index. In the second method, the layout's estimated size i.e its height and width is specified on basis of the layout type.
  const layoutProvider = new LayoutProvider(
    (index) => {
      return 0;
    },
    (type, dim) => {
      dim.width = width / (columnPerRaw ?? 1);
      dim.height = itemHeight ?? 30;
    },
  );

  const seperatorStyle = () => {
    return {
      height: 1,
      width: Dimensions.get('window').width,
      backgroundColor: '#dddddd',
    };
  };

  const handleListEnd = () => {
    if (onEndReached) onEndReached();
  };
  const itemGetter = (type: any, item: any, index: any, extendedState: any) => {
    return (
      <View style={styles.container} key={index}>
        <TouchableHighlight
          onPress={() => onItemPress(item, index)}
          underlayColor="#dddddd">
          <View>
            {renderItem(type, item, index)}
            {seperator === true ? <View style={seperatorStyle()} /> : null}
          </View>
        </TouchableHighlight>
      </View>
    );
  };
  console.log(items[0]);
  return (<>
    <View style={styles.listContainer}>
      <RecyclerListView
        ref={(c) => {
          recyclerListViewRef = c;
          if (onIni) onIni(c);
        }}
        rowRenderer={itemGetter}
        dataProvider={dataSource}
        layoutProvider={layoutProvider}
        forceNonDeterministicRendering={false}
        useWindowScroll={true}
        onEndReached={handleListEnd}
      />
    </View>

     </>
  );
};

export default ItemList;

const styles = StyleSheet.create({
  container: {
    justifyContent: 'space-around',
    alignItems: 'center',
    flex: 1,
    margin: 5,
  },

  listContainer: {
    flex: 1  
  },
});


And here is where im using it.


import React, {useContext, useState, useEffect, useRef} from 'react';
import item from '../cl/lightItem';
import {
  StyleSheet,
  ScrollView,
  View,
  Text,
  Image,
  NativeScrollEvent,
  ActivityIndicator,
  TouchableHighlight,
  Dimensions,
} from 'react-native';
import httpClient from '../cl/http';
import AppContext from '../context/appContext';
import appContext from '../interface/appcontext';
import ItemList from './itemList';

const filter = (items: item[], data: item[]): item[] => {
  return items.filter((x) => !data.find((a: item) => a.title === x.title));
};
const Home = ({navigation}: {navigation?: any}) => {
  const [data, setData] = useState([] as item[]);
  const [isLoading, setIsLoading] = useState(false);
  const [page, setPage] = useState(0);
  const [endResult, setEndResult] = useState(false);
  const [effectTrigger, setEffectTrigger] = useState(0);
  const [columnPerRar, setColumnPerRaw] = useState(3);
  const [scroll, setScroll] = useState({} as {scrollToTop: Function});
  const globalContext = useContext(AppContext);
  var width = Dimensions.get('window').width;

  globalContext.push({
    name: 'Home',
    action:async () => {
     setPage(0);
     setEndResult(false);
     setEffectTrigger(Math.random());
    },
  });

  useEffect(() => {
     let isCancelled = false;
     setIsLoading(true);
     setPage(page + 1);
    const fetchData = async () => {
   
  
     if (endResult && page > 1) return;
      console.log("fetching data");
      let items = await (page > 1
        ? globalContext.value?.latest(page)
        : globalContext.value?.latest(page));
        if (isCancelled)
           return
      items = filter(items, data);
      if (items.length <= 0) {
        setEndResult(true);
        setData(page > 1 ? data : []);
        setIsLoading(false);
   
        console.log("fetching data finished");
        return;
      }

      if (page > 1) items = data.concat(items as []);

      setData(items);
      setIsLoading(false);

      console.log("fetching data finished");      
    };

    fetchData();
    return () => {
      isCancelled = true;
    };
  }, [effectTrigger]);
  useEffect(() => {
    Dimensions.addEventListener('change', async (e) => {
      if (width > Dimensions.get('window').width) await setColumnPerRaw(3);
      else await setColumnPerRaw(4);
      width = Dimensions.get('window').width;

      // await setPage(0);
      // await setData([]);
      // setEffectTrigger(Math.random());
    });
  }, []);
  function isCloseToBottom(nativeEvent: NativeScrollEvent) {
    const paddingToBottom = 5;
    return (
      nativeEvent.layoutMeasurement.height + nativeEvent.contentOffset.y >=
      nativeEvent.contentSize.height - paddingToBottom
    );
  }

  function itemClick(x: item) {
    globalContext.setSelectedItem(x);
    navigation.navigate('Detali');
  }

  const renderItem = (type: any, item: item, index: any) => {
    return (
      <TouchableHighlight onPress={() => itemClick(item)}>
        <View style={styles.product}>
          <Image style={styles.image} source={{uri: item.image}}></Image>
          <View style={styles.footer}>
            <Text
              style={{
                fontSize: 8,
                overflow: 'hidden',
                textAlign: 'center',
                width: 90,
              }}>
              {item.title}
            </Text>
          </View>
        </View>
      </TouchableHighlight>
    );
  };

  return (
    <View style={styles.container}>
      {data && data.length > 0? (
        <ItemList
          columnPerRaw={columnPerRar}
          itemHeight={150}
          onIni={(s: any) => {
            setScroll(s);
          }}
          onItemPress={(item, index) =>
            alert('you clicked on ' + item.name + ' with index ' + index)
          }
          items={data ?? []}
          renderItem={renderItem}
          onEndReached={() => {
            if (globalContext.value.panination) setEffectTrigger(Math.random());
          }}
          keyName="name"
        />
      ) : null}
      {isLoading ? (
        <ActivityIndicator
          style={styles.loading}
          size="large"
          color="#0000ff"
        />
      ) : null}
    </View>
  );
};

const styles = StyleSheet.create({
  loading: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center',
  },
  container: {
    flex: 1,
    marginTop: 10,
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'center',
    marginBottom: 20,
  },

  product: {
    width: 100,
    height: 140,
    margin: 2,
    fontSize: 10,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#CCC',
    overflow: 'hidden',
  },
  footer: {
    backgroundColor: '#6eaaff',
    paddingLeft: 5,
    paddingTop: 3,
    paddingRight: 5,
    height: 30,
    flex: 0,
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'center',
  },

  image: {
    flex: 0,
    alignSelf: 'stretch',
    height: '80%',
    width: 100,
  },
});

export default Home;






and here is my package

{
  "name": "novelmanager",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android port=8088",
    "ios": "react-native run-ios",
    "start": "react-native start --port=8088",
    "test": "jest",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
  },
  "dependencies": {
    "@react-native-community/masked-view": "^0.1.10",
    "@react-native-community/picker": "^1.8.1",
    "@react-navigation/bottom-tabs": "^5.11.4",
    "@react-navigation/drawer": "^5.12.0",
    "@react-navigation/material-top-tabs": "^5.3.13",
    "@react-navigation/native": "^5.9.2",
    "@react-navigation/stack": "^5.14.0",
    "eslint-plugin-react-hooks": "^3.0.0",
    "install": "^0.13.0",
    "node-html-parser": "^2.0.2",
    "npm": "^6.14.11",
    "react": "16.13.1",
    "react-native": "0.63.4",
    "react-native-elements": "^3.1.0",
    "react-native-gesture-handler": "^1.9.0",
    "react-native-reanimated": "^1.13.2",
    "react-native-render-html": "^5.0.1",
    "react-native-safe-area-context": "^3.1.9",
    "react-native-screens": "^2.15.2",
    "react-native-vector-icons": "^8.0.0",
    "react-native-webview": "^11.2.0",
    "react-navigation-stack": "^2.10.2",
    "recyclerlistview": "^3.0.5-beta.1"
  },
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/runtime": "^7.8.4",
    "@react-native-community/eslint-config": "^1.1.0",
    "@types/jest": "^25.2.3",
    "@types/react-native": "^0.63.2",
    "@types/react-native-vector-icons": "^6.4.6",
    "@types/react-test-renderer": "^16.9.2",
    "babel-jest": "^25.1.0",
    "eslint": "^6.5.1",
    "jest": "^25.1.0",
    "metro-react-native-babel-preset": "^0.59.0",
    "react-test-renderer": "16.13.1",
    "typescript": "^3.8.3"
  },
  "jest": {
    "preset": "react-native",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

Any idee if im doing somthing wrong or is it a limitation for the controller ?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:6

github_iconTop GitHub Comments

2reactions
gulsher7commented, May 23, 2021

Here is the demo*** note: isGoing contain boolean value you can true or false according to your pagination behaviour if you done enough load data in your list then set isGoing value to false.

import React, { useEffect, useState } from ‘react’; import { Dimensions, StyleSheet, Text, View } from ‘react-native’; import { DataProvider, LayoutProvider, RecyclerListView } from ‘recyclerlistview’; const { width, height } = Dimensions.get(‘window’); let isGoing = true const App = () => {

let initialData = [{},{}]

const [data, setData] = useState(initialData)

const [dataSource, setDataSource] = useState( new DataProvider((r1, r2) => { return r1 !== r2; }).cloneWithRows(data), );

const onEnd = () => { if (isGoing) { let prevData = [{}, {}, {}, {}, {}, {},] setData([…data, …prevData]) isGoing = false } }

console.log(‘data value’, data.length)

const _childRLVLayoutProvider = new LayoutProvider( index => { return 0; }, (type, dim) => { dim.height = height; dim.width = width; } );

const _changeIndex = (i) => { // console.log(“i==>>>>”, i[0]) }

useEffect(() => { if (data && data.length > 0) setDataSource(dataSource.cloneWithRows(data)); }, [data]);

const _renderRow = (type, data, index, extendedState) => { // console.log(“this is props data”, data.item) return ( <View style={{ alignSelf: ‘center’, justifyContent: ‘center’, flex: 1 }}> <Text>HI {index}</Text> </View> ) } return ( <View style={styles.container}> <RecyclerListView style={{ flex: 1 }} showsVerticalScrollIndicator={false} layoutProvider={_childRLVLayoutProvider} dataProvider={dataSource} onVisibleIndicesChanged={_changeIndex} rowRenderer={(type, data, index, extendedState) => _renderRow(type, data, index, extendedState)} onEndReached={onEnd} /> </View> ); };

const styles = StyleSheet.create({ container: { flex: 1, }, });

export default App;

1reaction
AlenTomacommented, Oct 5, 2021

Well I made it work as fallow

  // when items update create new provider
  useEffect(() => {
    if (items && items.length > 0)
      setDataSource(
        new DataProvider((r1, r2) => {
          return r1 !== r2;
        }).cloneWithRows(items),
      );
  }, [items]);

I needed to create a new DataProvider each time the items gets updated and that it how I got it to work.

I did not test your solution so I have no Idea if the code you provided work or not sorry.

Read more comments on GitHub >

github_iconTop Results From Across the Web

React Native List Pagination to Load More Data dynamically
Example of React Native FlatList Pagination to Load More Data dynamically - Infinite List. ... We have added load more button on the...
Read more >
Newest 'recyclerlistview' Questions - Stack Overflow
In my program I added code to move the list items around but for example if I move ... Data get overlaps while...
Read more >
[F/OS] Recycler List View - Render larger data sets efficiently ...
Recycler List View An extension for rendering larger data sets efficiently using RecyclerView for AppInventor & Distros.
Read more >
Data Paging with Dynamic Data - CodeProject
Paging data collections using the Dynamic Data library. ... In LoadData() , data is added to the observable cache by calling the ...
Read more >
@elanf/recyclerlistview - npm
The listview that you need and deserve. It was built for performance, uses cell recycling to achieve smooth scrolling.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found