Conditional Rendering of child elements in React - javascript

I am trying to write a reusable Header Component in React-Native. I want to write it in a ways that the left and right button can be passed as child components. To know where to render which button I want to pass a prop like rightIcon or leftIcon. However I don't know how to access these props.
This is my App.js file
import React from 'react';
import {StyleSheet, TouchableHighlight, View} from 'react-native';
import Header from "./src/Header";
import {Ionicons} from '#expo/vector-icons';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Header headerText={"Barcode Scanner"}>
<TouchableHighlight righticon>
<Ionicons name="md-barcode" size={36} color="white"></Ionicons>
</TouchableHighlight>
</Header>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
});
And this is the Header Component
import React from 'react';
import {Text, View} from 'react-native';
export default class Header extends React.Component {
render() {
const {textStyle, viewStyle, rightButton} = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{this.props.headerText}</Text>
<View style={rightButton}>
{this.renderRightChild()}
</View>
</View>
);
}
renderRightChild = () => {
console.log("Check if rightIcon Prop is set");
}
}
const styles = {
viewStyle: {
backgroundColor: '#5161b8',
justifyContent: 'center',
alignItems: 'center',
height: 80,
paddingTop: 25,
shadowColor: '#000',
shadowOffset: {width: 0, height: 2},
shadowOpacity: 0.2,
elevation: 2,
position: 'relative'
},
textStyle: {
color: '#fff',
fontSize: 20
},
rightButton: {
position: 'absolute',
top:
35,
right:
20
}
}
;
I already tried to use React.Children.toArray but this always throws an error that the request entity is too large.
Thanks for all the answers

I guess you can always use a render prop that way you can not only decide whether to render left/right icon component but the component rendering the icon does not even have to know what to render:
The term “render prop” refers to a simple technique for sharing code
between React components using a prop whose value is a function.
return (
<View style={styles.container}>
<Header
headerText={"Barcode Scanner"}
renderRightIcon={() => (
<TouchableHighlight righticon>
<Ionicons name="md-barcode" size={36} color="white" />
</TouchableHighlight>
)}
/>
</View>
);
Then you can use call the right icon as a function:
return (
<View style={viewStyle}>
<Text style={textStyle}>{this.props.headerText}</Text>
{renderLeftIcon && (
<View style={leftButton}>
{renderLeftIcon()}
</View>)
}
{renderRightIcon && (
<View style={rightButton}>
{renderRightIcon()}
</View>)
}
</View>
);

You render both components, the right and left and you put an if condition inside state.
Header Component render method
render() {
const { leftOrRight } = this.props // right - true, left - false
return(
...
{ leftOrRight ? <RightIcon /> : <LeftIcon />}
);
}
Inside Component that calls Header
import Header from './somepath ...';
class Something extends React.Component {
this.state = { leftOrRight }
render() {
return(
<Header leftOrRight = {this.state.LeftOrRight}/>
);
}
}
You could have a function that sets leftOrRight in your parent class

One way to do this is write a Header Component and pass all the things, as props, which you can then access them in Header Components Props like..
<Header title="HeaderTitle"
leftButtonTitle="LeftButton"
rightButton={canBeAObjectWithSomeInfo}
leftButtonClick={handleClick} />
and then in your header component(can be class or a function)
const Header = ({}) => (
<View>
<View onPress={this.props.handleClick}>{this.props.leftButton}</View>
<View>{this.props.title}</View>
<View onPress={this.props.handleRightClick}>{this.props.rightButton}</View>
</View>
)
something like this you can have and then you can design header accordingly

Related

React Native: How to call a function from another component

