How do I position buttons in a specific grid in react native? - reactjs

So I am new to react native. I was Trying to place some buttons on my home screen. Suppose I have 6 buttons, I need the to be placed in a grid in lower half of the home screen. Something like this:
[ a ] [ b ]
[ c ] [ d ]
[ e ] [ f ]
I know we will use Flex in this and so far I have managed to put two buttons in row. Following is my code. ( I am only posting the relevant code)
<View style = { styles.allButtons }>
<View style = { styles.homeButtons } >
<Button style = { styles.buttonText }
color = "#157fd3"
title = 'Button 1'
onPress = {() => { Alert.alert("Pressed"); }}
/>
</View>
<View style = { styles.homeButtons } >
<Button style = { styles.buttonText }
color = "#157fd3"
title = 'My feeds'
onPress = {() => { Alert.alert("to be Updated."); }}
/>
</View>
</View>
And my stylesheet looks something like this.
allButtons: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
margin: 5,
},
homeButtons: {
flex: 1,
borderWidth: 0.5,
borderColor: 'white',
alignItems: 'center',
height: 45,
width: 260,
marginBottom: 3,
},
buttonText: {
fontSize: 18,
},
This is producing 2 buttons in row. But if I add another button, it gets placed in the same row i;e 3 buttons in row. Help would be appriciated.

Try adding flex-wrap: wrap; to your allButtons style
You may need to give your homeButtons container a flex-basis: 50% as well. Be careful because flex-basis doesn't account for margins.

Related

How to give spacing between the content of view in react native?

I am new in react native,I have to view right and left container given flex-between them,
now I have to give space in contents of left and right container also.
image shown in the below diagram as well as styling code,
const styles = StyleSheet.create({
mainContainer: {
width: "100%",
flexDirection: "row",
height: moderateScale(90),
backgroundColor: "red",
justifyContent: "space-between",
},
containerLeft: {
flexDirection: "row",
top: moderateScale(40),
backgroundColor: "green",
margin: moderateScale(12),
},
containerRightIcons: {
flexDirection: "row",
justifyContent: "flex-end",
top: moderateScale(40),
margin: moderateScale(12),
backgroundColor: "blue",
},
});
Wrap the individual child components inside a View and provide a marginRight when necessary. We could create a new component that handles this behaviour.
export function Spacer({isHorizontal = true, space = 10, children}) {
return <View style={isHorizontal ? {flexDirection: "row"} : null}>
{
React.Children.map(children, (child, index) => {
return index < children.length - 1 ? <View style={{marginRight: space}}>{child}</View> : child
})
}
</View>
}
We use the isHorizontal flag to indicate whether the children are layed out in a row. The default value is set to true to satisfy your current design. However, we can reuse it for column based layouts as well. The default spacing is set to 10. We can control it using the space prop.
Then, use it as follows.
<Spacer>
<Child1 />
<Child2 />
<Child3 />
</Spacer>
We can control the space via props.
<Spacer space={5}>
<Child1 />
<Child2 />
<Child3 />
</Spacer>

Create a dynamic width TextInput that is centered to the middle of the screen

I have a certain problem that I am facing and would like to see whether or not what I am facing is a react-native bug that needs submission to the react-native issues or if it is an error on my part.
I am trying to create a TextInput that is centered to my screen and is supposed to be able to grow in width as one types into it, once enough text is written it wraps to the next line. I am facing a problem that as one types in the TextInput the text appears first and then changes the size of the view causing a flicker and words typed to disappear and then reappear. Eventually the UI settles correctly and appears correct, but as one types the previous written characters can flow to be greater than the width of the View despite the View has no limit to width. I know I have figured out this is due to the parent have a alignItems property that makes the children (in this case the TextInput) centered.
The goal is to make the view expanded in width along side the text being written without this flickering bug.
return (
<KeyboardAvoidingView behavior="height" keyboardVerticalOffset={Platform.OS === 'ios' > 0 : 24} style={styles.flex1}>
<View style={styles.addingText}>
<TextInput
multiline
returnKeyType="done"
blurOnSubmit={true}
textAlignVertical="center"
style={styles.defaultAddingText}
autoFocus={addingText}
selectionColor="#3C99FF"
underlineColorAndroid="transparent"
value={text}
onChangeText={handleTextChange}
onSubmitEditing={handleSubmit}
/>
</View>
</KeyboardAvoidingView>
);
const styles = StyleSheet.create({
flex1: {
flex: 1,
},
addingText: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
zIndex: 100,
},
defaultAddingText: {
paddingHorizontal: 10,
borderRadius: 10,
},
});
Give a height to your defalutAddingText style as following,
const styles = StyleSheet.create({
flex1: {
flex: 1,
},
addingText: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
zIndex: 100,
},
defaultAddingText: {
height:auto,
paddingHorizontal: 10,
borderRadius: 10,
},
});

How to dynamically set height of a component whose height is an animated value post-animation?

