Just starting out with React Native, and was curious on how best to achieve passing events to a parent component from children buttons. So for example:
import React, { Component } from 'react';
import ViewContainer from '../components/ViewContainer';
import SectionHeader from '../components/SectionHeader';
import {
View
} from 'react-native';
class App extends Component {
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {() => console.log("SUCCESS")} ></SectionHeader>
</ViewContainer>
);
}
}
module.exports = ProjectListScreen;
and here is my section header. I was attempting to use an Event Emitter, but not working the way I'd hoped, unless I missed something.
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import EventEmitter from "EventEmitter"
import {
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native';
class SectionHeader extends Component {
componentWillMount() {
console.log("mounted");
this.eventEmitter = new EventEmitter();
this.eventEmitter.addListener('onCreateNew', function(){
console.log('myEventName has been triggered');
});
}
_navigateAdd() {
this.eventEmitter.emit('onCreateNew', { someArg: 'argValue' });
}
_navigateBack() {
console.log("Back");
}
render() {
return (
<View style={[styles.header, this.props.style || {}]}>
<TouchableOpacity style={{position:"absolute", left:20}} onPress={() => this._navigateBack()}>
<Icon name="chevron-left" size={20} style={{color:"white"}} />
</TouchableOpacity>
<TouchableOpacity style={{position:"absolute", right:20}} onPress={() => this._navigateAdd()}>
<Icon name="plus" size={20} style={{color:"white"}} />
</TouchableOpacity>
<Text style={styles.headerText}>Section Header</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00B9E6',
flexDirection: 'column',
//paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
});
module.exports = SectionHeader;
Thanks for your help!
Not sure if this is the right way to go about this, but using this.props.onCustomEvent from the nested Button's onPress event seems to work fine. Please let me know if there is a better way to go about doing this.
App:
import React, { Component } from 'react';
import ViewContainer from '../components/ViewContainer';
import SectionHeader from '../components/SectionHeader';
import {
View
} from 'react-native';
class App extends Component {
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {() => console.log("SUCCESS")} ></SectionHeader>
</ViewContainer>
);
}
}
module.exports = ProjectListScreen;
Section Header
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import {
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native';
class SectionHeader extends Component {
render() {
return (
<View style={[styles.header, this.props.style || {}]}>
<TouchableOpacity style={{position:"absolute", left:20}} onPress={() => this.props.onCreateNew()}>
<Icon name="chevron-left" size={20} style={{color:"white"}} />
</TouchableOpacity>
<Text style={styles.headerText}>Section Header</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00B9E6',
flexDirection: 'column',
//paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
});
module.exports = SectionHeader;
In addition to #arbitez's answer, if you want to maintain scope then you should make a method and bind to it, eg:
Parent:
constructor(props) {
super(props)
// ........
this.onCreateNew=this.onCreateNew.bind(this);
}
onCreateNew() {
console.log('this: ', this);
}
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {this.onCreateNew} ></SectionHeader>
</ViewContainer>
);
Related
I've asked a question already, and modified as much as I can, but still in trouble.
Combined all children Screen js files into App.js
When I compile and run, App shows LoginScreen.js, not LoadingScreen.js.
Also, onPress={this.props.navigation.navigate('Loading')} does not works. If I press the button, it shows nothing.
What am I still missing?
import React from 'react'
import { StyleSheet, Platform, Image, Text, View, TextInput, Button, ActivityIndicator } from 'react-native'
import { createAppContainer, createSwitchNavigator } from 'react-navigation'
import { createBottomTabNavigator } from 'react-navigation-tabs';
import Ionicons from 'react-native-vector-icons/Ionicons';
class LoadingScreen extends React.Component {
render() {
return (
<View style={styles.loadingcontainer}>
<Text>Loading</Text>
<ActivityIndicator size="large" />
<Button title="Move to LoginScreen" onPress={this.props.navigation.navigate('Login')} />
</View>
)
}
}
class LoginScreen extends React.Component {
state = { email: '', password: '' }
render() {
return (
<View style={styles.logincontainer}>
<Button
title='Sign in' onPress={this.props.navigation.navigate('Loading')}
/>
<Button
title='Sign Up'
/>
</View>
)
}
}
const styles = StyleSheet.create({
loadingcontainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
logincontainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
logintextInput: {
height: 40,
width: '90%',
borderColor: 'gray',
borderWidth: 1,
marginTop: 8
},
})
const App = createSwitchNavigator(
{
Loading: {
screen: LoadingScreen,
},
Login: {
screen: LoginScreen,
},
}
);
export default createAppContainer(App);
Thank you for your help.
For just navigation, you can use a stack navigator. The switch navigator used for conditional rendering of two different stacks. Anyways you can set loading as the first screen buy setting initialRouteName to loading. Here is an example
createSwitchNavigator(
{
LimitedAccess: {
screen: Trial,
},
AppScreens: {
screen: AppScreens,
},
AuthScreens: {
screen: AuthScreens,
},
},
{
initialRouteName: signedIn ? 'AppScreens' : 'AuthScreens',
},
),
);
Note: Here signedIn is a conditional operator which decides the rendering of stacks.
The way your props are currently defined causes them to be instantly executed.
The onPress prop is instantly executed.
return (
<View style={styles.loadingcontainer}>
<Text>Loading</Text>
<ActivityIndicator size="large" />
<Button title="Move to LoginScreen" onPress={this.props.navigation.navigate('Login')} />
</View>
)
You should instead attach a function to onPress that can be executed when the button is pressed.
return (
<View style={styles.loadingcontainer}>
<Text>Loading</Text>
<ActivityIndicator size="large" />
<Button title="Move to LoginScreen" onPress={() => this.props.navigation.navigate('Login')} />
</View>
)
Your onPress-calls are running instantly, which causes your problems.
Change to:
import React from 'react'
import { StyleSheet, Platform, Image, Text, View, TextInput, Button, ActivityIndicator } from 'react-native'
import { createAppContainer, createSwitchNavigator } from 'react-navigation'
import { createBottomTabNavigator } from 'react-navigation-tabs';
import Ionicons from 'react-native-vector-icons/Ionicons';
class LoadingScreen extends React.Component {
render() {
return (
<View style={styles.loadingcontainer}>
<Text>Loading</Text>
<ActivityIndicator size="large" />
<Button title="Move to LoginScreen" onPress={() => this.props.navigation.navigate('Login')} />
</View>
)
}
}
class LoginScreen extends React.Component {
state = { email: '', password: '' }
render() {
return (
<View style={styles.logincontainer}>
<Button
title='Sign in' onPress={() => this.props.navigation.navigate('Loading')}
/>
<Button
title='Sign Up'
/>
</View>
)
}
}
const styles = StyleSheet.create({
loadingcontainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
logincontainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
logintextInput: {
height: 40,
width: '90%',
borderColor: 'gray',
borderWidth: 1,
marginTop: 8
},
})
const App = createSwitchNavigator(
{
Loading: {
screen: LoadingScreen,
},
Login: {
screen: LoginScreen,
},
}
);
export default createAppContainer(App);
I want to click the TouchableOpacity and set the state true so that will open. I am getting error. and how to align the button in center at the header? alignSelf is not working.
`
import React, {Component} from 'react';
import {
StyleSheet,
SafeAreaView,
View,
TouchableOpacity,
Text,
} from 'react-native';
import Menu from '../../src/components/menubar';
export default class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {isMenubarDisplayed: false};
}
static navigationOptions = {
headerTitle: () => {
return (
<TouchableOpacity
onPress={()=> this.setState({isMenubarDisplayed: true})}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
);
},
headerTitleStyle: {
alignSelf: 'center',
flex: 1,
},
};
render() {
return (
<SafeAreaView style={styles.container}>
{this.state.isMenubarDisplayed ? (
<Menu />
) : null}
</SafeAreaView>
);
}
}`
You need to try this, expo-snack .
This is my below code for search.js ,
import * as React from 'react';
import { Text, View, StyleSheet,TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import Menu from './menu';
import Icon from 'react-native-vector-icons/FontAwesome';
export default class Search extends React.Component {
constructor(props){
super(props);
this.state={
isMenubarDisplayed: false,
}
}
static navigationOptions = ({ navigation }) => {
return {
headerTitle: () => {
return (
<TouchableOpacity onPress={navigation.getParam('toggleMenu')}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
);
},
};
};
toggleMenu = () => {
this.setState({ isMenubarDisplayed: !this.state.isMenubarDisplayed})
}
renderMenu = () => (
<Menu />
)
componentDidMount(){
this.props.navigation.setParams({
toggleMenu: this.toggleMenu
});
}
render() {
return (
<View style={styles.container}>
{this.state.isMenubarDisplayed?this.renderMenu():<View></View>}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Hope it helps. feel free for doubts.
This is all you need https://reactnavigation.org/docs/en/header-buttons.html#header-interaction-with-its-screen-component
static navigationOptions = ({ navigation }) => {
return {
headerTitle: () => {
return (
<View style={{ flex: 1, alignItems: 'center' }}>
<TouchableOpacity onPress={navigation.getParam('toggleMenu')}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
</View>
);
},
};
};
componentDidMount() {
this.props.navigation.setParams({ toggleMenu: this.toggleMenu });
}
toggleMenu = () => {
this.setState({isMenubarDisplayed: true});
}
i have small issue with below Code in Android As the Back Button of Device Make out of my Application , i surfed in the forum but i cant find the solution for that issue .
so how Handle This handle it inside my Code
Tab1.js File is below
import React from 'react';
import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
import { WebView } from 'react-native-webview';
import { Icon, Left, Right } from 'native-base';
export default class WebViewScreen extends React.Component {
buttonOnPress = () => {
console.warn('button Pressed')
}
constructor(props) {
super(props);
this.WEBVIEW_REF = React.createRef();
}
render() {
return (
<View style={styles.container}>
<Icon style={styles.Icon} onPress={() => this.WEBVIEW_REF.current.goBack()}>
<Text style={{textAlign: 'left', color: '#000000' }}></Text>
<Icon name="arrow-back" />
</Icon>
<WebView
source={{ uri: 'http://www.catalogmasr.com' }}
style={styles.webview}
ref={this.WEBVIEW_REF}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'stretch',
},
webview: {
flex: 1,
},
icon: {
padding : 2,
backgroundColor: '#cdcccc',
justifyContent: 'center',
alignItems: 'center'
}
});
All of the times I needed to handle the Back button at a Webview (or component), I needed to tell the component that my function was the correct handler/listener to the event.
componentDidMount() {
if (Platform.OS === 'android') {
BackHandler.addEventListener('hardwareBackPress', this.buttonOnPress );
} }
componentWillUnmount() {
if (Platform.OS === 'android') {
BackHandler.removeEventListener('hardwareBackPress');
} }
Best of luck, hope it fixes it for you.
I am trying to clean up my styles by using a StyleSheet, but I can't seem to get it to work. I believe the error (seen below) is caused when I attempt to create a StyleSheet (const styles = EStyleSheet.create.
Note I am using react-native-extended-stylesheet However this is not the problem. This also happens with react-native's stylesheet.
This a picture of the error:
This this my code:
import React, { Component } from "react";
import {
View,
Text,
Button,
Image,
StyleSheet,
TextInput,
KeyboardAvoidingView,
TouchableOpacity
} from "react-native";
export default class Login extends Component {
render() {
return (
<View style={styles.wrapper}>
<Text>Login screen </Text>
<KeyboardAvoidingView behavior="padding" style={styles.loginContainer}>
<TextInput
placeholder="username or email"
placeholderTextColor='whitesmoke'
style={styles.input}
/>
<TextInput
placeholder="password"
secureTextEntry
placeholderTextColor='whitesmoke'
style={styles.input}
/>
<TouchableOpacity style={styles.loginbutton} onPress={() => this.props.navigation.navigate("Grades")}>
<Text style={{
textAlign: 'center',
color: "whitesmoke",
fontWeight: '700',
}}>
Login
</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
loginContainer: {
paddingHorizontal: 9,
backgroundColor: "red"
},
input: {
paddingHorizontal: 10,
marginBottom: 10,
color: '#f1c40f', //sunflower color
backgroundColor: '#3498db',
},
logo: {
width: 231,
height: 231
},
wrapper: {
flex: 1,
alignItems: "center",
justifyContent: "center"
}
});
Here is my app.js.
import React from "react";
import { Font } from "expo";
import { Root } from "./app/router";
import { FontError } from "./app/components/fontError";
export default class App extends React.Component {
state = {
fontLoaded: false
};
async componentDidMount() {
await Font.loadAsync({
Arial: require("./app/resources/Arial.ttf")
});
this.setState({ fontLoaded: true });
}
render() {
if (!this.state.fontLoaded) return <FontError/>;
return <Root />;
}
// ...
}
My root component is router.js, I am using react-navigation.
import React, { Component } from "react";
import { createStackNavigator } from "react-navigation";
import {
Login,
Grades,
} from "./screens";
export const Root = createStackNavigator({
Login: {screen: Login},
Grades: {screen: Grades},
});
Feel free to ask me to append any additional information.
Thanks for your help in advance.
To fix my error I just re-cloned the repo and added back in all the code I wrote.
Thankfully I didn't push this error to the master branch.
This fixed the error.
*note in the process I got rid off react-native-extended-stylesheet, so it might have been cause of error.
I'm new to react native and I'm doing a simple android app.
I added a DrawerLayoutAndroid that I can drag from the left of my screen. However I'd like to open it when I click on my menu icon in my ToolbarAndroid having a Navigator gave me the error
"undefined is not an object (evaluating 'this.refs['DRAWER']')"
Then I solved this mistake but I got another one being
"undefined is not an object (evaluating 'this.props.sidebarRef').
My code is this:
MyToolbar.js
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text
} from 'react-native';
var ToolbarAndroid = require('ToolbarAndroid');
class MyToolbar extends Component {
render() {
var navigator = this.props.navigator;
return (
<ToolbarAndroid
title={this.props.title}
navIcon={require('./icons/ic_menu_white_24dp.png')}
style = {styles.toolbar}
titleColor={'white'}
onIconClicked={this._onIconClicked}/>
);
}
_onIconClicked(){
this.props.sidebarRef.refs['DRAWER'].openDrawer();
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
toolbar: {
height: 56,
backgroundColor: '#08AE9E',
width: 370,
alignItems: 'center'
}
});
module.exports = MyToolbar;
OpenDrawerFromToolbar.js
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
Navigator,
TouchableHighlight,
TouchableOpacity,
DrawerLayoutAndroid,
ScrollView,
} from 'react-native';
var ToolbarAndroid = require('ToolbarAndroid');
var MyToolbar = require('./MyToolbar');
var MenuItem = require('./MenuItem');
class OpenDraweFromToolbar extends Component {
render() {
var navigationView = (
<View style={{flex: 1, backgroundColor: '#fff'}}>
<Text style={{margin: 10, fontSize: 15, textAlign: 'left'}}>I'm in the Drawer!</Text>
<MenuItem
title="Calendar"
selected={this.props.activeTab === 'Calendar'}
//onPress={this.onTabSelect.bind(this, 'schedule')}
icon={require('./icons/ic_today_black_24dp.png')}
//selectedIcon={scheduleIconSelected}
/>
<MenuItem
title="Offers"
selected={this.props.activeTab === 'Offers'}
//onPress={this.onTabSelect.bind(this, 'schedule')}
icon={require('./icons/ic_today_black_24dp.png')}
//selectedIcon={scheduleIconSelected}
/>
<MenuItem
title="Boats"
selected={this.props.activeTab === 'Boats'}
//onPress={this.onTabSelect.bind(this, 'schedule')}
icon={require('./icons/ic_directions_boat_black_24dp.png')}
//selectedIcon={scheduleIconSelected}
/>
<MenuItem
title="Profile"
selected={this.props.activeTab === 'Profile'}
//onPress={this.onTabSelect.bind(this, 'schedule')}
icon={require('./icons/ic_account_circle_black_24dp.png')}
//selectedIcon={scheduleIconSelected}
/>
</View>
);
return (
<DrawerLayoutAndroid
drawerWidth={300}
drawerPosition={DrawerLayoutAndroid.positions.Left}
renderNavigationView={() => navigationView}
ref={'DRAWER'}>
<MyToolbar style={styles.toolbar}
title={'Calendar'}
navigator={this.props.navigator}
sidebarRef={this}/>
<View style={{flex: 1, alignItems: 'center'}}>
<Text style={{margin: 10, fontSize: 15, textAlign: 'right'}}>Hello</Text>
<Text style={{margin: 10, fontSize: 15, textAlign: 'right'}}>World!</Text>
</View>
</DrawerLayoutAndroid>
);
}
gotoPersonPage() {
this.props.navigator.push({
id: 'PersonPage',
name: 'hola',
});
}
_setDrawer() {
this.refs['DRAWER'].openDrawer();
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
toolbar: {
height: 200,
backgroundColor: '#08AE9E',
width: 370,
alignItems: 'center'
}
});
module.exports = OpenDraweFromToolbar;
and calendarpage.js
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
Navigator,
TouchableHighlight,
TouchableOpacity,
DrawerLayoutAndroid,
ScrollView,
MenuItem,
} from 'react-native';
var ToolbarAndroid = require('ToolbarAndroid');
var MyToolbar = require('./MyToolbar');
var OpenDrawerFromToolbar = require('./OpenDrawerFromToolbar');
class CalendarPage extends Component {
render() {
return (
<Navigator
initialRoute = {{ name: 'OpenDrawerFromToolbar', index: 0 }}
renderScene={this.renderScene.bind(this)}
configureScene={ () => { return Navigator.SceneConfigs.PushFromRight; }}
/>
);
}
renderScene(route, navigator) {
//_navigator = navigator;
return (
<OpenDrawerFromToolbar
route={route}
navigator={navigator}
//data={route.data}
/>
);
}
}
module.exports = CalendarPage;
Does someone know what should i try to solve this mistake? i checked this same forum and found similar answers but none of them worked for me.
Thanks.
you should pass drawerlayout opening method as props to toolbar like this:
sidebarRef={()=>this._setDrawer()}
And in your toolbar component you should call sidebarRef as props, which automatically call the drawerlayout opening method of previous OpenDraweFromToolbar.js like this:
onIconClicked={this.props.sidebarRef}
Finally your toolbar icon will be called. This might help you.