React Native: how to combine inline elements so they get wrapped together - react-native

I have a list of comma-separated inline touchables like this:
[(<TouchableOpacity><Text>a</Text></TouchableOpacity>),
(<Text>, </Text>),
(<TouchableOpacity><Text>b</Text></TouchableOpacity>),
(<Text>, </Text>),
(<TouchableOpacity><Text>c</Text></TouchableOpacity>)]
rendering as a comma-separated string of items:
a, b, c
The problem is that sometimes comma gets wrapped to a newline, which looks kinda ugly:
a, b, c
, d
How to combine two "inline" elements so they get wrapped together?
Update. Code:
...
let items1 = [];
for (let i = 1; i <= 100; i++) {
const text = makeid(i % 10 + 1);
items1.push((<TouchableOpacity><Text style={{fontSize: 18}}>{text}</Text></TouchableOpacity>));
items1.push((<Text style={{fontSize: 18}}>, </Text>));
}
return (<View style={styles.li}>
{items1}
</View>);
...
// just generates some random string
function makeid(len) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < len; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
const styles = StyleSheet.create({
li: {
width: "100%",
flexDirection: "row",
flexWrap: "wrap",
marginBottom: 5,
paddingLeft: 10
}
});
Result:

1. Starting solution
According to the official documentation:
Text supports nesting, styling, and touch handling.
So if you're adding TouchableOpacity just to get onPress support, then it's quite useless.
In that case you can simply remove it and wrap all Text blocks in a parent Text and your problem is solved.
As you correctly note, this
leaves a user without nice animation provided by <TouchableOpacity>.
I thought to a better solution and it was not so far :)
2. Other (and better?) solution
Simply wrap your <TouchableOpacity> and related <Text> with comma in a single <View> with flexDirection: 'row' and add it to the array you're going to render.
render() {
let items1 = [];
for (let i = 1; i <= 100; i++) {
const text = makeid((i % 10) + 1);
items1.push(
<View style={{ flexDirection: 'row'}}>
<TouchableOpacity>
<Text style={{ fontSize: 18 }}>{text}</Text>
</TouchableOpacity>
<Text style={{ fontSize: 18 }}>, </Text>
</View>
);
}
return (
<View style={styles.li}>{items1}</View>
);
}
This way you're sure comma do not separate from its previous word and you benefit of <TouchableOpacity> animation.
Here you can find a working example.
Hope this helps!

Related

React Native - Horizontal Flatlist with column wrapper doesn't take flex properly as required?

