Skip to content

Instantly share code, notes, and snippets.

@technoplato
Last active April 2, 2020 14:43
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save technoplato/e394369a6f202a58bf010635e6eb32c7 to your computer and use it in GitHub Desktop.
Save technoplato/e394369a6f202a58bf010635e6eb32c7 to your computer and use it in GitHub Desktop.
Infinite Scrolling List Flavors
import React, { useState, useEffect } from 'react'
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
Dimensions
} from 'react-native'
const useInfiniteScroll = load => {
const [isFetching, setIsFetching] = useState(true)
const [data, setData] = useState([])
useEffect(() => {
let didCancel = false
if (!isFetching) return
const loadAsync = async () => {
const lastIndex = data.length
const lastItem = data.length ? data[lastIndex] : null
const newData = await load({ lastIndex, lastItem })
if (!didCancel) {
setData(prevState => [...prevState, ...newData])
setIsFetching(false)
}
}
loadAsync()
return () => {
didCancel = true
}
}, [isFetching])
return [data, isFetching, setIsFetching]
}
const INITIAL_LOAD = 30
const PAGE_SIZE = 20
export default () => {
/**
* Right now, I'm mandating that whatever this method is accepts as a
* parameter an object containing the objects `lastIndex` and `lastObject`
* respectively. I believe this should suffice for effective paging.
*
* @param lastIndex
* @returns {Promise<R>}
*/
const fetchMoreListItems = ({ lastIndex }) => {
// Simulate fetch of next 20 items (30 if initial load)
return new Promise(resolve => {
setTimeout(() => {
resolve([
...Array.from(
Array(lastIndex === 0 ? INITIAL_LOAD : PAGE_SIZE).keys(),
n => {
n = n + lastIndex
return {
number: n.toString(),
id: n.toString()
}
}
)
])
}, 2000)
})
}
const [data, isFetching, setIsFetching] = useInfiniteScroll(
fetchMoreListItems
)
return (
<SafeAreaView style={styles.container}>
<View style={styles.blueBox}>
<Text style={styles.bigWhiteBoldText}>
{`${data.length} Items Loaded`}
</Text>
</View>
<FlatList
onEndReachedThreshold={7}
onEndReached={() => {
if (!isFetching) {
setIsFetching(true)
}
}}
data={data}
keyExtractor={item => item.id}
renderItem={({ item }) => {
return <Item item={item} />
}}
/>
{isFetching && (
<View style={styles.blueBox}>
<Text style={styles.bigWhiteBoldText}>(Fetching More)</Text>
</View>
)}
</SafeAreaView>
)
}
class Item extends React.PureComponent {
render() {
return (
<View style={styles.item}>
<Text style={styles.title}>{this.props.item.number}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 24,
backgroundColor: 'yellow'
},
item: {
backgroundColor: '#f9c2ff',
alignItems: 'center',
justifyContent: 'center',
height: Dimensions.get('window').height * 0.45,
marginVertical: 8,
marginHorizontal: 16
},
title: {
fontSize: 48
},
blueBox: {
height: 50,
backgroundColor: 'blue',
justifyContent: 'center',
alignItems: 'center'
},
bigWhiteBoldText: {
color: 'white',
fontSize: 32,
fontWeight: 'bold'
}
})
import React, {useState, useRef, useMemo} from 'react';
import {
SafeAreaView,
TouchableOpacity,
FlatList,
StyleSheet,
Text,
} from 'react-native';
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
selected: false,
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
selected: false,
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
selected: false,
},
];
function Item({item: {id, title, selected}, onSelect}) {
console.log('L29 "item is rendering" ===', id);
return (
<TouchableOpacity
onPress={() => onSelect(id)}
style={[
styles.item,
{backgroundColor: selected ? '#6e3b6e' : '#f9c2ff'},
]}>
<Text style={styles.title}>{title}</Text>
</TouchableOpacity>
);
}
function ItemPureFunctional({item: {id, title, selected}, onSelect}) {
return useMemo(() => {
console.log('L44 "item is rendering" ===', id);
return (
<TouchableOpacity
onPress={() => onSelect(id)}
style={[
styles.item,
{backgroundColor: selected ? '#6e3b6e' : '#f9c2ff'},
]}>
<Text style={styles.title}>{title}</Text>
</TouchableOpacity>
);
}, [selected]);
}
class ItemPureComponent extends React.PureComponent {
render() {
return (
<TouchableOpacity
onPress={() => this.props.onSelect(this.props.id)}
style={[
styles.item,
{backgroundColor: this.props.selected ? '#6e3b6e' : '#f9c2ff'},
]}>
<Text style={styles.title}>{this.props.title}</Text>
</TouchableOpacity>
);
}
}
export default function App() {
const [data, setData] = useState(DATA);
const onSelect = useRef(id => {
setData(oldData => {
return [
...oldData.map(item => {
if (id === item.id) {
return {
...item,
selected: !item.selected,
};
}
return item;
}),
];
});
});
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({item}) => (
<ItemPureFunctional item={item} onSelect={onSelect.current} />
)}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 24,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment