React-native propagate changes to details through ListView and Navigator

I have the following situation. I have a parent component that contains a list of elements, any of which can be drilled and viewed in a child component. From a child component, you should be able to change the value in the element you are looking at.

In the React world, this would be easy to solve with a parent storing the list as a state, and passing the element and callback for changes in the quality of the props for the child.

With React Native, it seems that this opportunity is lost, since making a change from a child component does not cause a re-rendering before the transition.

I recorded a video about how it looks. https://gfycat.com/GreenAgitatedChanticleer

The 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; 
+5
source share
2 answers

It turns out that this behavior is intentional, at least for now. There is a discussion topic here: https://github.com/facebook/react-native/issues/795

For those looking for a workaround, I use RCTDeviceEventEmitter to transfer data through the 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; 
+3
source

I wanted to propagate the props change to the rest of the route stack. And I find no way to render renderScene() from the first route. Therefore, I use navigator.replace() instead of updating props. I'm looking for a better way to handle this, because I believe there are many use route[0] for working with route[0] that has information and should propagate the change to the rest of the route stack, like what we do on the React props between the parent and his 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); } }); } }) }) 
0
source

All Articles