I have the following problem.
I am creating a React Native appliation and I want to pass a click handler to a child component. But when I try to call the click handler in the child component I keep getting a _this.props.onItemPress is not a function Exception.
When I try to pass the function with a .bind(this) inside the parent, it says the function is undefined.
Here's my code:
Parent
constructor(props) {
super(props)
this.handleTodoPress = this.handleTodoPress.bind(this)
}
...
handleTodoPress(event) {
console.warn('Press handled')
}
renderItem ({section, item}) {
return <TodoItem onItemPress={this.handleTodoPress} title={item.title} description={item.description} completed={item.completed} />
}
...
render () {
return (
<View style={styles.container}>
<SectionList
renderSectionHeader={this.renderSectionHeader}
sections={this.state.data}
contentContainerStyle={styles.listContent}
data={this.state.dataObjects}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
initialNumToRender={this.oneScreensWorth}
ListHeaderComponent={this.renderHeader}
SectionSeparatorComponent={this.renderSectionSeparator}
ListEmptyComponent={this.renderEmpty}
ItemSeparatorComponent={this.renderSeparator}
renderSectionFooter={this.renderSectionFooter}
/>
</View>
)
}
}
Child
import React, { Component } from 'react';
import { TouchableOpacity, View, Text, } from 'react-native';
import styles from './Styles/TodoItemStyles'
export default class TodoItem extends Component {
constructor(props) {
super(props)
this.state = {completed: 'Todo'}
this.setCompletedState = this.setCompletedState.bind(this)
}
itemPressed = (e) => {
console.warn(this.props);
this.props.onItemPress(e)
}
setCompletedState() {
if (this.props.completed == true) {
this.setState({completed: 'Completed'})
}
}
componentWillMount() {
this.setCompletedState()
}
render() {
return (
<TouchableOpacity onPress={this.itemPressed}>
<View style={styles.todoContainer}>
<Text style={styles.itemTitle}>{this.props.title}</Text>
<Text style={styles.itemDescription}>{this.props.description}</Text>
<Text style={[styles.itemLabel, this.props.completed ? styles.itemLabelCompleted : styles.itemLabelNotCompleted]}>{this.state.completed}</Text>
</View>
</TouchableOpacity>
);
}
}
TRY:
export default class TodoItem extends Component {
constructor(props) {
super(props)
this.state = {completed: 'Todo'}
this.setCompletedState = this.setCompletedState.bind(this)
}
itemPressed(e){
console.warn(this.props);
this.props.onItemPress(e)
}
setCompletedState() {
if (this.props.completed == true) {
this.setState({completed: 'Completed'})
}
}
componentWillMount() {
this.setCompletedState()
}
render() {
return (
<TouchableOpacity onPress={this.itemPressed}>
<View style={styles.todoContainer}>
<Text style={styles.itemTitle}>{this.props.title}</Text>
<Text style={styles.itemDescription}>{this.props.description}</Text>
<Text style={[styles.itemLabel, this.props.completed ? styles.itemLabelCompleted : styles.itemLabelNotCompleted]}>{this.state.completed}</Text>
</View>
</TouchableOpacity>
);
}
}
when you use
itemPressed = (e) => {
console.warn(this.props);
this.props.onItemPress(e)
}
that notations binds the current context inside the function
I think your problem is that how you are using arrow function for itemPressed. Try rewriting it and binding this for itemPressed the same as you did for setCompletedState.
Related
I have the following setup:
import {getNewImage} from '...'
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
imageURL: 'www.test.com/new.jpg',
}
}
update = () => {
this.setState({
imageURL: 'www.test.com/updated.jpg',
})
}
render() {
return (
<View>
<Image
source={{ uri: this.state.imageURL }}
/>
</View>
);
}
}
import Class1 from '...'
export default class SecondClass extends Component {
render() {
return (
<TouchableOpacity onPress={() => new FirstClass().update()}>
<Class1></Class1>
</TouchableOpacity>
);
}
}
The problem is: it doesn't update the imageURL. I tried multiple things. Logging this inside of update gave back the right object. But trying to log the this.setState() gives an undefinded back.
I suppose by Class1 you mean FirstClass.
you should use the reference of the component using ref, not creating new instance of FirstClass class
checkout this code
export default class SecondClass extends Component {
private firstClass = null;
render() {
return (
<TouchableOpacity onPress={() => this.firstClass.update()}>
<Class1 ref={ref => this.firstClass = ref} />
</TouchableOpacity>
);
}
}
You can try to update state in Class1 similar to what do you want by the react reference.
But on my opinion this is non a good case and predictable behavior to change child component state from parent.
As another options you can add additional state to the SecondClass and pass it via props to child component. And inside Class1 in getDerivedStateFromProps based on that prop change state.
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
isNeedToUdpate: false,
imageURL: "www.test.com/new.jpg"
};
}
static getDerivedStateFromProps(props, state) {
if (props.isNeedToUdpate !== state.isNeedToUdpate) {
return {
isNeedToUdpate: props.isNeedToUdpate,
imageURL: "www.test.com/updated.jpg"
};
}
return null;
}
render() {
return (
<View>
<Image source={{ uri: this.state.imageURL }} />
</View>
);
}
}
export default class SecondClass extends Component {
constructor() {
super();
this.state = {
isNeedToUpdateClass1: false
};
}
render() {
return (
<TouchableOpacity
onPress={() => {
this.setState({ isNeedToUpdateClass1: true });
}}
>
<Class1 isNeedToUpdate={this.state.isNeedToUpdateClass1}></Class1>
</TouchableOpacity>
);
}
}
I'm using React native navigation. (Stack Navigation).
But I can't call function in navigationOptions. Not working.
import React, { Component } from 'react';
import { StyleSheet, View, Text, TouchableHighlight, AsyncStorage, Alert } from 'react-native';
import { Button } from 'react-native-elements';
import Icon from 'react-native-vector-icons/FontAwesome';
import HandleBack from '../../HandleBack';
export default class Dashboard extends Component {
constructor(props) {
super(props);
}
static navigationOptions = ({ navigation }) => {
return {
title: 'Dasboard',
headerLeft: null,
headerRight: (
<TouchableHighlight underlayColor='transparent' onPress={this.login.bind(this)} style={{marginRight: 10}}>
<Icon
name="power-off"
size={25}
color="white"
/>
</TouchableHighlight>
)
};
};
login() {
alert('Button clicked!');
}
onBack = () => {
this.props.navigation.navigate('Screen3');
};
render() {
return(
<HandleBack onBack={ this.onBack }>
<View>
<Text> This is screen 2 </Text>
<TouchableHighlight onPress={() => this.props.navigation.navigate('Screen3')}>
<Text> Go to Screen 3 </Text>
</TouchableHighlight>
</View>
</HandleBack>
)
}
}
When I'm using onPress={this.login.bind(this)} get error
"TypeError: TypeError: undefined is not an object (evaluatinh '_class.login.bind')"
When I'm using onPress={this.login} no reaction.
When I'm using onPress={this.login()} get error
TypeError: TypeError: _class.login is not a function.
But
I'm using onPress={() => alert('test')} is working.
you can achieve it using setParams or getParams for react-navigation.
export default class Dashboard extends Component {
static navigationOptions = ({ navigation }) => {
return {
title: 'Dasboard',
headerLeft: null,
headerRight: (
<TouchableHighlight underlayColor='transparent'
onPress={navigation.getParam('login')} //call that function in onPress using getParam which we already set in componentDidMount
style={{marginRight: 10}}>
<Icon
name="power-off"
size={25}
color="white"
/>
</TouchableHighlight>
)
};
};
login() {
alert('login click')
}
onBack = () => {
this.props.navigation.navigate('Screen3');
};
componentDidMount() {
this.props.navigation.setParams({ login: this.login }); //initialize your function
}
render() {
return(
.....
)
}
}
in Parent component, I'm trying to focus the TextInput of second Child component when submit button is pressed in the TextInput of the first Child component but this error comes up: error message
Child.js
import React from "react";
import { View, TextInput} from "react-native";
export default class Child extends React.Component {
focus = ref => {
ref.input.focus();
};
render() {
return (
<View>
<TextInput
placeholder='enter text'
onSubmitEditing={() => {
this.focus(this.props.destinationRef);
}}
ref={input => {
this.input = input;
}}
/>
</View>
);
}
}
Parent.js
import React from "react";
import Child from "./Child";
import { View, StyleSheet } from "react-native";
export default class Parent extends React.Component {
// componentDidMount() {
// setTimeout(() => {
// this.two.input.focus();
// }, 3000);
// }
render() {
return (
<View style={styles.container}>
<Child
destinationRef={() => {
if (!this.two) return this.two;
}}
ref={input => {
this.one = input;
}}
/>
<Child ref={input => (this.two = input)} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});
Note that when I uncomment the componendDidMount, second TextInput successfully focuses three seconds after mounting.
I think the problem in render update. In first render time destinationRef is undefined, parent state not updated or force updated so props not updated too.
I try little bit optimize your code. You can use arrow function to bind this:
import React from "react";
import Child from "./Child";
import { View, StyleSheet } from "react-native";
export default class Parent extends React.Component {
_makeFocus(){
this.two.input.focus();
}
handleOnSubmit(){
this._makeFocus();
}
render() {
return (
<View style={styles.container}>
<Child
onSubmit={this.handleOnSubmit.bind(this)}
ref={input => {
this.one = input;
}}
/>
<Child ref={input => (this.two = input)} />
</View>
);
}
}
I would change the logic a little bit since I think the ref is giving you problems.
Parent:
<View style={styles.container}>
<Child next={this.two} ref={comp => this.one = comp}/>
<Child next={this.one} ref={comp => this.two = comp}/>
</View>
Child:
<TextInput
placeholder='enter text'
onSubmitEditing={() => {
if (this.props.next)
this.props.next.focus();
}}
/>
EDIT:
Your approach was correct I believe, I would just update the parent into this:
export default class Parent extends React.Component {
constructor(props){
super(props);
this.references = {};
}
onFocus = (ref) => {
this.references[ref].focus();
}
render() {
return (
<View style={styles.container}>
<Child
destinationRef={'two'}
onFocus={this.onFocus}
ref={input => this.references['one'] = input}
/>
<Child ref={input => this.references['two'] = input} />
</View>
);
}
}
and your child to:
export default class Child extends React.Component {
render() {
return (
<View>
<TextInput
placeholder='enter text'
onSubmitEditing={() => {
this.props.onFocus(this.props.destinationRef);
}}
/>
</View>
);
}
}
iam new in react native and start my first project , i have a login screen and when i press into TouchableHighlight i need to open another screen , but the problem is i failed to make the function that move from login to second screen , this is my code
Login.js
import React, { Component } from 'react';
import { AppRegistry, Text,SecureView ,Button,Image,TextInput,StyleSheet,View,NavigatorIOS,TouchableHighlight} from 'react-native';
require('./HygexListView.js');
class LoginView extends Component {
constructor(props){
super(props);
}
onPositive(){
this.props.navigator.pop()
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>
HYGEX
</Text>
<View>
<TextInput
placeholder="Username"
style={styles.formInput}
/>
<TextInput
placeholder="Password"
secureTextEntry={true}
style={styles.formInput1}
/>
<TouchableHighlight style={styles.button}
onPress={ () => this.onPositive() }>
<Text style={styles.buttonText}>Login</Text>
</TouchableHighlight>
</View>
</View>
);
}
onPress() {
this.props.navigator.push({
title: "HygexListView",
component: HygexListView,
});
}
}
and when press into TouchableHighlight i need to open this screen
HygexListView.js
'use strict';
import React, { Component } from 'react';
import { AppRegistry, ListView, Text, View } from 'react-native';
class HygexListView extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([
'John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin'
])
};
}
render() {
return (
<View style={{flex: 1, paddingTop: 22}}>
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
</View>
);
}
}
module.exports = HygexListView;
From what i see there, i think you forgot to use/setup the Navigator component. Try to organize it this way:
Your components
class HygexListView extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([
'John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin'
])
};
}
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'red', alignItems: 'center'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
renderScene(route, navigator) {
return (
<View style={{flex: 1, paddingTop: 22}}>
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
</View>
);
}
}
module.exports = HygexListView;
index.ios.js
class yourApp extends Component {
render() {
return (
<Navigator
initialRoute={{id: 'Login'}}
renderScene={this.renderScene.bind(this)}
configureScene={(route) => {
if (route.sceneConfig) {
return route.sceneConfig;
}
return Navigator.SceneConfigs.PushFromRight;
}} />
);
}
renderScene(route, navigator) {
switch (route.id) {
case 'HygexListView':
return (
<HygexListView navigator={navigator} />
);
case 'Login':
return (
<Login navigator={navigator} />
);
default:
return null;
}
}
}
basically what you do, instead of rendering your component, you render your navigator that using renderScene() renders your component/view.
the approach to use the index file as an organizer of the views, is just a preference of mine. but you will see there, that when an "id" is passed to the navigator, the scene will be rendered using the component that matches the id on the switch case.
I have a parent component and two child components. One of the child components is a form with an add button. I am able to capture the text when add button is pressed, but stuck when trying to pass that value to the parent component, update the array in parent component and re-render the view.
Parent Component:
var Tasks = ['Competitor Study','Content Plan','Write','Promote','Consumer Research']
//Build React Component
class MyTaskList extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.titleStyle}>
My Tasks
{"\n"}
({Moment().format("MMM Do YY")})
{"\n"}
</Text>
<AddTask />
{this.workitems()}
</View>
);
}
workitems() {
return (Tasks.map(function(workitem,i) {
return <ListItem key={i} workitem={workitem} />
}
)
);
}
}
Child component with the form
class AddTask extends Component {
constructor(props) {
super(props);
this.state = {
enterTask: 'Enter task'
};
}
onTaskTextChanged(event) {
this.setState({ enterTask: event.nativeEvent.text });
}
onAddPress() {
var newtask = this.state.enterTask;
console.log('new task - '+newtask);
}
render() {
return (
<View style={styles.addTask}>
<TextInput
style={styles.taskInput}
value={this.state.enterTask}
onChange={this.onTaskTextChanged.bind(this)}
placeholder='Enter task'/>
<TouchableHighlight style={styles.button}
onPress={this.onAddPress.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
);
}
}
I would instead use state for this as using forceUpdate() is not recommended
From React docs:
Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.
(Based off of #1ven answer)
Parent Component:
//Build React Component
class MyTaskList extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [
'Competitor Study',
'Content Plan',
'Write','Promote',
'Consumer Research'
]
}
}
handleAddTask(task) {
var newTasks = Object.assign([], this.state.tasks);
newTasks.push(task);
this.setState({tasks: newTasks});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.titleStyle}>
My Tasks
{"\n"}
({Moment().format("MMM Do YY")})
{"\n"}
</Text>
<AddTask onAddTask={this.handleAddTask.bind(this)} />
{this.workitems()}
</View>
);
}
workitems() {
return this.state.tasks.map(function(workitem,i) {
return <ListItem key={i} workitem={workitem} />
});
}
}
Child component with the form
class AddTask extends Component {
constructor(props) {
super(props);
this.state = {
enterTask: 'Enter task'
};
}
onTaskTextChanged(event) {
this.setState({ enterTask: event.nativeEvent.text });
}
onAddPress() {
var newtask = this.state.enterTask;
console.log('new task - '+newtask);
// Providing `newtask` variable to callback.
this.props.onAddTask(newtask);
}
render() {
return (
<View style={styles.addTask}>
<TextInput
style={styles.taskInput}
value={this.state.enterTask}
onChange={this.onTaskTextChanged.bind(this)}
placeholder='Enter task'/>
<TouchableHighlight style={styles.button}
onPress={this.onAddPress.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
);
}
}
You should provide handleAddTask callback from parent to child component:
var Tasks = ['Competitor Study','Content Plan','Write','Promote','Consumer Research']
//Build React Component
class MyTaskList extends Component {
handleAddTask(task) {
// When task will be added, push it to array
Tasks.push(task);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.titleStyle}>
My Tasks
{"\n"}
({Moment().format("MMM Do YY")})
{"\n"}
</Text>
<AddTask onAddTask={this.handleAddTask} />
{this.workitems()}
</View>
);
}
workitems() {
return (
Tasks.map(function(workitem,i) {
return <ListItem key={i} workitem={workitem} />
})
);
}
}
Next, you should pass task from child component to this callback:
class AddTask extends Component {
constructor(props) {
super(props);
this.state = {
enterTask: 'Enter task'
};
}
onTaskTextChanged(event) {
this.setState({ enterTask: event.nativeEvent.text });
}
onAddPress() {
var newtask = this.state.enterTask;
console.log('new task - '+newtask);
// Providing `newtask` variable to callback.
this.props.onAddTask(newtask);
}
render() {
return (
<View style={styles.addTask}>
<TextInput
style={styles.taskInput}
value={this.state.enterTask}
onChange={this.onTaskTextChanged.bind(this)}
placeholder='Enter task'/>
<TouchableHighlight style={styles.button}
onPress={this.onAddPress.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
);
}
}
That's it. Hope, it helps!