n8e n8e - 1 month ago 28
Javascript Question

Passing props / state in React-Native Navigator

React Native newbie here.

I'm trying to build a simple React Native app for practice that is essentially just a multi-page contact form.

I'm having trouble figuring out the best way to pass props / state to my child components when I'm rendering them via the navigator (navigatorRenderScene function)

Whenever I try to assign props to my component here, it allows me to pass a string but not a function or an object. For example in the component First, I'm trying to pass a string and a function. Only the string will hold its value once the page is rendered.

Am I doing this completely the wrong way? Do I need to look into some kind of state management system like Flux or Redux? Maybe even the redux router package? (Haven't even touched these yet to be honest)

The routing is all working great, it's just the props / state that I can't figure out.

Here's my index

import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';

import { Container, Header, Title, Content, List, ListItem, InputGroup, Input, Button, Icon, Picker } from 'native-base'

// a bunch of crap here being imported that I don't need, I'll trim it down later.

import First from './components/First'
import Second from './components/Second'

class App extends Component {

constructor(props) {
super(props);
this.state = {
text : 'initial state',
sentBy : '',
company : '',
phoneNumber : ''
};
this.testFunc = this.testFunc.bind(this)
}
// end constructor

mostRecentPush() {
// firebase stuff, not really important to the problem
console.log('pushing input data to DB')
firebase.database().ref('mostRecent/').set({
'body' : this.state.text,
'sentBy' : this.state.sentBy,
'location' : this.state.pickerValue,
'company' : this.state.company
});

firebase.database().ref('emails/').push({
'body' : this.state.text,
'sentBy' : this.state.sentBy,
'location' : this.state.pickerValue,
'company' : this.state.company
})
}
// end mostRecentPush() / firebase stuff

onButtonPress() {
this.props.navigator.push({
id: 'Second'
})
} // end onButtonPress

testFunc() {
console.log('calling this from the index')
}

render() {
console.log('rendering home')
return (
<Navigator
initialRoute = {{
id: 'First'
}}
renderScene={
this.navigatorRenderScene
}
/>
)
} // end render

navigatorRenderScene(route, navigator) {
_navigator = navigator;
switch (route.id){
case 'First':
return(<First testString="cat123" testFunc={this.testFunc} navigator={navigator} title="First" />)
// passing our navigator value as props so that the component has access to it
case 'Second':
return(<Second navigator={navigator} title="Second" />)
case 'Third':
return(<Third navigator={navigator} title="Third" />)
case 'Fourth':
return(<Fourth navigator={navigator} title="Fourth" />)
case 'Fifth':
return(<Fifth navigator={navigator} title="Fifth" />)
} //end switch
} // end navigatorRenderScene
} // end component

AppRegistry.registerComponent('App', () => App);


And here's an example component, First

import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';

import { Container, Header, Title, Content, List, ListItem, InputGroup, Input, Button, Icon, Picker } from 'native-base'

// too much stuff being imported, will clean this up later

class First extends Component {

constructor(props) {
super(props)
this.onButtonPress = this.onButtonPress.bind(this)
}

onButtonPress() {
this.setState({
text: 'changed state from first component'
})

console.log(this.state)

this.props.navigator.push({
id: 'Second'
})
}

render() {
return(
<Container>
{console.log('rendering first')}
<Content>
<List>
<ListItem>
<InputGroup>
<Input
placeholder='Name'
/>
</InputGroup>
</ListItem>
</List>
<TouchableHighlight onPress={this.onButtonPress}>
<Text>Go to Page 2</Text>
</TouchableHighlight>
</Content>
</Container>
)
}
}


export default First

Answer

I think the the problem it's with the scope. When setting the renderScene prop to the navigator, you are not binding that function to the actual class, therefore when the navigatorRenderScene gets executed, the scope it's not the same as testFunc.

Try changing this line of code:

navigatorRenderScene(route, navigator) {
  //...
}

For this:

navigatorRenderScene = (route, navigator) => {
  // ...
}

The arrow function will bind the navigatorRenderScene to the class, therefore this.testFunc will exist there.

Other options to do the binding

If you don't want to use an arrow function, you could try something like this in the constructor.

// First options
this.navigatorRenderScene = this.navigatorRenderScene.bind(this)

Or you can also use the bind method directly on the callback

// Second option
renderScene={
  this.navigatorRenderScene.bind(this)
}

Or an arrow function as well

// Third option
renderScene={
  (route, navigator) => this.navigatorRenderScene(route, navigator)
}

Let me know if this works. Good luck!