React Native: How to call a function from another component - javascript

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);
}

Related

react-native NumericInput component doesn't work on web browser

On the web browser, I have the + and - overlapping and clicking on them doesn't do anything. Also we can't see the number.
So I tried to run it on android and it worked properly.
But why isn't it working on the web browser ?
The code is however basic.
import React from 'react';
import { View, Text, Buttonn, FlatList } from 'react-native';
import NumericInput from 'react-native-numeric-input'
const style = require('#app/components/common/styles').style;
export default class HomeSettingsScreen extends React.Component {
constructor(props) {
super(props);
this.navigation = props.navigation;
this.state = {
homeCitiesNumber: 10,
}
}
render = () => {
return (
<View>
<View style={style.container}>
<View style={{ flexDirection: 'row', backgroundColor: 'black', width: '100%', fontFamily: 'DMSans-Regular' }}>
<Text style={style.titleContainer}>Settings</Text>
</View>
</View>
<NumericInput
value={this.state.homeCitiesNumber}
onChange={value => this.setState({homeCitiesNumber: value})}
minValue={0}
maxValue={25}
/>
<NumericInput onChange={value => console.log(value)} />
</View>
)
}
}

TypeError: undefined is not an object (evaluating '_this3.state.bind')

App.js code:
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
class HomeScreen extends React.Component {
constructor(props){
super(props);
this.state={count:0};
this.incrementCount=this.incrementCount.bind(this)
}
incrementCount(){
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text style={styles.homeScreen}>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => {
this.incrementCount();
this.props.navigation.navigate('Details');
}}
/>
</View>
);
}
}
class DetailsScreen extends React.Component {
constructor(props){
super(props);
this.state=this.state.bind(this)
this.incrementCount=this.incrementCount.bind(this)
}
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Hello </Text>
</View>
);
}
}
const AppNavigator = createStackNavigator(
{
Home: HomeScreen,
Details: DetailsScreen,
},
{
initialRouteName: 'Home',
}
);
const styles= StyleSheet.create({
homeScreen:{
}
});
export default createAppContainer(AppNavigator);
I wanted to increment a number (0), every time the user goes to the details(the second page) page. The incremented number should be displayed on the details(the second page) page.
I am a beginner in react native and I don't know how to use state in different classes. Do explain the concept of state along with the solution.
You have to send your count as prop to your DetailsPage. So in code it will look like this:
<Button
title="Go to Details"
onPress={() => {
this.incrementCount();
this.props.navigation.navigate('Details',{count:this.state.count});
}}/>
And in your DetailsScreen you have to access it like this:
class DetailsScreen extends React.Component {
constructor(props){
super(props);
//Remove these lines this is causing error and this is wrong
//this.state=this.state.bind(this)
//this.incrementCount=this.incrementCount.bind(this)
}
render() {
let count = this.props.navigation.getParam('count')
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>You were here {count} times </Text>
</View>
);
}
}

Using react-native-modalbox in ListView causes modalbox to only fill the list item space instead of full screen?

When I use the package react-native-modalbox with a FlatList (each list item can spawn a distinct modal when tapped), the modal that is spawned only fills the area of the list item instead of going full screen like it normally should.
A working snack that shows the issue is here:
https://snack.expo.io/BkICbjwWQ
For completeness I'll paste the code in here as well:
import React, { Component } from 'react';
import { Text, View, StyleSheet, FlatList, Button } from 'react-native';
import { Constants } from 'expo';
import Modal from "react-native-modalbox";
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-elements'; // Version can be specified in package.json
export default class App extends Component {
render() {
let myRefs = [];
return (
<View style={styles.container}>
<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <View>
<Modal
style={[styles.modal]}
ref={(modalItem) => {myRefs[item.key] = modalItem;} }
swipeToClose={true}
onClosed={this.onClose}
onOpened={this.onOpen}
onClosingState={this.onClosingState}>
<Text style={styles.text}>Basic modal</Text>
</Modal><Text>{item.key}</Text><Button title="Basic Modal" onPress={() => myRefs[item.key].open()} style={styles.btn}>Basic modal</Button></View>}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
I basically have the same question/problem as (react-native-modalbox stuck in child component context) but there was no answer to that question and he did not provide enough details with a minimum working example.
Your modal component is inside the rendered item. This causes it to be bound to the item. Although you can fix this issue by using appropriate props or some custom styling, this is not efficient. You would have 1000 modal components if you had 1000 items in your list.
You should move out your modal component and make it sibling to the FlatList. This way you would have only single modal. You can change the contents of the modal with a state value.
Sample
export default class App extends Component {
render() {
let myRefs = [];
return (
<View style={styles.container}>
<Modal
style={[styles.modal]}
ref={modalItem => { myRefs['modal'] = modalItem; }}
swipeToClose={true}
onClosed={this.onClose}
onOpened={this.onOpen}
onClosingState={this.onClosingState}>
<Text style={styles.text}>Basic modal</Text>
</Modal>
<FlatList
data={[{ key: 'a' }, { key: 'b' }]}
renderItem={({ item }) => (
<View>
<Text>{item.key}</Text>
<Button
title="Basic Modal"
onPress={() => myRefs['modal'].open()}
style={styles.btn}>
Basic modal
</Button>
</View>
)}
/>
</View>
);
}
}

Conditional Rendering of child elements in React

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

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)

Categories

Resources