I am making a simple app to practice using modals. I have my modal component in a separate file from App.js. I have a button inside the modal and outside of the modal that should toggle the visibility of the modal. To handle the visibility toggle, I have a method in App.js, setVisibility, that takes in a boolean arg and sets the isVisibility state. When I had the modal component defined within App.js earlier everything was working fine, but I'm not sure about accessing and setting the state of a component from another file.
My modal component:
import React, { Component } from "react";
import { View, Modal, TouchableHighlight, Text } from 'react-native';
export default class AppModal extends Component {
constructor(props) {
super(props);
this.state = {
isVisible: this.props.isVisible
}
this.toggleVisibility = this.toggleVisibility.bind(this);
}
toggleVisibility(show) {
this.props.setVisibility(show);
}
render() {
return (
<View>
<Modal
animationType='slide'
visible={this.state.isVisible}
onRequestClose={() => this.toggleVisibility(false)}
>
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
<Text>Inside the modal</Text>
<TouchableHighlight style={{padding: 10}} onPress={() => this.toggleVisibility(false)} >
<Text>Press me</Text>
</TouchableHighlight>
</View>
</Modal>
</View>
)
}
}
My app.js:
import React, { Component } from 'react';
import { Text, View, TouchableHighlight } from 'react-native';
import AppModal from './AppModal'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isVisible: false
}
this.setVisibility = this.setVisibility.bind(this);
}
setVisibility(show) {
this.setState({
isVisible: show
})
}
render() {
return (
<View style={{flex: 1}}>
<AppModal toggle={this.setVisibility} isVisible={this.state.isVisible} />
<View style={{justifyContent: 'center', alignItems: 'center', flex: 1}}>
<Text>Outside of the modal</Text>
<TouchableHighlight style={{padding: 10}} onPress={() => {this.setVisibility(true); console.log(this.state);}} >
<Text>Press me</Text>
</TouchableHighlight>
</View>
</View>
)
}
}
Now I get an error when I press the button in the modal which tells me that 'this.props.setVisibility is not a function'.
Please let me know if I can explain my question better. Thank you in advance!
You send the toggling callback method as toggle={this.setVisibility}, not setVisibility={this.setVisibility}, so your callback must be:
toggleVisibility(show) {
this.props.toggle(show);
}

How to return React-Native Component in Vue-Native method

I write application with Vue-Native and in my case i use React-Native component, that uses prop function renderItem returning React-Native element (for ex. <View>)
I have Vue element wineCard, which i should return in this function
In React this function looks something like this:
renderItem = ({ item, index, move, moveEnd, isActive }) => {
return (
<TouchableOpacity
onLongPress={move}
onPressOut={moveEnd}
>
<wineCard wineItem={item} />
</TouchableOpacity>
)
}
How it can be done using Vue-Native?
You can return a React-Native component in Vue-Native by making a .js file with the React-native component in it and put it in your Components directory in your Vue-Native application.
for example:
Components/flex.js:
import React, { Component } from 'react';
import { View } from 'react-native';
export default class FlexDirectionBasics extends Component {
render() {
return (
// Try setting `flexDirection` to `column`.
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
<View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
</View>
);
}
};
Views/Home.Vue:
<template>
<view>
<Flex/>>
</view>
</template>
<script>
import Flex from '.././Components/flex.js'
export default {
components:{Flex}
}
</script>

Read the value of Parent state and pass Boolean to Child Component React

