Attaching onPress handler to a Text that was constructed outside the component - reactjs

I have my component class, let's call it MyComponent. It has a complicated execution logic, but here's how it looks like with unnecessary code snippets omitted:
class MyComponent extends Component {
justWarn(input) {
console.warn(input);
// do something with `this` as well
}
render() {
return (
<View>
{this.props.myText}
</View>
);
}
}
const mapStateToProps = () => {
// Call the getText function here
return {
myText: getText()
};
}
export default connect(mapStateToProps)(MyComponent);
Now the function getText is defined in some other file, and looks like this:
function getText() {
// Let's assume it returns hard-coded value for the sake of brevity
return (
<Text>
<Text>I am the first part</Text>
<Text
onPress={function(){
// I want to call the function that is defined inside MyComponent
// since this Text will eventually be a part of MyComponent
this.justWarn('Warn me in the yellow box')
}}
>I am the clickable part
</Text>
</Text>
);
}
When I try to call the justWarn method defined inside the MyComponent from the Text as defined above, I get an error undefined is not a function, evaluating this.justWarn.
My question is, I am not using the arrow function, and as such this is not bound on when I declare the onPress handler. It is actually called when Text is a part of MyComponent, so shouldn't this correspond to MyComponent and code above should work fine?
What am I doing wrong here? Is there any way to achieve this by keeping the getText and MyComponent split in two files?
Thanks.

The problem is getText does not have this context so you need to bind it in someway that it gets the context.
One way to achieve this is by changing MyComponent like this-
class MyComponent extends Component {
justWarn(input) {
console.warn(input);
alert(input)
// do something with `this` as well
}
render() {
const myText = this.props.myText.bind(this)
return (
<View>
<Text>something should render</Text>
{myText()}
</View>
);
}
}
const mapStateToProps = () => {
// Call the getText function here
return {
myText: getText
};
}
export default connect(mapStateToProps)(MyComponent);
...
and then using arrow function in onPress event handler like this -
function getText() {
// Let's assume it returns hard-coded value for the sake of brevity
return (
<Text>
<Text>I am the first part</Text>
<Text
onPress={() => {this.justWarn('Warn me in the yellow box')}}
>I am the clickable part
</Text>
</Text>
);
}
...
Hope it helps.

Related

React Native setState not re-rendering

Expected behaviour of this component is like this: I press it, selectedOpacity() function is called, state is updated so it now renders with opacity=1.
But for some reason, after calling this.setState, it is not being re-rendered. I have to click this component again to make it re-render and apply changes of opacity from state.
export default class Category extends Component {
state = {
opacity: 0.5
}
selectedOpacity() {
// some stuff
this.setState({opacity: 1})
}
render() {
return(
<TouchableOpacity style={[styles.container, {opacity: this.state.opacity}]} onPress={() => {
this.selectedOpacity();
}}>
</TouchableOpacity>
)
}
I think what you are missing is binding of selectedOpacity(), else this would be undefined in it AFAIK.
Also better move the assignment of state to a constructor().
constructor(props) {
super(props);
this.state = {};
this.selectedOpacity = this.selectedOpacity.bind(this);
}
Also change to the following because creating an arrow function inside render affects performance.
onPress={this.selectedOpacity}
Change selectedOpacity to arrow function:
selectedOpacity = () => {
this.setState({opacity: 1})
}
Then:
onPress={this.selectedOpacity}
Edit: The react documentation says its experimental and the syntax is called public class field syntax.
Try change onpress to
onPress={() => this.selectedOpacity()}

Pass parameters to prop function without using an arrow function

I've heard that passing an arrow function as a prop is not ideal because it creates a new function every time which will lead to performance issues. However, I'm not entirely sure how to completely move away from them, as can be seen by the example below:
class Home extends Component {
onCardPress = (message) =>{
alert(message)
}
render(){
return(
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={()=>{onCardPress(message)}}
/>
)
}
}
I have tried changing onPress in Card to be onPress={onCardPress(message)}, but I know this doesn't work because I am invoking the function rather than passing a function object to the onPress of TouchableOpacity. What is the 'proper' way or best practice to remove the arrow function in TouchableOpacity while still being able to pass the message parameter from the parent component Home?
You could do:
class Card extends Component {
pressHandler = () => this.props.onCardPress(this.props.message);
render() {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={this.pressHandler.bind(this)}
/>
);
} }
If you want to avoid arrow function, you have to use bind(). Arrow functions will automatically bind "this".
class Home extends Component {
constructor(props) {
super(props);
this.onCardPress = this.onCardPress.bind(this);
}
onCardPress (message) {
alert(message)
}
render(){
return(
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={onCardPress(message)}
/>
)
}
}
As I understand it, the issue lies with calling bind inside of render, or returning the handler from yet another lambda, as this will create a new function each time. The conventional way to get around this problem is to bind your handler functions elsewhere -- like in the constructor. In your case, that could look like this:
constructor(props) {
....
this.onCardPress = this.onCardPress.bind(this);
}
...
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
Given you alternative option as arrow function already answered in above post.
class Card extends Component {
onClick = () => {
const { onCardPress, message } = this.props;
onCardPress(message);
}
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onClick}
/>
)
}
}
You don't need to pass the message prop because you can access it anywhere in the component.
Just supply a function in the onPress prop. And in that function, just access the message prop of the component.
class Home extends Component {
onCardPress = (message) => {
alert(message)
}
render() {
return (
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
onClick = () => {
const { message, onCardPress } = this.props;
onCardPress(message);
};
render() {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onClick}
/>
)
}
}

