Setting up a table layout in React Native - react-native

I'm transitioning a React project into React Native and need help setting up a grid layout in React Native. I want to set up a 5-col by x-row (number of rows may vary) view. I've played around with the react-native-tableview-simple package, but I can't specify the span of a cell. I've also tried the react-native-flexbox-grid package, which I'm able to set up columns, but I'm still not able to set the span-width of a specific cell. I wonder if there's anything I can use.
For reference, I would like my table to look something along the lines like this:
|Col 1|Col 2|Col 3|Col 4|Col 5|
|------------------------------
Row 1| Text | Yes | No |
|------------------------------
Row 2| Text | Yes | No |
|------------------------------
Row 3| Text | Dropdown |

You can do this without any packages. If each row is exactly the same doing the following should solve your problem;
export default class Table extends Component {
renderRow() {
return (
<View style={{ flex: 1, alignSelf: 'stretch', flexDirection: 'row' }}>
<View style={{ flex: 1, alignSelf: 'stretch' }} /> { /* Edit these as they are your cells. You may even take parameters to display different data / react elements etc. */}
<View style={{ flex: 1, alignSelf: 'stretch' }} />
<View style={{ flex: 1, alignSelf: 'stretch' }} />
<View style={{ flex: 1, alignSelf: 'stretch' }} />
<View style={{ flex: 1, alignSelf: 'stretch' }} />
</View>
);
}
render() {
const data = [1, 2, 3, 4, 5];
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
{
data.map((datum) => { // This will render a row for each data element.
return this.renderRow();
})
}
</View>
);
}
}

After looking here for answers I discovered a really great library that behaves much like Bootstrap tables. I have found layout with React Native very challenging but this library delivers predictable layout results.
React Native Easy Grid
I agree that basic flex tables should be built in to React Native but until they are this library worked great for me.

Though it is an old post but I was looking for something similar asked in the question,
I have brainstormed and havoked the internet for the solution and the best solution I got was to import the react-native-table-component package to my project and prepare a decent table layout for my application to display data. There might be many more souls figuring out a way to do this and may I recommend this link to all your answers:This link is the npm package installer link which have explanation to all the code and have examples

Using functional components:
(Specify the number of rows and columns in the array)
Create a Row component
function Row({ column }) {
return (
<View style={styles.rowStyle}>
{column.map((data) => (
<Cell data={data} />
))}
</View>
);
}
Create a Cell component
function Cell({ data }) {
return (
<View style={styles.cellStyle}>
<Text>{data}</Text>
</View>
);
}
Create a Grid component
function Grid() {
const data = [
[15, 14, 13, 12],
[11, 10, 9, 8],
[7, 6, 5, 4],
[0, 1, 2, 3],
];
return (
<View style={styles.gridContainer}>
{data.map((column) => (
<Row column={column} />
))}
</View>
);
}
Style the components:
const styles = StyleSheet.create({
gridContainer: {
width: 220,
},
rowStyle: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
},
cellStyle: {
flex: 1,
margin: 10,
},
});

Related

React-Native Tooltip Issue

I'm trying to find a simple working tooltip for react-native but I can't find any. All of them has many many bugs. I'd like to describe an issue in "react-native-elements/Tooltip" (version 3.4.2) and also ask for a working tooltip component.
...
render() {
return (
<View>
<Text style={styles.pageTitle}>{this.props.messages.account}</Text>
<View style={styles.horizontalFlex}>
<Text
style={styles.userInfo}>{this.props.messages.subscriptionModel}: {this.props.route.params.userProfile}
</Text>
<Tooltip popover={<Text>Info here</Text>}>
<EntypoIcon style={styles.infoIcon} name="info-with-circle" size={20} color={Colors.DARK_BLUE}/>
</Tooltip>
</View>
</View>
);
}
...
let styles = EStyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
},
pageTitle: {
...
},
userInfo: {
textAlign: "left",
justifyContent: "center",
marginLeft: "20rem",
color: Colors.DARK_BLUE,
fontSize: "15rem",
marginBottom: "10rem"
},
infoIcon: {
paddingLeft: "20rem",
},
horizontalFlex: {
flexDirection: "row"
}
});
...
The output for the above code is the following:
Somehow the icon which I'm putting a Tooltip to, slides above. It doesn't matter if it's an icon or a text, same issue occurs. How do I fix this? Do you know any other working tooltip in react-native which you tried and saw that it is working recently?
I had to set withOverlay to false and skipAndroidStatusBar to true. This wasn't what I needed but still it's acceptable. Here is the code:
<Tooltip
popover={<Text style={...text style here...}>Change here</Text>}
withOverlay={false}
skipAndroidStatusBar={true}
containerStyle={...container style here...}
backgroundColor={...color...}>

