I'm working on an app in React-Native and I am trying to render a component with children. If I use the component directly it functions 100% as expected, but if I return it from a conditional(switch) statement it doesn't render children. However the logic is functioning properly because I can actually see the state change.
I have it working 100% properly via conditional usage in another component, but it won't work in this particular case. It's imported correctly because the button renders, but without the child text inside of it, thus displaying just the button with no label text.
App.js
import React, { Component } from 'react';
import { View } from 'react-native';
import firebase from 'firebase';
import { LoginForm } from './components/LoginForm';
import { Header, Button, Spinner } from './components/common';
class App extends Component {
state = { loggedIn: null };
componentWillMount() {
firebase.initializeApp({
apiKey: 'nope',
authDomain: 'nope',
databaseURL: 'nope',
projectId: 'nope',
storageBucket: 'nope',
messagingSenderId: 'nope'
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ loggedIn: true });
} else {
this.setState({ loggedIn: false });
}
});
}
renderContent() {
switch (this.state.loggedIn) {
case true:
return (
<Button onPress={() => firebase.auth().signOut()}>
Log Out
</Button>
);
case false:
return <LoginForm />;
default:
return <Spinner size="large" />;
}
}
render() {
return (
<View>
<Header headerText="Authentication" />
{this.renderContent()}
</View>
);
}
}
export default App;
Button.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
const Button = ({ onPress, children }) => {
const { buttonStyle, textStyle } = styles;
return (
<TouchableOpacity onPress={onPress} style={buttonStyle}>
<Text style={textStyle}>
{children}
</Text>
</TouchableOpacity>
);
};
const styles = {
buttonStyle: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#fff',
borderRadius: 5,
borderWidth: 1,
borderColor: '#007aff',
marginLeft: 5,
marginRight: 5
},
textStyle: {
alignSelf: 'center',
color: '#007aff',
fontSize: 16,
fontWeight: '600',
paddingTop: 10,
paddingBottom: 10
}
};
export { Button };
The state change is in fact working properly, as I can see the state change, but when the button is rendered it does not render the child text "Log Out".
If I use the Log Out directly in the main render method it displays fine, but when I call it via the renderContent() method it does not display the "Log Out" text.
You should pass in the button text as a prop instead of as a child.
renderContent() {
switch (this.state.loggedIn) {
case true:
return <Button onPress={() => firebase.auth().signOut()} btnText="Log Out" />;
case false:
return <LoginForm />;
default:
return <Spinner size="large" />;
}
}
Button.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
const Button = ({ onPress, children }) => {
const { buttonStyle, textStyle } = styles;
return (
<TouchableOpacity onPress={onPress} style={buttonStyle}>
<Text style={textStyle}>
{this.props.btnText}
</Text>
</TouchableOpacity>
);
};
const styles = {
buttonStyle: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#fff',
borderRadius: 5,
borderWidth: 1,
borderColor: '#007aff',
marginLeft: 5,
marginRight: 5
},
textStyle: {
alignSelf: 'center',
color: '#007aff',
fontSize: 16,
fontWeight: '600',
paddingTop: 10,
paddingBottom: 10
}
};
export { Button };
Related
I'm learning react and react-native last 4 days because need build an mobile app and having the following problem when coding a react-native app:
After some research I found a way to embed a TouchableOpacity inside the top bar of DrawerNavigator, just after the hamburger button and it works giving me an alert.
The problem comes when I try to replace the alert with a call to useNavigation().navigate('Login'); from inside it onPress attribute, it throw the error below.
I want mirror the behavior of menu item on the TouchableOpacity doing the navigation.
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem., js engine: hermes
info Reloading app...
The full source follow as:
import * as React from 'react';
import { Button, Image, StyleSheet, Text, TouchableOpacity, View, Alert } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItemList,
} from '#react-navigation/drawer';
// https://reactnavigation.org/docs/use-navigation/
import { useNavigation } from '#react-navigation/native';
const getScreenCtx = content => {
return (<View style={stl.boxCtx}>{content}</View>);
}
const Home = ({ navigation }) => {
return ( getScreenCtx(<Text style={stl.boxDescr}>Home</Text>) );
}
const Login = ({ navigation }) => {
return ( getScreenCtx(<Text style={stl.boxDescr}>Login</Text>) );
}
const Logout = ({ navigation }) => {
return ( getScreenCtx(<Text style={stl.boxDescr}>Logout</Text>) );
}
const navToLogin = () => {
useNavigation().navigate('Login');
}
const NestedTabBar = props => {
return (
<>
<TouchableOpacity
style={stl.itemNav}
onPress={
() => {
//Alert.alert('NavigateToLogin');
navToLogin();
}
} >
<Text>[NavigateToLogin]</Text>
</TouchableOpacity>
</>
);
}
const ContentTopWideHamburgBar = props => {
return <NestedTabBar />;// <Text style={stl.hamburgBar}>ContentTopHamburgBar</Text>
}
const ContentColapsibleSideBarMenuHeader = props => {
return <Text style={stl.sideMenuHeader}>SideBarMenuHeader</Text>
}
const ContentColapsibleSideBarMenuFooter = props => {
return <Text style={stl.sideMenuFooter}>SideBarMenuFooter</Text>
}
const ContentColapsibleSideBarMenu = props => {
return (
<View style={stl.sideBarMenu}>
<DrawerContentScrollView {...props}>
<ContentColapsibleSideBarMenuHeader/>
<DrawerItemList {...props} />
</DrawerContentScrollView>
<ContentColapsibleSideBarMenuFooter/>
</View>
);
}
const Drawer = createDrawerNavigator();
const ContentItensNavigationRouteMap = () => {
return (
<>
<Drawer.Screen component={Home} name='Home' />
<Drawer.Screen component={Login} name='Login' />
<Drawer.Screen component={Logout} name='Logout' />
</>
);
}
const DrawerNavigator = () => {
return (
<Drawer.Navigator
screenOptions={
{
headerStyle: { backgroundColor: 'magenta', },
headerTintColor: 'white', // hamburg color
headerTitleStyle: { fontWeight: 'bold', },
headerTitle: (props) => <ContentTopWideHamburgBar {...props} />
}
}
contentOptions={
{
activeTintColor: 'red',
activeBackgroundColor: 'red',
inactiveTintColor: 'red',
inactiveBackgroundColor: 'red',
}
}
drawerContent={props => <ContentColapsibleSideBarMenu {...props} />} >
{ContentItensNavigationRouteMap()}
</Drawer.Navigator>
);
};
export default function ShellNavigator() {
return (
<NavigationContainer>
<DrawerNavigator />
</NavigationContainer>
);
}
const stl = StyleSheet.create({
boxDescr: {
fontSize: 30,
margin: 10,
padding:10,
backgroundColor: 'lightblue',
color: 'red',
},
boxCtx:{
display: 'flex',
flex: 1,
fontSize: 30,
margin: 0,
backgroundColor: 'yellow',
},
hamburgBar: {
fontSize: 20,
margin: 10,
backgroundColor: 'pink',
},
sideMenuHeader: {
fontSize: 20,
margin: 10,
backgroundColor: 'blueviolet',
},
sideMenuFooter: {
fontSize: 20,
margin: 10,
backgroundColor: 'purple',
},
sideBarMenu: {
flex: 1,
fontSize: 20,
margin: 10,
backgroundColor: 'greenyellow',
},
itemNav: {
fontSize: 40,
padding: 10,
backgroundColor: 'red',
},
});
Here navToLogin is a stand alone function, not in a Functional component. Hooks need to be in a functional component to use. The bellow code should work which navToLogin move the function inside a functional component.
const NestedTabBar = props => {
const navigation = useNavigation();
const navToLogin = () => {
navigation.navigate('Login');
}
return (
<>
<TouchableOpacity
style={stl.itemNav}
onPress={
() => {
//Alert.alert('NavigateToLogin');
navToLogin();
}
} >
<Text>[NavigateToLogin]</Text>
</TouchableOpacity>
</>
);
}
I am struggling a little bit. I have tried to create more components for my React native app but after I did it my ButtonSaving stoped redirecting to Dashboard for some reason. I was trying some ways to pass onRowPress to component but without luck. What do I do incorrectly here please?
Login button is working fine => redirecting to Dashboard
ButtonSaving not working at all => should redirect to Dashboard
AppNavigator.js
import { createStackNavigator } from 'react-navigation-stack'
import { createAppContainer } from 'react-navigation';
import Homepage from './components/Homepage/Homepage';
import Dashboard from './components/Dashboard/Dashboard';
const AppNavigator = createStackNavigator({
Homepage: Homepage,
Dashboard: { screen: Dashboard},
},
{
initialRouteName: 'Homepage',
defaultNavigationOptions: {
headerStyle: {
backgroundColor: 'white',
opacity: 70,
borderBottomColor: 'white',
borderColor: 'white'
},
headerTintColor: 'black',
headerTitleStyle: {
fontWeight: 'bold'
}
}
}
);
const Container = createAppContainer(AppNavigator);
export default Container;
Homepage.js
import React from 'react';
import { StyleSheet, Text, View, Button, Image } from 'react-native';
import {NavigationActions} from 'react-navigation';
// COMPONENTS
import ButtonSaving from './ButtonSaving/ButtonSaving';
class Homepage extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
},
this.handleClick = this.handleClick.bind(this);
this.onRowPress = this.onRowPress.bind(this);
}
handleClick() {
const counterApp = this.state.counter;
this.setState({
counter: counterApp + 1,
dashboard: 'Dashboard'
})
}
onRowPress = ({ navigation }) => {
this.props.navigation.navigate(this.state.dashboard);
}
render() {
return(
<View style={styles.container}>
{/* LOGIN BUTTON */}
<View style={styles.buttonContainer}>
<View style={styles.buttonLogin}>
<Button title="log in"
color="white"
onPress={() => this.props.navigation.navigate('Dashboard')}/>
</View>
</View>
{/* LOGO CONTAINER */}
<View style={styles.logoContainer}>
<Image
style={{height: 147, width: 170}}
source= {require('./REACT_NATIVE/AwesomeProject/logo.png')}
></Image>
</View>
{/* EXPLAINATION OF WALT */}
<Text style={styles.textContainer}>Lorem ipsum lorem upsum></Text>
{/* Needs to be refactored to VIEW */}
<ButtonSaving onPress={() => this.onRowPress}/>
</View>)
}
ButtonSaving.js
import React from 'react';
import { StyleSheet, Text, View, Button, Image, TouchableOpacity } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
class ButtonSaving extends React.Component {
constructor(props) {
super(props);
this.state = {
},
this.onRowPress = this.onRowPress.bind(this);
}
onRowPress = ({ navigation }) => {
this.props.navigation.navigate(this.state.dashboard);
}
render(){
return(
<View style={styleButton.container}>
<LinearGradient
colors={[
'#00b38f',
'#7dcf5a'
]}
style={styleButton.opacityContainer}>
<TouchableOpacity>
<Button
title="Start Saving"
color='white'
onPress={this.onRowPress}/>
</TouchableOpacity>
</LinearGradient>
</View>
)
}
}
const styleButton = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
width: '100%',
height: 50,
justifyContent: 'center',
marginTop: '39%'
},
opacityContainer: {
height: 48,
borderRadius: 25,
backgroundColor: 'darkgreen',
width: '70%',
justifyContent: 'center',
alignItems: 'center'
}
})
export default ButtonSaving;
You miss to put dashboard in your state in ButtonSaving.js
In Homepage.js when your are calling handleClick ?. Dunno how you got that working...
You say in the onRowPress this:
this.props.navigation.navigate(this.state.dashboard);
but I don't see anywhere that you set this.state.dashboard.
Probabbly you missed set it up.
It was simple refactor and this helped!
<ButtonSaving navigation ={this.props.navigation}/>
I will update solution for others later.
There is no point to save "dashboard" in the Homepage state or the ButtonSaving state.
In Homepage.js you don't need to pass onPress to ButtonSaving
...
<ButtonSaving navigation={this.props.navigation}/>
...
Next in ButtonSaving.js
onRowPress = () => {
this.props.navigation.navigate('Dashboard');
}
I'm trying to render a Segment, which will render a Photos or Reviews component, depending on which is selected. The renderSegments function is working and the console.log('Rendered Photos') is working. The issue seems to be how I'm returning the Photos component.
The Photos component should just be rendering the text "Photos" to the screen in the ScrollView of the View.js screen.
Error:
Error: Invariant Violation: Invariant Violation: Objects are not valid
as a React child (found: object with keys {_40, _65, _55, _72}). If
you meant to render a collection of children, use an array instead.
Photos.js
// Imports: Dependencies
import React, { Component } from "react";
import { Dimensions, View, SafeAreaView, StyleSheet, Text } from 'react-native';
// Screen Dimensions
const { height, width } = Dimensions.get('window');
// Component: Photos
export default class Photos extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.photos}>Photos</Text>
</View>
)
}
}
// Styles
const styles = StyleSheet.create({
container: {
width: width,
},
photos: {
fontFamily: 'System',
fontSize: 24,
fontWeight: '700',
color: '#000',
},
});
View.js
// Imports: Dependencies
import React, { Component } from "react";
import { Dimensions, SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Container, Header, Left, Body, Right, Button, Icon, Title, Segment, Content } from 'native-base';
// Imports: Components
import Photos from '../components/Photos';
import Reviews from '../components/Reviews';
// Screen Dimensions
const { height, width } = Dimensions.get('window');
// Screen: View
export default class View extends React.Component {
constructor(props) {
super(props);
this.state = {
segmentSelected: 'Photos',
};
}
// React Navigation: Options
static navigationOptions = {
headerStyle: {
elevation: 0,
shadowOpacity: 0,
borderBottomWidth: 0,
},
};
// Render Segments (Photos/Reviews)
renderSegments = async () => {
try {
console.log('Rendering Segments');
if (this.state.segmentSelected === 'Photos') {
console.log('Rendered Photos');
return (
<Photos />
)
}
if (this.state.segmentSelected === 'Reviews') {
console.log('Rendered Reviews');
return (
<Reviews />
)
}
}
catch (error) {
console.log(error);
}
}
render() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.segmentContainer}>
<Segment style={{ width: width }} style={{ backgroundColor: '#fff'}}>
<Button
first
active={this.state.segmentSelected === 'Photos' ? true : false}
onPress={() => this.setState({ segmentSelected: 'Photos' })}
>
<Text style={{ color: this.state.segmentSelected === 'Photos' ? '#fff' : '#007AFF'}}> Photos </Text>
</Button>
<Button
last
active={this.state.segmentSelected === 'Reviews' ? true : false}
onPress={() => this.setState({ segmentSelected: 'Reviews' })}
>
<Text style={{ color: this.state.segmentSelected === 'Reviews' ? '#fff' : '#007AFF'}}> Reviews </Text>
</Button>
</Segment>
</View>
<ScrollView style={{ backgroundColor: 'green' }}>
<View>
{this.renderSegments()}
</View>
</ScrollView>
</SafeAreaView>
)
}
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
// justifyContent: 'center',
alignItems: 'center',
width: width,
},
segmentContainer: {
marginTop: 7,
marginBottom: 7,
borderColor: '#999999',
width: width,
borderBottomWidth: .2,
},
text: {
fontFamily: 'System',
fontSize: 16,
fontWeight: '400',
color: '#222222',
},
});
Though I am experienced in React I am very new to react-native. I have tried several answers posted in SO for the same issue but none of them helping me fix the issue.
I have App component and the App component calls Account component. The Account component renders input fields but the input fields are not displayed in UI but If I add only Text in App component like <Text>Hello</Text> it is showing in UI but my custom Account component is not showing in the UI. I am not getting an idea what I am doing wrong.
PFB component details
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Account from './components/Form/components/Account';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<Account />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Account.js
import React, { Component } from "react";
import { StyleSheet, Text, View, Button, TextInput, ScrollView } from 'react-native';
export default class Account extends Component {
constructor(props){
super(props);
this.state = {
cardNo: "",
amount: 0
}
}
handleCardNo = no => {
this.setState({
cardNo: no
});
}
handleAmount = amount => {
this.setState({
amount
});
}
handleSend = event => {
const { cardNo, amount } = this.state;
this.setState({
loading: true
});
const regex = /^[0-9]{1,10}$/;
if(cardNo == null){
}else if (!regex.test(cardNo)){
//card number must be a number
}else if(!regex.test(amount)){
//amount must be a number
}else{
// const obj = {};
// obj.cardNo = cardNo;
// obj.amount = amount;
// const url = "http://localhost:8080/apple"
// axios.post(url, obj)
// .then(response => return response.json())
// .then(data => this.setState({
// success: data,
// error: "",
// loading: false
// }))
// .catch(err => {
// this.setState({
// error: err,
// success: "",
// loading: false
// })
// })
//values are valid
}
}
render(){
const { cardNo, amount } = this.state;
return(
<View>
<TextInput label='Card Number' style={{height: 40, flex:1, borderColor: '#333', borderWidth: 1}} value={cardNo} onChangeText={this.handleCardNo} />
<TextInput label='Amount' style={{height: 40, flex:1, borderColor: '#333', borderWidth: 1}} value={amount} onChangeText={this.handleAmount} />
<Button title='Send' onPress={this.handleSend}/>
</View>
)
}
}
Your code has some styling issues. Try changing you render function with this
render() {
const { cardNo, amount } = this.state
return (
<View style={{ alignSelf: 'stretch' }}>
<TextInput
placeholder="Card Number"
style={{ height: 40, borderColor: '#333', borderWidth: 1 }}
value={cardNo}
onChangeText={this.handleCardNo}
/>
<TextInput
placeholder="Amount"
style={{ height: 40, borderColor: '#333', borderWidth: 1 }}
value={amount}
onChangeText={this.handleAmount}
/>
<Button title="Send" onPress={this.handleSend} />
</View>
)
}
I'm trying to create a React Native app with some basic routing.
This is my code so far:
App.js:
import React from 'react'
import { StackNavigator } from 'react-navigation'
import MainScreen from './classes/MainScreen'
const AppNavigator = StackNavigator(
{
Index: {
screen: MainScreen,
},
},
{
initialRouteName: 'Index',
headerMode: 'none'
}
);
export default () => <AppNavigator />
MainScreen.js:
import React, { Component } from 'react'
import { StyleSheet, Text, View, Button, TouchableOpacity, Image } from 'react-native'
import HomeButton from './HomeButton'
export default class MainScreen extends Component {
static navigatorOptions = {
title: 'MyApp'
}
constructor(props) {
super(props)
}
render() {
return (
<View style={styles.container}>
<Image source={require('../img/logo.png')} style={{marginBottom: 20}} />
<HomeButton text='Try me out' classType='first' />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
HomeButton.js:
import React, { Component } from 'react'
import { StyleSheet, Text, View, Button, TouchableOpacity } from 'react-native'
import { StackNavigator } from 'react-navigation'
export default class HomeButton extends Component {
constructor(props) {
super(props)
}
render() {
return (
<TouchableOpacity
onPress={() => navigate('Home')}
style={[baseStyle.buttons, styles[this.props.classType].style]}
>
<Text style={baseStyle.buttonsText}>{this.props.text.toUpperCase()}</Text>
</TouchableOpacity>
)
}
}
var Dimensions = require('Dimensions')
var windowWidth = Dimensions.get('window').width;
const baseStyle = StyleSheet.create({
buttons: {
backgroundColor: '#ccc',
borderRadius: 2,
width: windowWidth * 0.8,
height: 50,
shadowOffset: {width: 0, height: 2 },
shadowOpacity: 0.26,
shadowRadius: 5,
shadowColor: '#000000',
marginTop: 5,
marginBottom: 5
},
buttonsText: {
fontSize: 20,
lineHeight: 50,
textAlign: 'center',
color: '#fff'
}
})
const styles = {
first: StyleSheet.create({
style: { backgroundColor: '#4caf50' }
})
}
Everything works fine, but when pressing the button I get
Can't find variable: navigate
I've read that I have to declare it like this:
const { navigate } = this.props.navigation
So I edited HomeButton.js and added that line at the beginning of the render function:
render() {
const { navigate } = this.props.navigation
return (
<TouchableOpacity
onPress={() => navigate('Home')}
style={[baseStyle.buttons, styles[this.props.classType].style]}
>
<Text style={baseStyle.buttonsText}>{this.props.text.toUpperCase()}</Text>
</TouchableOpacity>
)
}
Now I get:
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate')
It seems that the navigation object is not coming into the properties, but I don't understand where should I get it from.
What am I missing here?
React-navigation pass navigation prop to the screen components defined in the stack navigator.
So in your case, MainScreen can access this.props.navigation but HomeButton can't.
It should work if you pass navigation prop from MainScreen to HomeButton :
<HomeButton text='Try me out' classType='first' navigation={this.props.navigation}/>
Edit: You have to define the Homescreen in your stack navigator in order to navigate to it, your onPress={() => navigate('Home')} won't work until then.