How to add stateless component to a touchable component - reactjs

I am trying to add a stateless component to my button.
const button = ({onButtonPress, buttonText}) => {
return (
<TouchableHighlight
onPress={() => onButtonPress()}>
<ButtonContent text={buttonText}/>
</TouchableHighlight>
)
};
and get this error:
Warning: Stateless function components cannot be given refs (See ref "childRef"
in StatelessComponent created by TouchableHighlight).
Attempts to access this ref will fail.
I have read up on the issue but I am still new to javascript and RN and have not found a solution. Any help would be appreciated.
full code:
GlossaryButtonContent:
import React from 'react';
import {
View,
Text,
Image,
StyleSheet
} from 'react-native';
import Colours from '../constants/Colours';
import {
arrowForwardDark,
starDarkFill
} from '../assets/icons';
type Props = {
text: string,
showFavButton?: boolean
}
export default ({text, showFavButton} : Props) => {
return (
<View style={styles.container}>
{showFavButton &&
<Image
style={styles.star}
source={starDarkFill}/>}
<Text style={[styles.text, showFavButton && styles.favButton]}>
{showFavButton ? 'Favourites' : text}
</Text>
<Image
style={styles.image}
source={arrowForwardDark}
opacity={showFavButton ? .5 : 1}/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
favButton: {
marginLeft: 10,
color: Colours.darkTextHalf
},
text: {
flex: 1,
paddingTop: 5,
marginLeft: 20,
fontFamily: 'Bariol-Bold',
fontSize: 24,
color: Colours.darkText
},
image: {
marginRight: 20
},
star: {
marginLeft: 10
}
});
GlossaryButton:
import React from 'react';
import {
TouchableHighlight,
StyleSheet
} from 'react-native';
import Colours from '../constants/Colours';
import ShadowedBox from './ShadowedBox';
import GlossaryButtonContent from './GlossaryButtonContent';
type Props = {
buttonText: string,
onButtonPress: Function,
rowID: number,
sectionID?: string,
showFavButton?: boolean
}
export default ({buttonText, onButtonPress, rowID, sectionID, showFavButton} : Props) => {
return (
<ShadowedBox
style={styles.container}
backColor={showFavButton && Colours.yellow}>
<TouchableHighlight
style={styles.button}
underlayColor={Colours.green}
onPress={() => onButtonPress(rowID, sectionID)}>
<GlossaryButtonContent
text={buttonText}
showFavButton={showFavButton}/>
</TouchableHighlight>
</ShadowedBox>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
height: 60,
marginBottom: 10,
borderRadius: 5
},
button: {
flex: 1,
borderRadius: 5
}
});

Basically, Stateless components cannot have refs.
So, having a stateless component in the middle of the render tree will create a void in the ref chain, meaning you cannot access lower-down components.
So, the problem comes from trying to do this:
let Stateless = (props) => (
<div />
);
let Wrapper = React.createClass({
render() {
return <Stateless ref="stateeee" />
}
});
TouchableHighlight needs to give a ref to its child. And this triggers that warning.
Answer:
You can't actually make a stateless component a child of TouchableHighlight
Solution:
Use createClass or class to create the child of TouchableHighlight, that is GlossaryButtonContent.
See this github issue for more info and this one

Related

navigation.navigate is not a function. (In 'navigation.navigate("HomeScreen")', 'navigation.navigate' is undefined)

I m very new in react native and I m getting this error. Please help!
navigation.navigate is not a function. (In 'navigation.navigate("HomeScreen")', 'navigation.navigate' is undefined)
import { View, Text,Button,StyleSheet, TouchableOpacity } from 'react-native'
import React, {useState} from 'react'
import { NavigationContainer,CommonActions, useNavigation } from '#react-navigation/native';
const GetOtpButton = (navigation) => {
return (
<View >
<TouchableOpacity style = {styles.button} onPress={() => navigation.navigate("HomeScreen") } >
<Text style = {styles.text}>Log in</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: "white",
width: "100%",
height: 50,
borderColor: "#E13C72",
backgroundColor: "#E13C72",
borderWidth: 0.1,
borderRadius: 80,
// marginBottom: 40,
// marginVertical: 5,
// marginTop: 10,
},
text: {
justifyContent: 'center',
textAlign: 'center',
color: "white",
fontWeight: 'bold'
}
});
export default GetOtpButton
---------------------App.js------------------
import react from "react";
import { StatusBar } from "expo-status-bar";
import { SafeAreaView, StyleSheet, Text, View, Dimensions } from "react-native";
import SigninScreen from "./src/SigninScreen/SigninScreen";
import HomeScreen from "./src/HomeScreen/HomeScreen";
import { NavigationContainer, StackActions } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="SigninScreen" options={{headerShown: false}}>
<Stack.Screen name = 'SigninScreen' component={SigninScreen} options={{headerShown: false}}/>
<Stack.Screen name = 'HomeScreen' component={HomeScreen} options={{headerShown: false}}/>
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
root: {},
container: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#FFFFFF",
},
});
The component GetOptButton is not defined as a screen in the navigator, thus the navigation object will not be passed to it automatically by the navigation framework. Thus, you have multiple choices here.
Define it as a screen inside the navigator
<Stack.Screen name = 'GetOpt' component={GetOptButton} />
The framework will now pass the navigation object to the GetOptButton component. You can access it as follows.
const GetOtpButton = ({navigation}) => { ... }
Now, since GetOptButton seems to be a component rather than a screen to which you will navigate, it might not make much sense to define it inside the navigator.
Pass the navigation object from a screen which is defined in the navigator that uses the GetOptButton component
// this works, since `Homescreen` is defined as a screen in your navigator
const HomeScreen = ({navigation}) => {
return (
<GetOptButton navigation={navigation} />
)
}
Destructure the navigation object inside GetOptButton as shown above.
Use the useNavigation hook
For some components it might not make sense to pass the navigation object from its parent, e.g. if the component is deeply nested. For this cases you can use the useNavigation hook.
const GetOptButton = () => {
const navigation = useNavigation()
}
You are not showing how GetOtpButton is used but guessing that navigation is passed as prop to GetOtpButton you need to get to navigation like this
const GetOtpButton = ({navigation}) => {
return (
<View >
<TouchableOpacity style = {styles.button} onPress={() => navigation.navigate("HomeScreen") } >
<Text style = {styles.text}>Log in</Text>
</TouchableOpacity>
</View>
)
}
See that the difference is that navigation is surrounded by curly brackets, so later you get the parameter navigation inside the props