I'm having trouble learning how to pass data between parent and child in React Native.
In my parent component I have a state property (audioPlaying) which is a Boolean value.
state = {
//The title informs the Button and TitleArea components
title: 'hello',
audioPlaying: false,
};
I'd like to change that value on the press of a button (onPress).
<Button
title={this.state.title}
onPress={this.playPauseHandler}
audioPlaying={this.state.audioPlaying}
/>
...by calling the playPauseHandler.
playPauseHandler = () => {
this.setState(prevState => ({
audioPlaying: !prevState.audioPlaying
}));
}
Then in my child (Button) Component I want to evaluate the audioPlaying state property. If it's true, I want to show one things and false I want to show something else.
<View style={styles.playBtnStyle}>
{this.props.audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)}
}
</View>
However, when I run this I get undefined for the value of audioPlaying.
React Native Error Message
Here are the full files for both:
App.js
import React, { Component } from 'react';
import { View, StatusBar } from 'react-native';
import Carousel from './src/components/Carousel/Carousel';
import Button from './src/components/Button/Button';
import TitleArea from './src/components/TitleArea/TitleArea';
import MapArea from './src/components/MapArea/MapArea';
const styles = {
container: {
flex: 1,
justifyContent: 'space-between',
},
playArea: {
flex: 1,
},
};
export default class App extends Component {
state = {
//The title informs the Button and TitleArea components
title: 'hello',
audioPlaying: false,
};
playPauseHandler = () => {
this.setState(prevState => ({
audioPlaying: !prevState.audioPlaying
}));
}
render() {
return (
<View style={styles.container}>
<TitleArea title={this.state.title} />
<StatusBar hidden={false} />
<Carousel />
<MapArea />
<Button
title={this.state.title}
onPress={this.playPauseHandler}
audioPlaying={this.state.audioPlaying}
/>
</View>
);
}
}
Button.js
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, Dimensions } from 'react-native';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
const { width } = Dimensions.get('window');
const height = width * 0.2;
const styles = {
textStyle: {
color: '#87888C',
fontSize: 18,
fontWeight: '600',
backgroundColor: 'white',
alignSelf: 'center',
},
buttonContainer: {
height,
flexDirection: 'row',
backgroundColor: 'white',
alignItems: 'center',
},
playBtnStyle: {
marginLeft: 50,
backgroundColor: 'white',
},
childStyle: {
flex: 1,
},
};
const button = (props) => {
return (
<View style={styles.buttonContainer}>
<TouchableOpacity>
<View style={styles.playBtnStyle}>
{this.props.audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)}
}
</View>
</TouchableOpacity>
<View style={styles.childStyle}>
<Text style={styles.textStyle}>Chapter 1: {props.title}</Text>
</View>
</View>
);
}
export default button;
There is no this in the context of button. That is just a function returning JSX.
Instead, use props
<View style={styles.playBtnStyle}>
{props.audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)}
</View>
Ok so I solved my own problem! (step one to being a developer)
Two issues:
Capturing Touch Events
React Native has what's called Touchables. According to the documentation these are "wrappers that make views respond properly to touches".
TouchableOpacity, the one I'm using:
On press down, the opacity of the wrapped view is decreased, dimming it. Opacity is controlled by wrapping the children in an Animated.View, which is added to the view hierarchy.
https://facebook.github.io/react-native/docs/touchablewithoutfeedback#onpress
All Touchables accept the onPress prop. So by adding the onPress prop to the Touchable, I'm able to capture the touch event instead of just firing it.
Passing Callback to Parent
This article helped me understand more about how a parent function can be called from a child.
https://medium.com/#thejasonfile/callback-functions-in-react-e822ebede766
So I'm calling playPause() (I renamed the prop and destructured it) in TouchableOpacity, which fires from a touch event causing state to change and component to re-render.
const button = (props) => {
const {
title,
audioPlaying,
playPause,
} = props;
return (
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={() => playPause()}>
<View style={styles.playBtnStyle}>
{audioPlaying === false ? (
<MaterialIcons
name='play-arrow'
size={50}
color="#87888C"
/>
) : (
<MaterialIcons
name='pause'
size={50}
color="#87888C"
/>
)
}
</View>
</TouchableOpacity>
<View style={styles.childStyle}>
<Text style={styles.textStyle}>
Chapter 1:
{title}
</Text>
</View>
</View>
);
};

How to render a loader until data is fetched in React Native