I have a dynamic flatlist with values that should be rendered in the horizontal list with 3 columns. But, I am not able to make it display as I wanted.
I am trying the code as follow,
let data = ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8"];
const { width } = Dimensions.get('window');
const itemWidth = (width - 12) / 3;
<FlatList
columnWrapperStyle={{ justifyContent: 'space-around', alignItems: 'flex-start' }}
numColumns={3}
data={data}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator
keyExtractor={(item, index) => `${index}${item}`}
renderItem={(item) => {
return (
<View style={{ flex: 1, backgroundColor: '#ddd', minWidth: itemWidth, maxWidth: itemWidth }}>
<Text >
Hello World
</Text>
</View>
);
}}
/>
But, the output is not as I wanted.
Let say, if the list is the length of multiple of 3 like 6,9,12 it works as I wanted.
But, if the list has length 8 first two rows are rendered okay. But, the third row is giving full space to the remaining two items. Which I don't want.
Currently i am geeting output as follow
But the output should be like,
Can anyone suggest a workaround?
Thanks.
See i can provide you one logical solution, and i'm assuming you want to plot the text inside the Flatlist. For this kind of scenarios, you would need to append an empty div for the respective empty slots.
If you want the numOfColumns to be 3,
let data = ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8"];
First check the remainder and num of rows to be added by
let numOfRowsToBeAdded = 3-(data.length%3);
// here im ommitting if we get 0 as remainder as its not required
if(numOfRowsToBeAdded){
for(let i=0;i<numOfRowsToBeAdded;i++){
data.push(null);
}
}
Hope you got the logic. feel free for doubts. ill help
Since you calculate itemWidth manually, you can use justify: flex-end for your columnWrapperStyle and then add a marginRight: 6 to every first and second item in every row:
renderItem={(item, index) => {
const isThirdColumn = (index + 1) % 3 === 0;
const marginRight = isThirdColumn ? 0 : 6;
return (
<View style={{ marginRight, ...otherStyles }}>

How to add space between components that comes from a mapping function?

I am trying to add some buttons with a title that is inside every position of an array (for exaMple the first button have its title in the content of the array in the position 0), that is the reason i am using a map function and it works but... i can not add space between each button
it looks like this:
THIS IS WHERE THE MAPPING FUNCTION IS CALLED:
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
<View style={{
flexDirection: 'row',
justifyContent: 'space-between'}}>
{mapping()}
</View>
</ScrollView>
THIS IS THE FUNCTION:
function mapping() {
const horas = ["6:30-7:30", "7:30-8:30", "7:30-8:30"...]
const mappeo = horas.map((i) => {
return (
<Button
title={i}
type="outline"
style={{ padding:10}}/>
)
}
)
return (mappeo)
}
So how can i separate the buttons?
How about using margin. Padding are used for content inside a container and margins are used for applying margins or spaces outside the container.
Try this
function mapping() {
const horas = ["6:30-7:30", "7:30-8:30", "7:30-8:30"...]
const mappeo = horas.map((i) => {
return (
<Button
title={i}
type="outline"
style={{ padding:10, marginHorizontal:10}}/>
)
}
)
return (mappeo)
}
add 'marginHorizontal' to your button like below:
function mapping() {
const horas = ["6:30-7:30", "7:30-8:30", "7:30-8:30"...]
const mappeo = horas.map((i) => {
return (
<Button
title={i}
type="outline"
style={{ padding:10, marginHorizontal: 5 }}/>
)
}
)
return (mappeo)
}

How to use marginTop when view wrap to next row?

I have an array data , i map the array return <Button />, but i find that when the view wrap <Button /> to next row automatically, there is no spacing each row.
How do i know what time is the view wrap to next row ? So that i can control margingTop between each row.
I try to add position: 'relative' in my itemBody style, but there is no any different.
Any help would be appreciated. Thanks in advance.
Here is my view code:
<Body style={styles.timeBody}>
{releasedTime.map((value, index) => {
const userTime = GetUserTime.getUserHour();
const theTime = GetUserTime.getAsiaTime(value, 'YYYY/MM/DD HH:mm:ss');
const hour = theTime.getHours();
const minute = (theTime.getMinutes() < 10 ? '0' : '') + theTime.getMinutes();
if (userTime < hour) {
return (
<GrayButton key={index}>{`${hour}:${minute}`}</GrayButton>
);
}
return (
<Button key={index}>{`${hour}:${minute}`}</Button>
);
})}
</Body>
Here is my style for wrap:
const styles = {
timeBody: {
flex: 1,
flexDirection: 'row',
paddingTop: 5,
flexWrap: 'wrap',
width: '100%'
}
};
add marginTop on Your Button and customButtons
<Button style={{marginTop:5}} key={index}>{`${hour}:${minute}`}</Button>
Like this also add to customButton like <GrayButton/>

Precalculate component in react native

I have a piece of components which will be rendered after a user click on a button. However, these components involving too many calculation. It takes 3~5 seconds to complete it.
I want to compute it in advance and store into my state.
So I can show it on the fly.
But somehow, the content always returns null to my state.
Any idea?
prepopulateDetail(){
let displayedSources = {}
this.setState({setState: (
<View>
{
[... new Set(this.props.definition)].map(wordDef => {
if (wordDef.content.length >= 0 && displayedSources.hasOwnProperty(wordDef.source) === false) {
displayedSources[wordDef.source] = true
return (
<View style={styles.definitions} key={guid()} i={wordDef.source}>
<Badge containerStyle={{
backgroundColor: PRIMARY_BG,
width: 100,
marginTop: 5,
marginBottom: 5,
borderRadius: 0
}}>
<Text style={{color: 'white'}}>{wordDef.source.toUpperCase()}</Text>
</Badge>
{
wordDef.content.map((content, j) => {
let data = JSON.parse(content)
return this.displayWordDefinition(data)
})
}
</View>
)
}
})
}
</View>
)}, this.showState)
}

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