Pass Function to Functional Component in React Native

I have an ItemForm component in a react-native expo app I would like to use for both Creates and Updates. It is a functional component which I am trying to pass a handler from a parent component to either create a new item or update an existing item. I am having trouble understanding how to pass the onPress handler down from the parent Class Component down to the functional child form Component.
Parent Component:
import React, { Component } from "react";
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import ItemForm from './components/ItemForm';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class App extends Component {
handleSubmit = ({ name }) => {
alert(name);
// todo: get form data and create or update database
}
render() {
return (
<View style={styles.container}>
<Card>
<ItemForm onSubmit={() => this.handleSubmit}/>
</Card>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
ItemForm.js
import React, { useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { Button, Card, Icon, Input } from "react-native-elements";
export default function ItemForm({ onSubmit }) {
const [name, setName] = useState("");
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>Add an Item</Text>
</View>
<Card
containerStyle={{
height: "80%",
margin: 0,
elevation: 1,
borderWidth: 0,
shadowOpacity: 0,
shadowRadius: 0,
}}
>
<Input
label="Item Name"
labelStyle={styles.itemText}
placeholder="Name Here"
onChangeText={(name) => setName(name)}
/>
<Button
icon={
<Icon
name="check-circle"
size={20}
style={{ paddingRight: 10 }}
onPress={onSubmit({name: name})}
/>
}
title="Save Item"
/>
</Card>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
},
header: {
alignItems: "center",
paddingTop: 40,
},
headerText: {
fontSize: 32,
},
itemText: {
fontSize: 25,
}
});
This is currently throwing:
onSubmit is not a function. (In 'onSubmit({name: name})', 'onSubmit' is undefined)
I've tried various different syntaxes of passing or calling the function by reading the docs here but I run into a lot of errors like Please attach a method to this component and I can't seem to get past this?
onPress should be a function that calls onSubmit, not the return value of calling onSubmit (onPress={onSubmit({name: name})}).
onPress needs to be on the Button.
You need to pass down this.handleSubmit from the parent component, and not a function that returns this.handleSubmit (onSubmit={() => this.handleSubmit}):
So in App:
<ItemForm onSubmit={this.handleSubmit}/>
In ItemForm:
<Button
onPress={() => onSubmit({name: name})}
icon={
<Icon
name="check-circle"
size={20}
style={{ paddingRight: 10 }}
/>
}
title="Save Item"
/>
demo

How can I change the color of an icon without re-render (TextField loosing focus)

I am using the package called react-native-phone-input, and when the phone number is changed it runs the function this.updateInfo() which updates the state. In my code, the icon color is dependent on the state and changes based on whether the phone number is valid. This works, however, when the state is changed, the screen re-renders and the keyboard is dismissed since the text field loses focus. Is there another way I can change the icons color? Or is there a way that I can keep the focus on the text field?
This is what I am referring to:
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, TextInput, Keyboard} from 'react-native';
import { Icon } from 'react-native-elements';
import KeyboardAccessory from 'react-native-sticky-keyboard-accessory';
import PhoneInput from 'react-native-phone-input';
export default class PhoneLogin extends Component {
constructor() {
super();
this.state = {
valid: "",
type: "",
value: "",
iconColor: "#D3D3D3",
};
this.updateInfo = this.updateInfo.bind(this);
}
updateInfo() {
this.setState({
valid: this.phone.isValidNumber(),
type: this.phone.getNumberType(),
value: this.phone.getValue().replace(/[- )(]/g,''),
});
}
render() {
return (
<View style={styles.container}>
<PhoneInput
ref={ref => {
this.phone = ref;
}}
style={{height: 50, borderColor:'#44c0b9', borderBottomWidth:2}}
onChangePhoneNumber={ (phone) => {this.updateInfo()} }
/>
<KeyboardAccessory backgroundColor="#fff">
<View style={{ alignItems: 'flex-end', padding:10 }}>
<Icon
raised
reverse
color={(this.state.valid) ? "#44c0b9" : "#D3D3D3"}
name='arrow-right'
type='font-awesome'
onPress={ Keyboard.dismiss()}
/>
</View>
</KeyboardAccessory>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
padding: 20,
paddingTop: 60
},
info: {
// width: 200,
borderRadius: 5,
backgroundColor: "#f0f0f0",
padding: 10,
marginTop: 20
},
button: {
marginTop: 20,
padding: 10
}
});
In your onPress of your Icon you need to send an arrow function.
The Icon should be like this
<Icon
raised
reverse
color={(this.state.valid) ? "#44c0b9" : "#D3D3D3"
name='arrow-right'
type='font-awesome'
onPress={() => Keyboard.dismiss()}
/>
The problem was the Keyboard.dismiss() was immediately running every time the component re-rendered thus dismissing your keyboard.
Hope this helps!

React Native custom event

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

Show DrawerLayoutAndroid via ToolbarAndroid using Navigator (React Native)

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.

Resources