The goal: have a dropdown view that animates the height-expansion over time. the caveat is this: once the view is expanded, it needs to be able to dynamically handle whether or not there is additional view data present. if it is present, a couple extra text components will be rendered.
The problem: as it currently is, the animation the parent's height to a fixed height. so when the additionalContent is rendered, it surpasses the bounds of the parent, whose height is fixed. I dont want to not set the height of the parent explicitly, because then I cant animate that aspect the way I want. I want to maintain the height animation as-is, as well as dynamically size the parent to contain the children when the additionalContent is present
const ListItem = (props) => {
const [checkInModal, setCheckInModal] = useState(false);
const [animatedHeight, setAnimatedHeight] = useState(new Animated.Value(0))
const [animatedOpacity] = useState(new Animated.Value(0))
const [dynamicHeight, setDynamicHeight] = useState(0);
const [expanded, setExpanded] = useState(false);
const toggleDropdown = () => {
if (expanded == true) {
// collapse dropdown
Animated.timing(animatedHeight, {
toValue: 0,
duration: 200,
}).start()
} else {
// expand dropdown
Animated.timing(animatedHeight, {
toValue: 100,
duration: 200,
}).start()
}
setExpanded(!expanded)
}
const renderAdditionalContent = () => {
setDynamicHeight(75);
if (someVariable == true) {
return (
<View> <Text> Some Content </Text> </View>
)
}
}
const interpolatedHeight = animatedHeight.interpolate({
inputRange: [0, 100],
outputRange: [75, 225]
})
const interpolatedOpacity = animatedOpacity.interpolate({
inputRange: [0, 100],
outputRange: [0.0, 1.0]
})
return (
<Animated.View
style={[styles.container, { height: interpolatedHeight + dynamicHeight }]}
>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', }}>
<View style={styles.leftContainer}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={styles.title}>{props.title}</Text>
</View>
<Text style={styles.subtitle}>{time()}</Text>
</View>
<View style={styles.rightContainer}>
<TouchableOpacity onPress={() => toggleDropdown()} style={styles.toggleBtn}>
<Image source={require('../assets/img/chevron-down.png')} resizeMode={'contain'} style={styles.chevron} />
</TouchableOpacity>
</View>
</View>
{expanded == true ? (
<Animated.View style={[styles.bottomContainer, { opacity: interpolatedOpacity }]}>
<Components.BodyText text="Subject:" style={{ fontFamily: Fonts.OPENSANS_BOLD }} />
<Components.BodyText text={props.subject} />
{ renderAdditionalContent() }
</Animated.View>
) : null}
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
borderRadius: 25,
width: width * 0.95,
marginBottom: 5,
marginHorizontal: 5,
paddingVertical: 15,
paddingHorizontal: 15
},
leftContainer: {
justifyContent: 'space-between',
},
rightContainer: {
flexDirection: 'row',
alignItems: 'center'
},
title: {
fontFamily: Fonts.OPENSANS_BOLD,
fontSize: 20,
color: '#454A66'
},
subtitle: {
color: '#454A66',
fontSize: 14
},
typeIcon: {
height: 25,
width: 25
},
chevron: {
height: 15,
width: 15
},
toggleBtn: {
borderWidth: 1,
borderColor: Colors.PRIMARY_DARK,
borderRadius: 7,
paddingTop: 4,
paddingBottom: 2.5,
paddingHorizontal: 4,
marginLeft: 10
},
bottomContainer: {
marginVertical: 20
},
buttonContainer: {
flexDirection: 'row',
width: 250,
justifyContent: 'space-between',
alignSelf: 'center',
marginVertical: 20
},
noShadow: {
elevation: 0,
shadowOffset: {
width: 0,
height: 0
},
shadowRadius: 0,
}
});
export default ListItem;
How can this be accomplished? So far ive tried creating a state variable dynamicHeight and setting it inside the function that renders additional content, but that hasn't worked.
Heres the snack: https://snack.expo.io/P6WKioG76
clarification edit: renderAdditionalContent function renders additional content (obviously), this content could be anywhere from one line of characters to multiple lines. regardless of the char count, the main parent container of the component needs to have all the children within its bounds. as it stands, if the additional-content-rendered has too much content, the content will spill over the border of the component's main parent container, which must be avoided. this can be done by simply not giving a height to the main component container, obviously. but the the idea is to have the animated height AND wrap the child content properly
Any suggestions?
EDITED : easy and simple way
You can also apply the height 100%, in that way we don't need to calculate the height of inner content it will auto adjust as per the content provided
const interpolatedHeight = animatedHeight.interpolate({
inputRange: [0, 100],
outputRange: ["0%", "100%"] // <---- HERE
})
To make this happen I have added <View> tag around the <Animated.View> to get height 100% correct and few changes in css, this looks more elegant and provides a perfect solution to your problem.
WORKING DEMO
So, you can use onLayout
This event is fired immediately once the layout has been calculated
First step:
// setHeight
const setHeight = (height) => {
setDynamicHeight(prev => prev + height);
}
<View onLayout={(event) => {
var { x, y, width, height } = event.nativeEvent.layout;
setHeight(height); // <--- get the height and add it to total height
}}>
<Text>Subject</Text>
<Text>Subject Content</Text>
{renderAdditionalContent()}
</View>
Second step:
useEffect(() => {
// trigger if only expanded
if (expanded) {
// trigger height animation , whenever there is change in height
Animated.timing(animatedHeight, {
toValue: dynamicHeight, // <--- animate to the given height
duration: 200,
}).start();
}
}, [dynamicHeight]); // <--- check of height change
WORKING DEMO (you can test it by adding remove text)
Here's a working snack of your code with a dynamic dropdown height: https://snack.expo.io/4mT5Xj6qF
You can change the height by changing the thisIsAValue constant.
Honestly you were very close with your code, you just had a few bits and pieces missing:
When you're animating height you don't need to use interpolate, you can let the <Animated.View> straight up calculate the height. The interpolation is needed if you want to animate, for example, a rotation, and you need to calculate the degrees to which the element must move.
You need to pass your dynamic height into the animation, as your to: value
I set up a simple useEffect hook checking for changes in the someVariable and dynamicHeight prop
This solution will let you set the height dynamically through a prop.
However if you want to calculate the height based on elements which are present in the View you may want to check #vivek-doshi 's answer
You can use React refs. This allows you to access a component directly.
const el = React.useRef(null);
Assign el to the ref of the container div of whatever content you have. Then hide/show the div using the visibility style:
<div style={{ visibility: !expanded && "hidden" }} ref={el}>
<View /* snip */>
{ renderAdditionalContent() }
/* -- snip -- */
</View>
</div>
Then in the toggleDropdown function you can access the height of the component via the .current.offsetHeight property:
Animated.timing(animatedHeight, {
toValue: el.current.offsetHeight,
duration: 200,
}).start()
Refs can be used on any html element and allow you to access the element's raw properties. Animations are probably one of the most common use cases for them.
Demo: https://snack.expo.io/heLsrZNpz
You can read more here: https://reactjs.org/docs/refs-and-the-dom.html

