React-Native - undefined is not an object" (evaluating 'this.state.*) _renderRow - reactjs

I am trying to call the state inside the _renderRow() function but I keep receiving the following error:
This is my source code:
Source code
var Store = require('./store/Store.js');
var MessageOptions = require('./MessageOptions.js')
var React = require('react');
var ReactNative = require('react-native');
var {
AppRegistry,
Image,
ListView,
StyleSheet,
Text,
View,
} = ReactNative;
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowID: 20,
dataSource: ds
}
}
componentWillMount() {
this.getNewMessages();
}
getNewMessages() {
Store.getMessages().then((messages) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(messages)
});
},(reason) => {
console.log("Error:", reason);
});
}
_renderRow(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
var currentSelectedRowID = this.state.selectedRowID;
return (
<View>
<View style={styles.row}>
<Image style={styles.thumb} source={require('../android/app/src/main/res/mipmap-hdpi/ic_launcher.png')} />
<Text style={styles.text}>
{rowData.text}
</Text>
</View>
<MessageOptions optionsData={rowData.options} message_uri={rowData.uri}/>
</View>
)
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
/>
)
}
};
var styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'center',
padding: 10,
backgroundColor: 'white',
},
thumb: {
width: 64,
height: 64,
},
text: {
flex: 1,
},
news_item: {
paddingLeft: 10,
paddingRight: 10,
paddingTop: 15,
paddingBottom: 15,
marginBottom: 5
},
news_item_text: {
color: '#575757',
fontSize: 18
}
});
module.exports = Application;
The error is coming from the _renderRow method where I am storing this.state.selectedRowID to the var currentSelectedRowID.
Thanks in advance.

Solution
The problem was with my ES6 class constructor:
class Application extends React.Component {
constructor(props) {
super(props);
this._renderRow = this._renderRow.bind(this);
this.state = {
selectedRowID: 20,
dataSource: ds
}
}
The solution was to add the following line to my constructor:
this._renderRow = this._renderRow.bind(this);

Related

Passing props to child doesn't make him update - React Native

I am writing because I can't figure why one of the two childs Graph.js won't update after I udpate the state of the parent Data.js (throught a "lift up" via the second child Bouton.js).
I feel giga dumb and it's been now hours, I'm desperate...
I am trying to display charts with buttons above to choose a period of time for the chart (day, week, month). Clicking the button can change the state of the parent but I can't make the child Graph to update. I know I am doing something wrong.
Parent: Data.js
export default class Data extends React.Component {
constructor(props) {
super(props);
this.state = { periode: "Jour" };
}
handleClick(p) {
this.setState({
periode: p
});
}
render() {
console.log(this.state);
return (
<View>
<Boutons
onClick={res => this.handleClick(res)}
cursor={this.state.periode}
/>
<Graph periode={this.state.periode} dataType="temp" />
<Graph periode={this.state.periode} dataType="press" />
</View>
);
}
}
Child 1 (everything seems fine)
export default class Boutons extends React.Component {
constructor(props) {
super(props);
}
_getNextEntryTime() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var res;
if (m >= 30) {
res = (h + 1).toString() + ":00";
} else {
res = h.toString() + ":30";
}
return res;
}
//Gestion de la selection des boutons
_boutonStyle(periode) {
if (this.props.cursor == periode) {
return {
// backgroundColor: "#9c9c9c",
borderBottomWidth: 3,
borderColor: "#728FB5",
width: Dimensions.get("window").width / 3 - 10,
height: 30,
alignItems: "center",
justifyContent: "center"
};
} else {
return {
backgroundColor: "#dfdfdf",
width: Dimensions.get("window").width / 3 - 10,
height: 30,
borderRadius: 2,
alignItems: "center",
justifyContent: "center"
};
}
}
_textStyle(periode) {
if (this.props.cursor == periode) {
return { color: "#728FB5" };
} else {
return { color: "black" };
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.container_top}>
<View style={styles.rect}>
<Text style={styles.text_top}>
Prochain relevé: {`\n`}
<Text style={styles.numbers}>{this._getNextEntryTime()}</Text>
</Text>
</View>
<Single />
</View>
<View style={styles.container_buttons}>
<TouchableOpacity
style={this._boutonStyle("Jour")}
onPress={() => this.props.onClick("Jour")}
>
<Text style={this._textStyle("Jour")}>Jour</Text>
</TouchableOpacity>
<TouchableOpacity
style={this._boutonStyle("Semaine")}
onPress={() => this.props.onClick("Semaine")}
>
<Text style={this._textStyle("Semaine")}>Semaine</Text>
</TouchableOpacity>
<TouchableOpacity
style={this._boutonStyle("Mois")}
onPress={() => this.props.onClick("Mois")}
>
<Text style={this._textStyle("Mois")}>Mois</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Graph.js Child 2 that won't update, nothing is happening
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [], format_const: null };
}
// Chargement de la page et recherche des entrys
componentDidMount() {
const entrys = getEntry(this.props.periode);
entrys.then(reponse => {
reponse.map(donnee => {
this.setState({
data: this.state.data.concat(donnee[this.props.dataType])
});
});
this.setState({
format_const: Config.CHART[this.props.dataType],
isLoading: false
});
});
}
// Affichage du loading
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
}
_displayChart() {
return (
<LineChart
data={{
datasets: [
{
data: this.state.data,
strokeWidth: 2 // optional
}
],
legend: [this.state.format_const["label"]]
}}
width={Dimensions.get("window").width - 10} // from react-native
height={220}
withInnerLines={false}
yAxisSuffix={this.state.format_const["alert"]}
onDataPointClick={({ value, dataset, getColor }) =>
Alert.alert(`${value}` + this.state.format_const["alert"])
}
chartConfig={{
backgroundGradientFrom: this.state.format_const["color"],
backgroundGradientTo: this.state.format_const["color"],
decimalPlaces: 0, // optional, defaults to 2dp
color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
style: {
borderRadius: 16
},
propsForDots: {
r: "2"
}
}}
bezier
style={{
marginVertical: 10,
borderRadius: 16
}}
/>
);
}
render() {
if (!this.state.isLoading) {
return <View>{this._displayChart()}</View>;
} else {
return <View>{this._displayLoading()}</View>;
}
}
}
It appears that your only use of the prop periode is in the componentDidMount method of Graph. So at mount time, Graph reads what the prop is, and then sets the state, which is used in the Graph render method. But when the parent component changes its state, and the new value for this.state.periode is passed as a prop to Graph, Graph doesnt doesn't necessarily know what to do with this updated information. So you'll need to use a componentDidUpdate statement to read new props coming in from the parent's state:
componentDidUpdate(prevProps){
if (prevProps.periode !== this.props.periode){
const entrys = getEntry(this.props.periode);
entrys.then(reponse => {
reponse.map(donnee => {
this.setState({
data: this.state.data.concat(donnee[this.props.dataType])
});
});
});
}
}
I'm assuming you want the same thing to happen in `componentDidMount` as is happening in `componentDidUpdate`, but you may need to change the code within `componentDidUpdate` to whatever you need.

