How can we achieve the grid like this. I looked in many react native libraries but still in search of it. I tried a custom grid view but creating an issue when not having more data. Please let me know the best way for it.
How can we achieve the grid like this. I looked in many react native libraries but still in search of it. I tried a custom grid view but creating an issue when not having more data. Please let me know the best way for it.
I managed to do it for my project By creating a custom component.
Note: Before you use the following component keep in mind that:
ScrollView instead of FlatList because it wasn't feasible in my case. And it might not be that efficient.lodash Please make sure to install that.MyImage which handles loading and errors according to my need. You can skip and use your Image tag or Any Other.Explanation:
I created cells containing three images. There are a total of 3 types of cells.
I create chunks or we can array of arrays using lodash which works as data for each row. Each small chunk or array will have 3 objects.
groupEveryNthRow = 3 means I'll have cell with a big image on every 3rd row starting from 0.
bigImageSide represents that on which side the big image should appear. I keep changing left to right. You can choose according to your case.
All other code is just self-explanatory. Let me know if you don't understand anything else.
Code
InstaGrid.js
import React from 'react';
import {
View,
StyleSheet,
Dimensions,
ScrollView,
ActivityIndicator,
} from 'react-native';
var {width} = Dimensions.get('window');
import * as _ from 'lodash';
import MyImage from './MyImage';
const InstaGrid = ({
data,
columns,
onEndReachedThreshold,
onEndReached,
loading = false,
onItemClick,
}) => {
const groupEveryNthRow = 3;
const {mainContainer, groupedGridContainer} = styles;
var currentRow = 0;
const rowsArray = _.chunk(data, columns);
var bigImageSide = 'right';
const renderGroupedItem = (row) => {
const smallImage1 = row[0];
const smallImage2 = row[1];
const largeImage = row[2];
if (bigImageSide === 'right') {
bigImageSide = 'left';
return (
<View style={{flexDirection: 'row'}}>
<View style={groupedGridContainer}>
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnail}
sourceObj={smallImage1}
onPress={() => {
onItemClick(smallImage1);
}}
/>
</View>
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnail}
sourceObj={smallImage2}
onPress={() => {
onItemClick(smallImage2);
}}
/>
</View>
</View>
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnailLarge}
sourceObj={largeImage}
onPress={() => {
onItemClick(largeImage);
}}
/>
</View>
</View>
);
} else {
bigImageSide = 'right';
return (
<View style={{flexDirection: 'row'}}>
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnailLarge}
sourceObj={largeImage}
onPress={() => {
onItemClick(largeImage);
}}
/>
</View>
<View style={groupedGridContainer}>
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnail}
sourceObj={smallImage1}
onPress={() => {
onItemClick(smallImage1);
}}
/>
</View>
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnail}
sourceObj={smallImage2}
onPress={() => {
onItemClick(smallImage2);
}}
/>
</View>
</View>
</View>
);
}
};
const renderSingleItem = (item) => {
return (
<View style={styles.gridStyle}>
<MyImage
style={styles.imageThumbnail}
sourceObj={item}
onPress={() => {
onItemClick(item);
}}
/>
</View>
);
};
const renderCell = (row) => {
if (row.length >= columns && currentRow % groupEveryNthRow === 0) {
currentRow++;
return <View>{renderGroupedItem(row)}</View>;
}
currentRow++;
return (
<View style={{flexDirection: 'row'}}>
{row.map((item) => {
return renderSingleItem(item);
})}
</View>
);
};
const isCloseToBottom = ({layoutMeasurement, contentOffset, contentSize}) => {
const paddingToBottom = 20;
return (
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom
);
};
const renderFooter = () => {
return (
<View style={{marginBottom: 16}}>
<ActivityIndicator animating size="large" />
</View>
);
};
return (
<ScrollView
scrollEventThrottle={onEndReachedThreshold}
onScroll={({nativeEvent}) => {
if (isCloseToBottom(nativeEvent)) {
onEndReached();
}
}}>
<View style={mainContainer}>
{rowsArray.map((row) => {
return renderCell(row);
})}
</View>
{loading && renderFooter()}
</ScrollView>
);
};
const styles = StyleSheet.create({
mainContainer: {
width: '100%',
},
groupedGridContainer: {
flexDirection: 'column',
flexWrap: 'wrap',
},
imageThumbnail: {
height: width / 3 - 12,
width: width / 3 - 12,
resizeMode: 'stretch',
alignSelf: 'flex-start',
justifyContent: 'flex-start',
},
imageThumbnailLarge: {
height: width * 0.6 + 12,
width: width * 0.6 + 12,
marginLeft: 4,
resizeMode: 'stretch',
alignSelf: 'flex-start',
justifyContent: 'flex-start',
},
gridStyle: {
margin: 4,
},
});
export default InstaGrid;
MyImage.js
import React, {useState} from 'react';
import {
TouchableOpacity,
Image,
StyleSheet,
ActivityIndicator,
} from 'react-native';
const MyImage = ({style, sourceObj, onPress}) => {
const [imageError, setImageError] = useState(false);
const [loading, setLoading] = useState(true);
return (
<TouchableOpacity onPress={onPress}>
{imageError || !sourceObj.card_images ? (
<Image
source={require('../images/userImage.jpg')}
style={style}
onLoadEnd={() => setLoading(false)}
/>
) : (
<Image
style={style}
source={{uri: sourceObj.card_images.front_image}}
onError={(e) => {
setLoading(false);
setImageError(true);
}}
onLoadEnd={() => setLoading(false)}
/>
)}
{loading && (
<ActivityIndicator
style={styles.activityIndicator}
animating={loading}
/>
)}
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
activityIndicator: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
});
export default MyImage;
How to Use: Just
<InstaGrid
data={details}
columns={3}
loading={loading}
onItemClick={(item) => {
console.log('Got the Item:' + JSON.stringify(item));
}}
onEndReachedThreshold={400}
onEndReached={() => (offset !== -1 ? fetchData() : null)}
/>