How do I bind a function outside of scope in React Native? I'm getting the errors:
undefined is not an object evaluating this.state
&
undefined is not an object evaluating this.props
I'm using the render method to evoke renderGPSDataFromServer() when the data has been loaded. The problem is, I'm trying to use _buttonPress() and calcRow() inside of renderGPSDataFromServer(), but I'm getting those errors.
I've added
constructor(props) {
super(props);
this._buttonPress = this._buttonPress.bind(this);
this.calcRow = this.calcRow.bind(this);
to my constructor and I've changed _buttonPress() { to _buttonPress = () => { and still nothing.
I think I understand the problem but I don't know how to fix it:
renderLoadingView() {
return (
<View style={[styles.cardContainer, styles.loading]}>
<Text style={styles.restData}>
Loading ...
</Text>
</View>
)
}
_buttonPress = () => {
this.props.navigator.push({
id: 'Main'
})
}
renderGPSDataFromServer =() => {
const {loaded} = this.state;
const {state} = this.state;
return this.state.dataArr.map(function(data, i){
return(
<View style={[styles.cardContainer, styles.modularBorder, styles.basePadding]} key={i}>
<View style={styles.cardContentLeft}>
<TouchableHighlight style={styles.button}
onPress={this._buttonPress().bind(this)}>
<Text style={styles.restData}>View Video</Text>
</TouchableHighlight>
</View>
<View style={styles.cardContentRight}>
<Text style={styles.restData}>{i}</Text>
<View style={styles.gpsDataContainer}>
<Text style={styles.gpsData}>
{Number(data.lat).toFixed(2)}</Text>
<Text style={styles.gpsData}>{Number(data.long).toFixed(2)}</Text>
</View>
<Text style={styles.gpsData}>
{this.calcRow(55,55).bind(this)}
</Text>
</View>
</View>
);
});
}
render = ()=> {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return(
<View>
{this.renderGPSDataFromServer()}
</View>
)
}};
How do I go about fixing this and in this case what is the problem?
this.props are read-only
React docs - component and props
And therefore a component shouldn't try a to modify them let alone mutate them as you are doing here:
_buttonPress = () => {
this.props.navigator.push({
id: 'Main'
})
}
I'd suggest using state instead:
_buttonPress = () => {
this.setState = {
...this.state,
navigator: {
...this.state.navigator,
id: 'Main'
}
}
}
Regarding your binding issue:
the .map method takes a 2nd argument that is used to set the value of this when the callback is invoked.
In the context of your question, you just need to pass thisas the 2nd argument to you .map method to bind the components scope's this to it.
This is happening because, the function inside the map method creates a different context. You can use arrow functions as the callback in the map method for lexical binding. That should solve the issue you are having.
renderGPSDataFromServer =() => {
const {loaded} = this.state;
const {state} = this.state;
return this.state.dataArr.map((data, i) => {
return(
<View style={[styles.cardContainer, styles.modularBorder, styles.basePadding]} key={i}>
<View style={styles.cardContentLeft}>
<TouchableHighlight style={styles.button}
onPress={this._buttonPress().bind(this)}>
<Text style={styles.restData}>View Video</Text>
</TouchableHighlight>
</View>
<View style={styles.cardContentRight}>
<Text style={styles.restData}>{i}</Text>
<View style={styles.gpsDataContainer}>
<Text style={styles.gpsData}>
{Number(data.lat).toFixed(2)}</Text>
<Text style={styles.gpsData}>{Number(data.long).toFixed(2)}</Text>
</View>
<Text style={styles.gpsData}>
{this.calcRow(55,55).bind(this)}
</Text>
</View>
</View>
);
});
}
Also, once you've used arrow functions in the class function definition you
don't need to bind them in constructor like:
constructor(props) {
super(props);
this._customMethodDefinedUsingFatArrow = this._customMethodDefinedUsingFatArrow.bind(this)
}
Also, once you have defined class functions as arrow functions, you
don't need to use the arrow functions while calling them either:
class Example extends React.Component {
myfunc = () => {
this.nextFunc()
}
nextFunc = () => {
console.log('hello hello')
}
render() {
// this will give you the desired result
return (
<TouchableOpacity onPress={this.myFunc} />
)
// you don't need to do this
return (
<TouchableOpacity onPress={() => this.myFunc()} />
)
}
}
not sure if this is the problem, but I think is code is wrong, and may be potentially causing your issue.
<View style={styles.cardContentLeft}>
<TouchableHighlight style={styles.button}
onPress={this._buttonPress().bind(this)}>
<Text style={styles.restData}>View Video</Text>
</TouchableHighlight>
</View>
specifically this line onPress={this._buttonPress().bind(this)}>
you are invoking the function and binding it at the same time.
The correct way to do this would be so
onPress={this._buttonPress.bind(this)}>
this way the function will be called only onPress.
You are going in the right direction, but there is still a minor issue. You are passing a function to your map callback that has a different scope (this) than your component (because it is not an arrow function), so when you do bind(this), you are rebinding your callback to use the scope from map. I think this should work, it basically turns the callback that you pass to map into an arrow function. Also, since you bind your function in the constructor, you do not need to do it again:
// The constructor logic remains the same
// ....
renderLoadingView() {
return (
<View style={[styles.cardContainer, styles.loading]}>
<Text style={styles.restData}>
Loading ...
</Text>
</View>
)
}
_buttonPress = () => {
this.props.navigator.push({
id: 'Main'
})
}
renderGPSDataFromServer =() => {
const {loaded} = this.state;
const {state} = this.state;
return this.state.dataArr.map((data, i) => {
return(
<View style={[styles.cardContainer, styles.modularBorder, styles.basePadding]} key={i}>
<View style={styles.cardContentLeft}>
<TouchableHighlight style={styles.button}
onPress={this._buttonPress}>
<Text style={styles.restData}>View Video</Text>
</TouchableHighlight>
</View>
<View style={styles.cardContentRight}>
<Text style={styles.restData}>{i}</Text>
<View style={styles.gpsDataContainer}>
<Text style={styles.gpsData}>
{Number(data.lat).toFixed(2)}</Text>
<Text style={styles.gpsData}>{Number(data.long).toFixed(2)}</Text>
</View>
<Text style={styles.gpsData}>
{this.calcRow(55,55).bind(this)}
</Text>
</View>
</View>
);
});
}
render = ()=> {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return(
<View>
{this.renderGPSDataFromServer()}
</View>
)
}};
Related
I am getting an error that I do not understand when using the useState and useSetState functions that I sent as prop to the component I created.
Here is the main component which the CalcButton placed:
export const Calculator = () => {
const [operationText, setOperationText] = React.useState('');
const [operationHistory, setOperationHistory] = React.useState([]);
return (
<SafeAreaView>
<CalcButton operationText={operationText} setOperationText={setOperationText} />
</SafeAreaView>
);
};
Calcbutton.tsx Component
export default function CalcButton(
operationText,
setOperationText,
) {
const handleClick = () => {
setOperationText('2')
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={handleClick} style={styles.inner}>
<View style={styles.middleInner}>
<Text style={styles.label}>=</Text>
</View>
</TouchableOpacity>
</View>
);
}
[enter image description here][1]
When I click CalcButton it gives that error.
https://i.stack.imgur.com/FFfYl.png
Thanks for all helps!
In CalcButton component I added curly braces to my props and it works
export default function CalcButton(
{operationText,
setOperationText}
) {
const handleClick = () => {
setOperationText('2')
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={handleClick} style={styles.inner}>
<View style={styles.middleInner}>
<Text style={styles.label}>=</Text>
</View>
</TouchableOpacity>
</View>
);
}
I'm trying to make a wrapper component in react-native that I can pass down all its props to the children it wraps around. What I really want is to pass down all function props down to all its children. It looks something like this below. I want the onPress in Wrapper to be called when the TouchableOpacity is pressed.
I tried this below but it doesn't work
const Wrapper = ({children,...props})=>{
return <View {...props}>{children}</View>
}
const App = ()=>{
return (
<View style={{flex:1}}>
<Wrapper onPress={()=>{console.log(2)}}>
<TouchableOpacity/>
</Wrapper>
</View>
)
}
It looks like you're looking to map the children and apply the props to each one. That might look like this:
const Wrapper = ({children,...props})=>{
return (<>
{React.Children.map(children, child => (
React.cloneElement(child, {...props})
))}
</>);
}
(method of mapping the children borrowed from this answer)
const App = () => {
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={() => {
// do the action need here here
}}>
<Wrapper >
</Wrapper>
</TouchableOpacity>
</View>
)
}
I would advise you to use hooks function instead
If you try to reuse functions that are related
** useAdd.js **
export default () => {
const addFuction(a, b) => {
// do preprocessing here
return a + b
}
return [addFuction]
}
main componet
import useAdd from "/useAdd";
const App = () => {
const [addFuction] = useAdd()
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={() => {
addFuction(4,5)
}}>
...action component...
</TouchableOpacity>
</View>
)
}
console in useAdd hook.... to see visual changes use the react useState
I'm trying to map an array from state - but confused re the correct syntax - can anyone please advise where i'm going wrong:
This is what I have at the mo:
newsStorys = () => {
return (
{this.state.newsFeed.map((a) => {
<View style={ModalStyles.newsArticle}>
<Text style={ModalStyles.newsDate}>{a.date}</Text>
<Text style={ModalStyles.newsTitle}>{a.title}</Text>
<Text style={ModalStyles.newsDesc}>
{a.story}
</Text>
</View>
}
}
);
};
I'm not sure if that is the whole code of your component, but I can see three things.
If newsFeed is not initialized when component first render (let's say it is undefined yet), then newsFeed.map()will throw an exception.
You are not returning anything from map call. you should write something like this:
newsStorys = () => {
if (!this.state.newsFeed) return null;
return this.state.newsFeed.map((a) => ({ // <--- note the parentheses here, you don't have it
<View style={ModalStyles.newsArticle}>
<Text style={ModalStyles.newsDate}>{a.date}</Text>
<Text style={ModalStyles.newsTitle}>{a.title}</Text>
<Text style={ModalStyles.newsDesc}>
{a.story}
</Text>
</View>
});
);
};
If you want to avoid the parentheses, then you need to explicitly return something, like this:
this.state.newsFeed.map((a) => {
return (
<View style={ModalStyles.newsArticle}>
<Text style={ModalStyles.newsDate}>{a.date}</Text>
<Text style={ModalStyles.newsTitle}>{a.title}</Text>
<Text style={ModalStyles.newsDesc}>
{a.story}
</Text>
</View>
);
});
It is possible that you need an extra view to wrap the list of views returned by map.
Also you need to provide a unique key to each view, so React can keep track on them.
<View style={ModalStyles.newsArticle} key={'nome unique value'}>
...
</View>
Finally I think it would be better using a FlatList instead of map.
Cheers!
Had a play and a good dig around the web and found the syntax answer: (Thanks to Bruno for the Key and pointers).
newsStorys = () => {
return this.state.newsFeed.map((value, index) => {
return (
<View style={ModalStyles.newsArticle} key={index}>
<Text style={ModalStyles.newsDate}>{value.date}</Text>
<Text style={ModalStyles.newsTitle}>{value.title}</Text>
<Text style={ModalStyles.newsDesc}>{value.story}</Text>
</View>
);
});
};
Try this
newsStorys = () => (
this.state.newsFeed.map(({ date, story, title }, index) =>
<View key={`news-${index}`} style={ModalStyles.newsArticle}>
<Text style={ModalStyles.newsDate}>{date}</Text>
<Text style={ModalStyles.newsTitle}>{title}</Text>
<Text style={ModalStyles.newsDesc}>{story}</Text>
</View>
));
So I've seen many posting the same problem, but for some I don't seem to be able to adapt the posted solutions to my case.. I hope someone can tell me exactly what changes I need to do in order to get this working, since I don't know how to implement the suggested solutions!
I am using React Native Swipeable
Example of someone having the same issue
I have a file in which I built the Swipeable Component and an other class which calls the component. I've set a timeout close function on the onSwipeableOpen as a temporary solution. But ideally it should close immediately upon pressing "delete". The "..." stands for other code which I deleted since it's not important for this case.
AgendaCard.js
...
const RightActions = ({ onPress }) => {
return (
<View style={styles.rightAction}>
<TouchableWithoutFeedback onPress={onPress}>
<View style={{ flexDirection: "row", alignSelf: "flex-end" }}>
<Text style={styles.actionText}>Löschen</Text>
<View style={{ margin: 5 }} />
<MaterialIcons name="delete" size={30} color="white" />
</View>
</TouchableWithoutFeedback>
</View>
);
};
...
export class AgendaCardEntry extends React.Component {
updateRef = (ref) => {
this._swipeableRow = ref;
};
close = () => {
setTimeout(() => {
this._swipeableRow.close();
}, 2000);
};
render() {
return (
<Swipeable
ref={this.updateRef}
renderRightActions={() => (
<RightActions onPress={this.props.onRightPress} />
)}
onSwipeableOpen={this.close}
overshootRight={false}
>
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.entryContainer}>
<Text style={styles.entryTitle}>{this.props.item.info}</Text>
<Text style={styles.entryTime}>
eingetragen um {this.props.item.time} Uhr
</Text>
</View>
</TouchableWithoutFeedback>
</Swipeable>
);
}
}
Agenda.js
...
renderItem(item) {
...
<AgendaCardAppointment
item={item}
onRightPress={() => firebaseDeleteItem(item)}
/>
...
}
I'm having the same issue and have been for days. I was able to hack through it, but it left me with an animation I don't like, but this is what I did anyways.
export class AgendaCardEntry extends React.Component {
let swipeableRef = null; // NEW CODE
updateRef = (ref) => {
this._swipeableRow = ref;
};
close = () => {
setTimeout(() => {
this._swipeableRow.close();
}, 2000);
};
onRightPress = (ref, item) => { // NEW CODE
ref.close()
// Delete item logic
}
render() {
return (
<Swipeable
ref={(swipe) => swipeableRef = swipe} // NEW CODE
renderRightActions={() => (
<RightActions onPress={() => this.onRightPress(swipeableRef)} /> // NEW CODE
)}
onSwipeableOpen={this.close}
overshootRight={false}
>
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.entryContainer}>
<Text style={styles.entryTitle}>{this.props.item.info}</Text>
<Text style={styles.entryTime}>
eingetragen um {this.props.item.time} Uhr
</Text>
</View>
</TouchableWithoutFeedback>
</Swipeable>
);
}
}
I have a listview, whenever user clicks on an item, i want to do something with onPress (touchable highlight).
I tried defining function then calling them in onPress but they didnt work.
first i defined a function like this :
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
this._dosome = this._dosome.bind(this);
}
_dosome(dd){
console.log(dd);
}
Then :
render(){
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<View style={{
flex: 1
}}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
style={styles.listView}
/>
</View>
);
}
renderRow(data) {
var header = (
<View>
<View style={styles.rowContainer}>
<View style={styles.textContainer}>
<Text style={styles.title}>{data.nid}</Text>
<Text style={styles.description} numberOfLines={0}>{data.title}</Text>
</View>
</View>
<View style={styles.separator}></View>
</View>
);
///////////
var cid = [];
var content = [];
for(let x=0; x < Object.keys(data.course).length; x++){
cid[x] = data.course[x].course_id;
content.push(
<TouchableHighlight
underlayColor='#e3e0d7'
key={x}
onPress={ this._dosome } //// **** PROBLEM HERE ****
style={styles.child}
>
<Text style={styles.child}>
{data.course[x].title}
</Text>
</TouchableHighlight>
);
}
console.log(cid);
var clist = (
<View style={styles.rowContainer}>
{content}
</View>
);
////////////
return (
<Accordion
header={header}
content={clist}
easing="easeOutCubic"
/>
);
}
renderLoadingView() {
return (
<View style={styles.loading}>
<Text style={styles.loading}>
Loading Courses, please wait...
</Text>
</View>
);
}
}
But it didnt worked, error :
TypeError: Cannot read property '_dosome' of null(…)
I tried calling it onPress like this :
onPress={ this._dosome.bind(this) }
didn't help
onPress={ ()=> {this._dosome.bind(this)}}
didn't help
onPress={ console.log(this.state.loaded)}
even i cannot even access props defined in constructor. it don't know what is "this" !
I tried to define the function other way like this :
var _dosome = (dd) => {
console.log(dd);
};
I tried defining the _dosome before render, after render, inside render, inside renderRow, and after renderRow. all same results.
I did check lots of tutorials, lots of sample apps on rnplay, lots of threads in stackoverflow, lots of examples, but none of them worked for me.
any solutions/ideas?
Any help is highly appreciated. i'm struggling with this issue for 2 days now.
Thanks in Advance!
How to solve your problem
Change
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
style={styles.listView}
/>
To
<ListView
dataSource={this.state.dataSource}
renderRow={data => this.renderRow(data)}
style={styles.listView}
/>
Or
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
style={styles.listView}
/>
Why?
As you already know(since you are trying to solve the problem by using arrow function and bind), to call renderRow, the caller should be in the same context as the component itself.
But this connection is lost in renderRow={this.renderRow}, which created a new function(new context).