undefined is not an object (evaluating 'this.state.user.avatar')

Image of Error
I am using ios emulator and keep on receiving this error when i go to run the profile page. It first loads for a little bit then stops and the error pops up it says it's on the 'this.state.user.avatar' but i can't seem to see what i wrong with it? what am i doing wrong? if someone can help me that would be great!
This is my ProfileScreen.js file
import React from "react";
import {View, Text, StyleSheet, TouchableOpacity, Button, Image } from "react-native";
import Fire from '../utilities/Fire';
export default class ProfileScreen extends React.Component {
state = {
user: {}
};
unsubscribe = null;
componentDidMount() {
const user = this.props.uid || Fire.shared.uid
this.unsubscribe = Fire.shared.firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState({ user: doc.data() });
});
}
componentWillUnmount() {
this.unsubscribe();
};
render() {
return(
<View style={styles.container}>
<View style = {{ MarginTop: 64, alignItems: "Center" }}>
<View style={styles.avatarContainer}>
<Image style={styles.avatar} source={this.state.user.avatar ? { uri: this.state.user.avatar } : require("../assets/avatar.png")}
/>
</View>
<Text style={styles.name}>{this.state.user.name}</Text>
</View>
<View style={styles.subContainer}>
<View style={styles.stat}>
<Text style={styles.info}>8/10</Text>
<Text style={styles.Title}>Rewards</Text>
</View>
<View style={styles.stat}>
<Text style={styles.info}>80/100</Text>
<Text style={styles.Title}>Badges</Text>
</View>
</View>
<Button onPress={() => {Fire.shared.signOUt()}} title="Log Out" />
</View>
);
}
}
const styles = StyleSheet.create({
container:{
flex: 1,
},
avatarContainer:{
shadowColor: "#151734",
shadowRadius: 15,
shadowOpacity: 0.4
},
avatar: {
width: 136,
height: 136,
borderRadius: 68
},
name: {
marginTop: 24,
fontSize: 16,
fontWeight: "600"
},
subContainer: {
flexDirection: "row",
justifyContent: "space-between",
margin: 32,
},
stat: {
alignItems:"center",
},
info: {
color: "#4F566D",
fontSize: 18,
fontWeight: "300"
},
Title: {
color: "#C3C5CD",
fontSize: 12,
fontWeight: "500",
marginTop: 4
}
});
This is my Fire.js file
import FirebaseKeys from '../config';
import firebase from 'firebase';
require("firebase/firestore");
class Fire{
constructor() {
firebase.initializeApp(FirebaseKeys);
}
addPost = async({ text, localUri }) => {
const remoteUri = await this.uploadPhotoAsync(localUri, 'photos/${this.uid}/${Date.now()}');
return new Promise ((res, rej) => {
this.firestore
.collection("posts")
.add ({
text,
uid: this.uid,
timestamp: this.timestamp,
image: remoteUri
})
.then(ref => {
res(ref);
})
.catch(error => {
rej(error);
});
});
};
uploadPhotoAsync = async (uri, filename) => {
return new Promise(async (res, rej) => {
const response = await fetch(uri);
const file = await response.blob();
let upload = firebase
.storage()
.ref(filename)
.put(file);
upload.on(
"state_changed",
snapshot => {},
err => {
rej(err);
},
async () => {
const url = await upload.snapshot.ref.getDownloadURL();
res(url);
}
);
});
};
createUser = async user => {
let remoteUri = null
try {
await firebase.auth().createUserWithEmailAndPassword(user.email, user.password)
let db = this.firestore.collection("users").doc(this.uid)
db.set({
name: user.name,
email: user.email,
avatar: null
})
if (user.avatar) {
remoteUri = await this.uploadPhotoAsync(user.avatar, 'avatars/${this.uid}')
db.set({avatar: remoteUri }, { merge: true})
}
} catch (error) {
alert("Error: ", error);
}
};
signOut = () => {
firebase.auth().signOut();
};
get firestore(){
return firebase.firestore();
}
get uid() {
return (firebase.auth().currentUser || {}).uid;
}
get timestamp() {
return Date.now();
}
}
Fire.shared = new Fire();
export default Fire;
If you want to set the state like that you need to do it in the constructor like this:
constructor(props) {
super(props);
this.state = {
user: {}
}
}
So add that code to the top of the class and it should actually set the user to an empty object..
Everywhere else in the app you use setState....
Try to change user{} by user[]

