Dynamic added data eg RecyclerListView with pagination
See original GitHub issueOk 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:
- Created 3 years ago
- Reactions:2
- Comments:6
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
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;
Well I made it work as fallow
I needed to create a new
DataProvider
each time theitems
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.