Why are my React Native View heights inconsistent?

I have the following code:
return (
<>
<View style={styles.dashboardListItemContainer}>
<View style={styles.listItemBorder} />
</View >
</>
)
const styles = StyleSheet.create({
dashboardListItemContainer: {
minHeight: 73,
backgroundColor: COL_DARK_BG,
display: "flex",
flexDirection: "column",
},
listItemBorder: {
minHeight: 0.5,
backgroundColor: 'white',
}
});
Which produces the following result:
My question is, why are the lines inconsistently bright / appear to be different heights?
Also when I change column to row here: flexDirection: "row", the line disappears completely. I can't tell if this is a bug or there are errors in my code?
TIA.
Update:
I cannot solve this problem on any device, and I've tried different methods for determining the height.
borderBottomWidth: StyleSheet.hairlineWidth, looks like this:
You can see in the above picture some lines do not even show up.
borderBottomWidth: StyleSheet.hairlineWidth * 2, looks like this:
And height: 0.5, looks like this on a white background:
The problem is consistent across devices, it is at the edge of an item in a <FlatList>.
Android Emulator has less resolution, try on a real device, sometimes i have the same error, and when I run on a real device it looks good.
We can use flex value.
import React, { Component } from "react";
import { View, FlatList } from "react-native";
import { StyleSheet } from "react-native";
class App extends Component {
render() {
return (
<FlatList
data={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
extraData={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
horizontal={false}
renderItem={({ item, index }) => {
return (
<View style={styles.dashboardListItemContainer}>
<View style={styles.listItemBorder} />
</View>
);
}}
key={({ item, index }) => index}
/>
);
}
}
export default App;
const styles = StyleSheet.create({
dashboardListItemContainer: {
backgroundColor: "red",
display: "flex",
flex: 1,
minHeight: 73,
flexDirection: "column"
},
listItemBorder: {
minHeight: 0.5,
backgroundColor: "yellow"
}
});

Access to style property and change it

I am new to React-Native. Say I have a styleSheet contains style for a green button:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
greenB:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
backgroundColor:'green',
}, ........
And for some reason I need to change that colour in my code.
render(){
....
const {container, greenB}=styles; //Not sure, how else can I define greenB?
greenB.backgroundColor='black';
return (
<View style={styles.container}>
<View style={styles.greenB} >
....
How can I access backgroundColor from greenB and change that?
I'm getting a TypeError "Attempted to assign to readonly property",
which is reasonable cause styles is a const.
tried to read about state management but not sure how to use it in this case.
React Native's StyleSheet.create is used for better performance and clean code, It caches the style ids to reduce the amount of data that goes through the bridge, so that is why you cannot alter it but only use it .
But there're many solutions, You can use simple javascript object and alter the values as you want.
For example:-
let Styles= {
Text:{
color:'blue'
}
}
Styles.Text.color= 'green'
<Text style={Styles.Text} > </Text>
More over, For dynamic styling i recommend using states instead of variables .

React Native FlatList rendering a few items at a time

I have a list of chat messages in my app to which new items are added to the bottom. I used some code from another SO question to make the FlatList stick to the bottom when new items are added, as below
<FlatList
data={messages}
renderItem={({item}) => <ChatMessage message={item}></ChatMessage>}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={messages.length}
initialScrollIndex={messages.length-1}
ref={ref => this.flatList = ref}
onContentSizeChange={(contentWidth, contentHeight)=>{
this.flatList.scrollToEnd();
}}
/>
The problem is that when the initial list renders (only 35 items, hardcoded in an array for now) it seems to render just a few items, then scroll down a bit, then render a few more, then scroll down a bit until it finally completes the rendering and sticks to the bottom. It's choppy and slow, despite adding initialNumToRender={messages.length} and rendering an incredibly simple node for each result.
Ideally I guess I need to wait for it to fully render before displaying anything to the user but (A) they'd have to wait a couple of seconds to start using the chat room and (B) I don't think that's how Flatlist works, I assume the elements have to be viewable before it is rendered.
Is there just a better way to do this? (Testing on Android by the way)
EDIT: Adding ChatMessage component for completeness
// Chat Message
import React, { Component } from 'react'
import {
StyleSheet,
ImageBackground,
Text,
View
} from 'react-native'
class ChatMessage extends Component {
constructor(props) {
super(props)
this.state = { }
}
render() {
return (
<View style={styles.chatMessage}>
<View style={styles.chatMessage_layout}>
<View style={styles.chatMessage_pic}>
<View style={styles.chatMessage_pic_image}>
<ImageBackground
source={require('./assets/images/profile-pics/example-profilr.png')}
style={styles.chatMessage_pic_image_background}
imageStyle={{ borderRadius: 40/2 }}
resizeMode="cover"
>
</ImageBackground>
</View>
</View>
<View style={styles.chatMessage_details}>
<View style={styles.chatMessage_name}>
<Text style={styles.chatMessage_name_text}>
{this.props.message.name}
<Text style={styles.chatMessage_name_time}> 24h</Text>
</Text>
</View>
<View style={styles.chatMessage_message}>
<Text style={styles.chatMessage_message_text}>{this.props.message.text}</Text>
</View>
</View>
</View>
</View>
)
}
}
export default ChatMessage;
const styles = StyleSheet.create({
chatMessage: {
paddingVertical: 10,
paddingHorizontal: 24
},
chatMessage_layout: {
flexDirection: 'row'
},
chatMessage_pic: {
width: 40,
height: 40,
marginRight: 12
},
chatMessage_pic_image: {
width: 40,
height: 40
},
chatMessage_pic_image_background: {
width: 40,
height: 40
},
chatMessage_details: {
flex: 1
},
chatMessage_name_text: {
color: '#FFF',
fontSize: 14,
fontWeight: 'bold'
},
chatMessage_name_time: {
fontSize: 11,
color: 'rgba(255,255,255,0.6)'
},
chatMessage_message: {
flexDirection: 'row',
alignItems: 'center'
},
chatMessage_message_text: {
color: '#FFF',
fontSize: 12
}
})
If you have less number of items and want to render all items at once then you should use ScrollView as mentioned in the docs
ScrollView: Renders all elements at once, but slow if there are large number of elements.
FlatList: Renders items in a lazy mode, when they are about to appear and removes them when they leave the visible display to save memory that makes it usable for performance on large lists.
For Flatlist optimization you need to use PureComponent whenever you render the child so that it only shallow compares the props.
Also in the keyExtractor use a unique id for your item and do not depend upon the index, since when the item updates the index is not reliable and may change

React Native FlatList with columns, Last item width

I'm using a FlatList to show a list of items in two columns
<FlatList style={{margin:5}}
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => item.id }
renderItem={(item) => <Card image={item.item.gallery_image_url} text={item.item.name}/> }
/>
The card component is just a view with some styles:
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', height: 130}} ></View>
It is working fine, but if the number of items is odd, the last row only contains one item and that item stretches to the full width of the screen.
How can I set the item to the same width of the others?
for your case use flex: 1/2
therefore: Your item should have flex of 1/(number of columns) if you have 3 columns your item should have flex:1/3
Theres a few things you can try here.
A) Setting a pre-defined width for the card ( Maybe equal to the height you've set? ). Then you can use alignItems in order to have the card positioned in the middle or on the left - Unsure as to which you wanted here.
B) If there are an even number of cards, you could add an empty View at the end in order to fill this space. I find this method pretty clunky but useful when trying to leave space for future elements.
C) Simply use alignItems: 'space-between, i like to use this to center items, but you would have to define the width, or use something like flex:0.5
I suggest researching more into flexbox to help you with this, as it is hard to tell the context of this situation. I'm assuming the above methods will help, but if not, here are some links for you to look at -
A Complete Guide to Flexbox (CSS Tricks)
Layout with Flexbox (React Native)
Video Tutorial (Youtube) Link Broken
Hope this helps. If you need any further clarification - just ask
This is the cleanest way to style a FlatList with columns and spaced evenly:
<FlatList style={{margin:5}}
numColumns={2} // set number of columns
columnWrapperStyle={style.row} // space them out evenly
data={this.state.items}
keyExtractor={(item, index) => item.id }
renderItem={(item) => <Card image={item.item.gallery_image_url} text={item.item.name}/> }
/>
const style = StyleSheet.create({
row: {
flex: 1,
justifyContent: "space-around"
}
});
You can try to get the current width of the device via Dimensions, do some math based on the number of columns you want to render, minus off the margins and set that as the minWidth and maxWidth.
For example:
const {height, width} = Dimensions.get('window');
const itemWidth = (width - 15) / 2;
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', minWidth: {this.itemWidth}, maxWidth: {this.itemWidth}, height: 130}} ></View>
The reason for it is your Card have style flex: 1, so it will try to expand all the space remain.
You can fix it by add maxWidth: '50%' to your Card style
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', height: 130, maxWidth: '50%'}} ></View>
#Emilius Mfuruki suggestion is good, but if you have text with varying length it doesn't work perfectly. Then use this width inside your item view:
const {height, width} = Dimensions.get('window');
const itemWidth = (width - (MarginFromTheSide * 2 + MarginInBetween * (n-1))) / n;
In FlatList use:
columnWrapperStyle={{
flex: 1,
justifyContent: 'space-evenly',
}}
Works perfectly.
The simplest solution is do the math.
Imagine we have 2 View for each Row and we want to give 10 margin to every side it will look something like that:
As you see in the image above each View have 2 margins in horizontal. (inside of red rectangle)
So we have to subtract the product of margin, number of column and 2 from the width.
import { Dimensions } from 'react-native';
const {width} = Dimensions.get("window")
const column = 2
const margin = 10
const SIZE = (width - (margin * column * 2)) / column
<View style={{ margin: 10, width: SIZE }} ></View>
I tried some of the solutions above but I still had some problems with the margins on the last item (2 columns list).
My solution was simply wrapping the item into a parent container, leaving the original container with flex: 1 and the parent container of the item with flex: 0.5 so it would take the margin correctly.
itemContainer: {
flex: 0.5,
},
itemSubContainer: {
flex: 1,
marginHorizontal: margin,
},
A simple way with flex
<FlatList style={{margin:5}}
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => item.id }
renderItem={({item, index}) => {
const lastItem = index === this.state.items.length - 1;
return (
<View style={{flex: lastItem ? 1 / 2 : 1 }}>
<Card image={item.gallery_image_url} text={item.name}/>
</View>
)}}
/>
You can use ListFooterComponent={this.renderFooter}
None of the above answers have worked perfectly for me so I post my own answer:
works with padding and margins
the last element will always have the correct size
<FlatList
data={data}
numColumns={2}
renderItem={({item, index}) => {
const lastItem = index === data.length - 1;
return (
<View style={{flex: 1, padding: 8, maxWidth: lastItem ? '50%' : '100%' }}>
...
</View>
)}}
/>
Note: change maxWidth according to number of columns
Result:
just use flex:0.5 and width:'50%'
Create an array with odd number of images in it, like:
const images = [
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
];
And then, use the code given below,
const App = () => {
const _renderItem = ({ item, index }) => (
<Image
source={item}
style={{
width: '50%',
height: 200,
}}
resizeMode="cover"
/>
);
return (
<View style={{flex: 1, marginHorizontal: 10,}}>
<FlatList
columnWrapperStyle={{ justifyContent: 'space-between' }}
keyExtractor={(_, index)=> index.toString()}
data={images}
numColumns={2}
renderItem={_renderItem}
/>
</View>
)
};
export default App;
Working Example