React-Native unable to call function

In my app i have a function outside 'export' which return a view, on click event of a button inside view another function is invoked which defined inside 'export'.
While clicking button error is received -
this4.myFunc is not a function.
screenshot attached below - error screen shot
below is my code -
export default class RosterView extends Component{
constructor(props){
super(props)
this.state={
....
}
render () {
mContext = this;
return (
<List dataArray={mContext.state.rosterList}
renderRow={(item) =>
<View style = {MainView.PlayerSelected}>
<AddPlayersToRosterList
data={item}/>
</View>
}>
</List>
);
}
isExist = (item) =>{
//My Code
return flag;
}
addRemovePlayer = (data) => {
//My Code
}
}
const AddPlayersToRosterList= ({data}) => (
(!mContext.isExist(data))?
<View style = {MainView.PlayerSelected}>
<Ripple style={MainView.PlayerAddRemoveContainer} onPress={()=> this.addRemovePlayer(data)}>
</Ripple>
</View>
)
when press button inside AddPlayersToList, this.addRemovePlayer() shows error.
please provide your help how to fix this problem.
Thanks in advance.
Hi you have many errors here. i tried to rewrite your code. try this here. and let me know if that helps. delete the whole AddPlayersToRosterList functional component that you wrote at the end. and rewrite it inside the component RosterView as a function.
The problem is, if you try to use functions that were not declared inside functional components, those components dont know the functions you are trying to call. In the case of const AddPlayersToRosterList you call the functions isExist(data) and this.addRemovePlayer(data), that were declared inside RosterView, but not inside AddPlayersToRosterList.
// add this function inside RosterView
AddPlayersToRosterList = (data) => {
(!this.isExist(data))?
<View style = {MainView.PlayerSelected}>
<Ripple
style={MainView.PlayerAddRemoveContainer}
onPress={() => this.addRemovePlayer(data)}>
</Ripple>
</View>
}
render (){
var mContext = this;
return (
<List dataArray={mContext.state.rosterList}
renderRow={(item) =>
<View style = {MainView.PlayerSelected}>
{this.AddPlayersToRosterList(item)// make sure you call the function here}
</View>
}>
</List>
);
}
you could just pass as props the functions you need to your functional component without delete anything. but it would be cost more memory and compution power that you probably need depending of how many props you want to pass.
const AddPlayersToRosterList= ({data, isExist, mContext, addRemovePlayer}) => (
(!mContext.isExist(data))?
<View style = {MainView.PlayerSelected}>
<Ripple
style={MainView.PlayerAddRemoveContainer}
onPress={()=> this.addRemovePlayer(data)}
>
</Ripple>
</View>
)
and in your render method
render () {
mContext = this;
return (
<List dataArray={mContext.state.rosterList}
renderRow={(item) =>
<View style = {MainView.PlayerSelected}>
<AddPlayersToRosterList
data={item}
isExist={this.isExist}
mContext={mContext}
addRemovePlayer={this.addRemovePlayer}
/>
</View>
}>
</List>
);
}

How to access this.setstate in onShouldStartLoadWithRequest() of a WebView in React Native?

I'm really struggling to understand how to read and set this.state inside of functions called by WebView when doing specific operations. My end goal is to:
Show a activity indicator when the user clicks a link inside the webview
Perform certain actions based on the URL the user is clicking on
I'm very new to React, but from what I've learned, I should use () => function to bind this from the main object to be accessible inside the function.
This works on onLoad={(e) => this._loading('onLoad Complete')} and I can update the state when the page loaded the first time.
If I use onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest} I can see that it works and my console.warn() is shown on screen. this.state is of course not available.
However if I change it to onShouldStartLoadWithRequest={() => this._onShouldStartLoadWithRequest} the function doesn't seem to be executed at all, and neither this.state (commented in the code below) or console.warn() is run.
Any help is appreciated!
import React, { Component} from 'react';
import {Text,View,WebView} from 'react-native';
class Question extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
debug: 'Debug header'
};
}
render() {
return (
<View style={{flex:1, marginTop:20}}>
<Text style={{backgroundColor: '#f9f', padding: 5}}>{this.state.debug}</Text>
<WebView
source={{uri: 'http://stackoverflow.com/'}}
renderLoading={this._renderLoading}
startInLoadingState={true}
javaScriptEnabled={true}
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest}
onNavigationStateChange = {this._onShouldStartLoadWithRequest}
onLoad={(e) => this._loading('onLoad Complete')}
/>
</View>
);
}
_loading(text) {
this.setState({debug: text});
}
_renderLoading() {
return (
<Text style={{backgroundColor: '#ff0', padding: 5}}>_renderLoading</Text>
)
}
_onShouldStartLoadWithRequest(e) {
// If I call this with this._onShouldStartLoadWithRequest in the WebView props, I get "this.setState is not a function"
// But if I call it with () => this._onShouldStartLoadWithRequest it's not executed at all,
// and console.warn() isn't run
//this.setState({debug: e.url});
console.warn(e.url);
return true;
}
}
module.exports = Question;
To access correct this (class context) inside _onShouldStartLoadWithRequest, you need to bind it with class context, after binding whenever this method will get called this keyword inside it will point to react class.
Like this:
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest.bind(this)}
or use arrow function like this:
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest}
_onShouldStartLoadWithRequest = (e) => {...}
Or like this:
onShouldStartLoadWithRequest={(e) => this._onShouldStartLoadWithRequest(e)}
Check this answer for more details: Why is JavaScript bind() necessary?