Calling Method in React Native using Refs Does Nothing

I'm making a simple pomodoro app in React Native, and I came across a problem with calling a method from a child component. In the code below, the method I am trying to call is reset, which I call from resetTimer in the parent. This does not work, though no errors are produced; console.logging within the method also produces nothing. I followed the model outlined here in the docs. Any help resolving this issue would be appreciated!
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
class Timer extends React.Component {
constructor(props) {
super(props)
this.state = {
minutes: 25,
seconds: 0,
pomodoro: props.pomodoro,
}
}
componentDidMount() {
this.interval = setInterval(this.decrement, 1000)
}
reset = () => {
this.setState(prevState => ({
minutes: (prevState.pomodoro ? 5 : 25),
seconds: 0,
}))
}
decrement = () => {
if ((this.state.minutes+this.state.seconds)===0){
this.setState(prevState => ({
pomodoro: !prevState.pomodoro,
minutes: (prevState.pomodoro ? 25 : 5),
}))
} else{
if (this.props.start){
if (this.state.seconds===0){
this.setState(prevState => ({
minutes: prevState.minutes - 1,
seconds: 59,
}))
} else{
this.setState(prevState => ({
seconds: prevState.seconds - 1
}))
}
}
}
}
render() {
return (
<Text style={styles.time}>
{("0"+this.state.minutes).slice(-2)}:
{("0"+this.state.seconds).slice(-2)}
{this.props.start}
</Text>
);
}
}
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
start: false,
pomodoro: false,
buttonText: "Start"
}
}
toggleStart = () => this.setState(prevState => ({
start: !prevState.start,
buttonText: (prevState.start ? "Start" : "Stop")
}))
resetTimer = () => {
this.toggleStart()
this._timer.reset()
}
render() {
return (
<View style={styles.container}>
<Timer
start={this.state.start}
pomodoro={this.state.pomodoro}
reset={this.state.reset}
toggleStart={() => this.toggleStart}
ref={component => { this._timer = component; }}
/>
<View style={styles.buttonRow}>
<Button
title={this.state.buttonText}
onPress={this.toggleStart}>
</Button>
<Button
title="Reset"
onPress={this.resetTimer}>
Timer.resetTime
</Button>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
time: {
fontSize: 70,
color: 'tomato',
alignItems: 'center',
justifyContent: 'center',
},
buttonRow: {
flexDirection: 'row'
},
});
Usually, you shouldn't have to call a childs function in the parent. When you find yourself in this situation, you might be overcomplicating your component structure. Why not move the reset button into the Timer component?
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
class Timer extends React.Component {
constructor(props) {
super(props)
this.state = {
minutes: 25,
seconds: 0,
pomodoro: props.pomodoro,
}
}
componentDidMount() {
this.interval = setInterval(this.decrement, 1000)
}
reset = () => this.setState(prevState({
minutes: (prevState.pomodoro ? 5 : 25),
seconds: 0,
}))
decrement = () => {
if ((this.state.minutes+this.state.seconds)===0){
this.setState(prevState => ({
pomodoro: !prevState.pomodoro,
minutes: (prevState.pomodoro ? 25 : 5),
}))
} else{
if (this.props.start){
if (this.state.seconds===0){
this.setState(prevState => ({
minutes: prevState.minutes - 1,
seconds: 59,
}))
} else{
this.setState(prevState => ({
seconds: prevState.seconds - 1
}))
}
}
}
}
render() {
return (
<View>
<Text style={styles.time}>
{("0"+this.state.minutes).slice(-2)}:
{("0"+this.state.seconds).slice(-2)}
{this.props.start}
</Text>
<View style={styles.buttonRow}>
<Button
title={this.props.buttonText}
onPress={this.props.toggleStart}>
</Button>
<Button
title="Reset"
onPress={this.reset}>
Timer.resetTime
</Button>
</View>
</View>
);
}
}
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
start: false,
pomodoro: false,
buttonText: "Start"
}
}
toggleStart = () => this.setState(prevState => ({
start: !prevState.start,
buttonText: (prevState.start ? "Start" : "Stop")
}))
render() {
return (
<View style={styles.container}>
<Timer
start={this.state.start}
pomodoro={this.state.pomodoro}
toggleStart={this.toggleStart}
buttonText={this.state.buttonText}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
time: {
fontSize: 70,
color: 'tomato',
alignItems: 'center',
justifyContent: 'center',
},
buttonRow: {
flexDirection: 'row'
},
});