How can i access my state variables in StyleSheet?

I calculate various margin's based on the images i'm displaying in my app, which range in different pixel widths each time images are selected, so my need us quite dynamic. Once i calculate the margin widths, i save them in state variables marginL and marginR.
However i can't seem to access these in StyleSheet, i just get an error message to say marginLeft is undefined.
const styles = StyleSheet.create({
container: {
flex: 1,
},
gap: {
flex: 0.5,
marginLeft: this.state.marginL,
marginRight: this.state.marginR
}
})
How do i get access to my variables?
I'm not sure about dynamically changing in StyleSheet value. But when you calculate the margin widths you can override your style something like below
<View style={[styles.gap, {marginLeft: this.state.marginL, marginRight: this.state.marginR}]} />
Check complete example
import React, { Component } from "react";
import { View, StyleSheet } from "react-native";
export default class App extends Component {
state = {
marginL: 10,
marginR: 20
};
render() {
return (
<View style={styles.container}>
<View style={[ styles.gap, { marginLeft: this.state.marginL, marginRight: this.state.marginR }]} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "red"
},
gap: {
flex: 0.5,
marginLeft: 0,
marginRight: 0,
backgroundColor: "green"
}
});
This not be the optimal solution but hope this helps you. Feel free for doubts.

what do I need to do that all 4 of my buttons open a different new page

Im pretty new to react so sorry if the question is bad described
i have 4 buttons and I want for each a different page that opens onPress
I can bind one open new page function to all four buttons but I can't bind one function to one button only to the button "group"
import React, { Component } from 'react';
import { FlatList, Button, StyleSheet, Text, View } from 'react-native';
import TrainingListItem from '../components/TrainingListItem'
export default class TrainingScreen extends Component {
render() {
return (
<View style={styles.container}>
<FlatList contentContainerStyle={{flexGrow: 1, justifyContent: 'center'}} data={[
{ "_id": 1, name: "Disziplin"},
{ "_id": 2, name: "Selbstbewusstsein"},
{ "_id": 3, name: "Selbstwertgefühl"},
{ "_id": 4, name: "Vertrauen"},
]}
keyExtractor={item => item.name}
renderItem={
({item}) => (
<TrainingListItem training={item.name} onPress={(id) => {}}/>
)
}
ItemSeparatorComponent={() => <View style={styles.listSeparator}/>}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 30,
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
listView: {
flex: 1,
justifyContent: 'center',
},
listSeparator: {
padding: 30
}
});
Ok, i didn´t understood that much but i will try.
You want to open a Different page with each button ?
If so, use your name property to do so, just call your page the same name it has in the button
So you can do this in renderItem
renderItem={
({item}) => (
<TrainingListItem training={item.name} onPress={(id) => {this.gotopage(item.name)}}/>
)
}
and handle that function like so
gotopage(NameOfPage){
//Navigate to page with name NameOfPage
//with react-navigation it will be :
this.props.navigation.navigate(NameOfPage)
}
Is that what you want to do?

Resources