How to properly use mapDispatchToProps function in sub component?

I'm making simple Todo Application using React Native + Redux following Youtube.
Adding Todo works well. so I took next step, trying to deleting todo got problem. The Video is little bit old, so the version and platform(Mine is Android) is different. so the way of it little different... (ES5/ES6 etc.)
Anyway... I want to send action to dispatcher using mapDispatchToProps's function, onDeleteTodo, but it's not working.
First I tried to connect the component to store, so Added line TodoItem = connect(mapStateToProps, mapDispatchToProps)(TodoItem);. but the error still left.
Something wrong... but I can't find, How can I fix it?
Thanks in advance... below is my code.
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
ScrollView,
TouchableOpacity
} from 'react-native';
import {connect} from 'react-redux';
import {addTodo, deleteTodo} from '../actions';
class TodoItem extends Component {
render() {
return (
// ***************************************
// Below line (onPress prop) is problem.
// when I trying to save todo,
// Error "undefined is not a function (evaluating 'this.props.onDeleteTodo(this.props.id)')
<TouchableOpacity onPress={this.props.onDeleteTodo(this.props.id)}>
<View style={styles.todoContainer}>
<Text style={styles.todoText}>
{this.props.text}
</Text>
</View>
</TouchableOpacity>
)
}
}
TodoItem = connect(
mapStateToProps,
mapDispatchToProps
)(TodoItem);
class Main extends Component {
constructor(props) {
super(props);
this.state = {
newTodoText: ""
}
}
render() {
var renderTodos = () => {
return this.props.todos.map((todo) => {
return (
<TodoItem text={todo.text} key={todo.id} id={todo.id}/>
)
})
};
return (
<View style={styles.wrapper}>
<View style={styles.topBar}>
<Text style={styles.title}>
To-Do List
</Text>
</View>
<View style={styles.inputWrapper}>
<TextInput
onChange={(event) => {
this.setState({
newTodoText: event.nativeEvent.text
});
}}
value={this.state.newTodoText}
returnKeyType="done"
placeholder="New Todo"
onSubmitEditing={
() => {
if(this.state.newTodoText && this.state.newTodoText != ''){
this.props.onAddTodo(this.state.newTodoText);
this.setState({
newTodoText: ''
});
}
}
}
underlineColorAndroid='transparent'
style={styles.input}/>
</View>
<ScrollView
automaticallyAdjustContentInsets={false}>
{renderTodos()}
</ScrollView>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
todos: state.todos
}
};
const mapDispatchToProps = (dispatch) => {
return {
onAddTodo: (todo) => {
dispatch(addTodo(todo))
},
onDeleteTodo: (id) => {
dispatch(deleteTodo(id))
}
}
};
Main = connect(
mapStateToProps,
mapDispatchToProps
)(Main);
export default Main
If you write yoru code like this onPress={ this.props.onDeleteTodo(this.props.id) } then you are passing to the onPress property anything that is returned by the function this.props.onDeleteTodo. In other words, this.props.onDeleteTodo is executed when the component is rendering.
If you want to pass this function (and not it's returned value) then you need to write onPress={ this.props.onDeleteTodo.bind(this, this.props.id) }. This way you are passing this function with this as a context and this.props.id as it's first argument. More about this method here: Use of the JavaScript 'bind' method
I found the solution... but I don't know why it works.
change prop to callback function
onPress={this.props.onDeleteTodo(this.props.id)}
==>
onPress={ () => { this.props.onDeleteTodo(this.props.id) } }
: Is onPress prop only receive callback function? I don't know.
Move connect statement to below of const mapStateToProps and mapDispatchToProps
: Is const ... variable only can reference below its declaration? I don't know also.
I don't really know If I've understood your code...
By the way, if you are importing any function from somewhere, I think that you don't have to use dispatch method, since deleteTodo is not a property method.
Try again without dispatch(), moreover, try to call directly deleteTodo() method.
EDIT: in onPress event write this -> onPress={() => deleteTodo(this.props.id)}
It should call the method onces the event is triggered
And let me know if it works!

Resources