Module Error Requiring unknown module

Hello am having a problem I am using react native am a newbie and I am creating a app that has some tabs but what i want is when i click the tab each as its own navigation bar.
I follow [AppCoda Example][1] but i notice that code base is different from the new code base react native. my code is bellow. Remember am trying to get a Nav bar for each tabs an i created a folder structure to require each tab in but am getting that unknown module when i know its there. Just to add if i add the same code which was in sub folder in the index.os.js it works look below:
'use strict';
var React = require('react-native');
var SearchButton = require('./app/components/buttons/searchButton');
var CameraButton = require('./app/components/buttons/cameraButton');
var ProfileButton = require('./app/components/buttons/profileButton');
var ContactButton = require('./app/components/buttons/contactButton');
var {
AppRegistry,
TabBarIOS,
NavigatorIOS,
StyleSheet,
Text,
View
} = React;
class AwesomeProject extends React.Component{
constructor(props) {
super(props);
this.state = {
selectedTab: 'Search'
};
}
render() {
return (
<TabBarIOS selectedTab={this.state.selectedTab} barTintColor="darkslateblue">
<TabBarIOS.Item
selected={this.state.selectedTab === 'Search'}
systemIcon="search"
onPress={() => {
this.setState({
selectedTab: 'Search'
});
}} style={styles.container} >
<SearchButton/>
</TabBarIOS.Item>
<TabBarIOS.Item systemIcon="bookmarks"
selected={this.state.selectedTab === 'Camera'}
icon={{uri:'Camera'}}
onPress={() => {
this.setState({
selectedTab: 'Camera'
});
}}>
<CameraButton/>
</TabBarIOS.Item>
<TabBarIOS.Item systemIcon="history"
selected={this.state.selectedTab === 'Profile'}
icon={{uri:'Profile'}}
onPress={() => {
this.setState({
selectedTab: 'Profile'
});
}}>
<ProfileButton/>
</TabBarIOS.Item>
<TabBarIOS.Item systemIcon="contacts"
selected={this.state.selectedTab === 'Contacts'}
icon={{uri:'Contacts'}}
onPress={() => {
this.setState({
selectedTab: 'Contacts'
});
}}>
<ContactButton/>
</TabBarIOS.Item>
</TabBarIOS>
);
}
};
var styles = StyleSheet.create({
navigator: {
flex: 1,
},
tabContent: {
flex: 1,
alignItems: 'center', },
tabText: {
color: 'white',
margin: 50, },
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
Now in the search button which should get the search title from a navigation folder thats where the problem is its saying unknown module
'use strict';
var React = require('react-native');
var searchTitle = require('./app/components/navigation/searchTitle');
var {
StyleSheet,
View,
NavigatorIOS,
Text
} = React
var styles = StyleSheet.create({
navigator: {
flex: 1
}
}
);
class Search extends React.Component{
render() {
return (
<NavigatorIOS
style={styles.navigator}
initialRoute={{
title: 'SomeTitle',
component: searchTitle
}}/>
);
}
}
module.exports = Search;
can some one help me with this.
You are asking require to search a relative path. From your examples I see that searchButton is in ./app/components/buttons/ and searchTitle is in ./app/components/navigation/, so if you want to require searchTitle from searchButton the path you need to specify is ../navigation/searchTitle.
var back_bg = require('./../img/menu.png');

react-native propagate changes in props through ListView and Navigator

I have the following situation. I have a parent component that contains a list of items, any of which can be drilled down into and viewed in a child component. From the child component, you should be able to change a value in the item you are looking at.
In the React web world, this would be easy to solve with the parent storing the list as state, and passing the item and a callback for changes as props to the child.
With React Native, it seems like that possibility is lost, since causing a change from the child component does not trigger a re-render until navigating away.
I've recorded a video of what this looks like. https://gfycat.com/GreenAgitatedChanticleer
Code is below.
index.ios.js
var React = require('react-native');
var {
AppRegistry,
Navigator
} = React;
var List = require('./list');
var listviewtest = React.createClass({
render: function() {
return (
<Navigator
initialRoute={{ component: List }}
renderScene={(route, navigator) => {
return <route.component navigator={navigator} {...route.passProps} />;
}} />
);
}
});
AppRegistry.registerComponent('listviewtest', () => listviewtest);
list.js
var React = require('react-native');
var _ = require('lodash');
var {
View,
Text,
TouchableHighlight,
ListView
} = React;
var Detail = require('./detail');
var List = React.createClass({
getInitialState() {
var LANGUAGES = [
{ id: 1, name: 'JavaScript' },
{ id: 2, name: 'Obj-C' },
{ id: 3, name: 'C#' },
{ id: 4, name: 'Swift' },
{ id: 5, name: 'Haskell' }
];
var ds = new ListView.DataSource({ rowHasChanged: (a, b) => a !== b })
return {
languages: LANGUAGES,
ds: ds.cloneWithRows(LANGUAGES)
};
},
goToLanguage(language) {
this.props.navigator.push({
component: Detail,
passProps: {
language: language,
changeName: this.changeName
}
});
},
changeName(id, newName) {
var clone = _.cloneDeep(this.state.languages);
var index = _.findIndex(clone, l => l.id === id);
clone[index].name = newName;
this.setState({
languages: clone,
ds: this.state.ds.cloneWithRows(clone)
});
},
renderRow(language) {
return (
<TouchableHighlight onPress={this.goToLanguage.bind(this, language)}>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', paddingTop: 5, paddingBottom: 5, backgroundColor: '#fff', marginBottom: 1 }}>
<Text style={{ marginLeft: 5, marginRight: 5 }}>{language.name}</Text>
</View>
</TouchableHighlight>
);
},
render() {
return (
<View style={{ flex: 1, backgroundColor: '#ddd' }}>
<Text style={{ marginTop: 60, marginLeft: 5, marginRight: 5, marginBottom: 10 }}>Select a language</Text>
<ListView
dataSource={this.state.ds}
renderRow={this.renderRow} />
</View>
);
}
});
module.exports = List;
detail.js
var React = require('react-native');
var {
View,
Text,
TouchableHighlight
} = React;
var Detail = React.createClass({
changeName() {
this.props.changeName(this.props.language.id, 'Language #' + Math.round(Math.random() * 1000).toString());
},
goBack() {
this.props.navigator.pop();
},
render() {
return (
<View style={{ flex: 1, backgroundColor: '#ddd', alignItems: 'center', justifyContent: 'center' }}>
<Text>{this.props.language.name}</Text>
<TouchableHighlight onPress={this.changeName}>
<Text>Click to change name</Text>
</TouchableHighlight>
<TouchableHighlight onPress={this.goBack}>
<Text>Click to go back</Text>
</TouchableHighlight>
</View>
);
}
});
module.exports = Detail;
Turns out this behavior is intentional, at least for now. There's a discussion thread here: https://github.com/facebook/react-native/issues/795
For anyone looking for a workaround, I'm using RCTDeviceEventEmitter to pass data across Navigator. Updated code below
list.js
var React = require('react-native');
var _ = require('lodash');
var {
View,
Text,
TouchableHighlight,
ListView
} = React;
var Detail = require('./detail');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var List = React.createClass({
getInitialState() {
var LANGUAGES = [
{ id: 1, name: 'JavaScript' },
{ id: 2, name: 'Obj-C' },
{ id: 3, name: 'C#' },
{ id: 4, name: 'Swift' },
{ id: 5, name: 'Haskell' }
];
var ds = new ListView.DataSource({ rowHasChanged: (a, b) => a !== b })
return {
languages: LANGUAGES,
ds: ds.cloneWithRows(LANGUAGES)
};
},
goToLanguage(language) {
this.props.navigator.push({
component: Detail,
passProps: {
initialLanguage: language,
changeName: this.changeName
}
});
},
changeName(id, newName) {
var clone = _.cloneDeep(this.state.languages);
var index = _.findIndex(clone, l => l.id === id);
clone[index].name = newName;
RCTDeviceEventEmitter.emit('languageNameChanged', clone[index]);
this.setState({
languages: clone,
ds: this.state.ds.cloneWithRows(clone)
});
},
renderRow(language) {
return (
<TouchableHighlight onPress={this.goToLanguage.bind(this, language)}>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', paddingTop: 5, paddingBottom: 5, backgroundColor: '#fff', marginBottom: 1 }}>
<Text style={{ marginLeft: 5, marginRight: 5 }}>{language.name}</Text>
</View>
</TouchableHighlight>
);
},
render() {
return (
<View style={{ flex: 1, backgroundColor: '#ddd' }}>
<Text style={{ marginTop: 60, marginLeft: 5, marginRight: 5, marginBottom: 10 }}>Select a language</Text>
<ListView
dataSource={this.state.ds}
renderRow={this.renderRow} />
</View>
);
}
});
module.exports = List;
detail.js
var React = require('react-native');
var {
View,
Text,
TouchableHighlight
} = React;
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var Detail = React.createClass({
getInitialState() {
return {
language: this.props.initialLanguage,
subscribers: []
};
},
componentDidMount() {
var subscriber = RCTDeviceEventEmitter.addListener('languageNameChanged', language => {
this.setState({ language });
});
this.setState({
subscribers: this.state.subscribers.concat([subscriber])
});
},
componentWillUnmount() {
this.state.subscribers.forEach(sub => {
console.log('removing');
sub.remove();
});
},
changeName() {
this.props.changeName(this.state.language.id, 'Language #' + Math.round(Math.random() * 1000).toString());
},
goBack() {
this.props.navigator.pop();
},
render() {
return (
<View style={{ flex: 1, backgroundColor: '#ddd', alignItems: 'center', justifyContent: 'center' }}>
<Text>{this.state.language.name}</Text>
<TouchableHighlight onPress={this.changeName}>
<Text>Click to change name</Text>
</TouchableHighlight>
<TouchableHighlight onPress={this.goBack}>
<Text>Click to go back</Text>
</TouchableHighlight>
</View>
);
}
});
module.exports = Detail;
I wanted to propagate prop change to the rest of route stack as well. And I don't find any way to render renderScene() from the first route.. So I use navigator.replace() instead of updating props. I'm looking for the better way to deal with this because I believe there are a lot of use-case to deal with the route[0] that has info and need to propagate the change to the rest of route stack, like what we do on React props between parent and its children.
# this is on parent component and the change is pushed to props(I'm using Redux)
componentWillReceiveProps(nextProps){
this.props.hubs.map((currentHub) => {
nextProps.hubs.map((hub) => {
if(currentHub.updatedAt !== hub.updatedAt){
this.props.navigator.getCurrentRoutes().map((r, index) => {
if(r.getHubId && ( r.getHubId() === hub.objectId ) ){
let route = Router.getHubRoute(hub);
this.props.navigator.replaceAtIndex(route, index);
}
});
}
})
})

Resources