I am fetching data through an async request. I know that I need to wait for the api request to complete before displaying the data. Unfortunately, I'm not sure how to create a loader to wait for the data to load.I am new to react, so if I could also get help with implementing it as well, that would be fantastic! Here is my current code:
import React, { Component, PropTypes } from 'react';
import { View, Text, ListView, StyleSheet, TouchableHighlight} from 'react- native';
import Header from '../Components/Header';
import Api from '../Utility/Api';
export default class CalendarPage extends Component {
constructor(props) {
super(props);
}
async componentWillMount() { this.setState(
{data: await Api.getDates()},
)
}
render() {
return (
<View style={{flex: 1}}>
<Header pageName="Calendar" navigator={this.props.navigator}/>
<View style = {{flex:9}}>
<View>
{ this.state.data.days[0].items.map((item) => (
<View>
<Text>{item.summary}</Text>
<Text>{item.start.dateTime}</Text>
<Text>{item.description}</Text>
</View>
))}
</View>
</View>
</View>
);
}
}
A simple example using ActivityIndicator -
import ActivityIndicator
import { View, Text, ListView, StyleSheet, TouchableHighlight, ActivityIndicator} from 'react- native';
set data state to null
constructor(props) {
super(props);
this.state = {
data: null
}
}
do conditional rendering
render() {
if (!this.state.data) {
return (
<ActivityIndicator
animating={true}
style={styles.indicator}
size="large"
/>
);
}
return (
<View style={{flex: 1}}>
<Header pageName="Calendar" navigator={this.props.navigator}/>
....
....
</View>
);
}
}
indicator style
const styles = StyleSheet.create({
indicator: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
height: 80
}
});
Although solution proposed by #vinayr works fine but user will still be able to click on screen and perform some action even while loader is being shown which can lead to crash.
One solution is wrap loader inside a Modal.
import React, { Component } from 'react';
import {
StyleSheet,
View,
Modal,
ActivityIndicator,
} from 'react-native';
const styles = StyleSheet.create({
modalBackground: {
flex: 1,
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'space-around',
backgroundColor: '#00000040',
},
activityIndicatorHolder: {
backgroundColor: '#FFFFFF',
height: 100,
width: 100,
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-around',
},
});
const SmartLoader = (props) => {
const {
isLoading,
...attributes
} = props;
return (
<Modal
transparent
animationType={'none'}
visible={isLoading}
onRequestClose={() => { console.log('Noop'); }}
>
<View style={styles.modalBackground}>
<View style={styles.activityIndicatorHolder}>
<ActivityIndicator
animating={isLoading}
size="large"
/>
</View>
</View>
</Modal>
);
};
export default SmartLoader;
After that you can use it anywhere in your component, user will not be able to perform any action till loader is finished ( made hidden based on flag)

TabBarIOS in ReactNative not working, items overlapping each other

So im making an app and this is my code:
import React, { Component } from 'react';
import {
View,
Text,
TabBarIOS,
StyleSheet,
Dimensions
} from 'react-native';
//import Styles from './LayoutStyle.js';
class Layout extends Component {
constructor(){
super()
this.state = {selectedTab: 'tabThree'}
}
setTab(tabId){
this.setState({selectedTab: tabId})
}
render() {
return(<View style={Styles.Layout}>
<TabBarIOS>
<TabBarIOS.Item
systemIcon='history'
selected={this.state.selectedTab === 'tabOne'}
onPress={() => this.setTab('tabOne')}>
<View>
<Text>Jure1</Text>
</View>
</TabBarIOS.Item>
<TabBarIOS.Item
systemIcon='bookmarks'
selected={this.state.selectedTab === 'tabTwo'}
onPress={() => this.setTab('tabTwo')}>
<View>
<Text>Jure2</Text>
</View>
</TabBarIOS.Item>
<TabBarIOS.Item
systemIcon='more'
selected={this.state.selectedTab === 'tabThree'}
onPress={() => this.setTab('tabThree')}>
<View>
<Text>Jure3</Text>
</View>
</TabBarIOS.Item>
</TabBarIOS>
</View>
);
}
}
const Styles = StyleSheet.create({
Layout: {
alignItems: "center",
justifyContent: "center",
height: Dimensions.get('window').height,
width: Dimensions.get('window').width,
},
TabBar: {
backgroundColor: 'grey'
}
});
export default Layout;
Well what i expected was an app where you have a TabBar on the bottom with three different items to choose from and it should look like i would in a native ios app. Well thats not the case, what i get is this:
Well what should i do? How do i style this item to not overlap? Any ideas?
The layout style is causing the inner content to get centred oddly, change Layout style to this:
Layout: {
flex:1,
}
Additionally, when trying to draw a scene from the tab clicked you will want to use a render function inside the TabBarIOS.Item object, react native provides some good examples of how to do this in the documentation: https://facebook.github.io/react-native/docs/tabbarios.html
I would highly recommend placing a navigator for each object which allows you to have much more control over the scene changes:
<TabBarIOS.Item
systemIcon='history'
title= 'Jure1'
selected={this.state.selectedTab === 'tabOne'}
onPress={() => this.setTab('tabOne')}>
<View style = {{flex:1}}>
<Navigator
renderScene={this._renderScene}
/>
</View>
</TabBarIOS.Item>

Categories

Resources