In the following example:
MapView
displays elements of aListView
as annotations- Clicking on a
ListView
element should result in painting it in blue color. - Bonus if the
MapView
andListView
efficiently use the state object
Modifying the DataSource
of ListView
seems to cause the conflict when the active
attribute gets modified:
You attempted to set the key 'active' with the value 'false' on an object that is meant to be immutable and has been frozen.
What is the right way of setting the state?
'use strict';
import React, {Component} from 'react';
import {AppRegistry,View,ListView,MapView,Text,TouchableOpacity} from 'react-native';
var annotations = [
{
title: 'A',active: false,latitude: 45,longitude: 26,latitudeDelta: 0.015,longitudeDelta: 0.015,
},{
title: 'B',active: false,latitude: 49,longitude: 14,latitudeDelta: 0.015,longitudeDelta: 0.015,
},{
title: 'C',active: false,latitude: 26,longitude: 25,latitudeDelta: 0.015,longitudeDelta: 0.015,
}
]
class SampleApp extends Component {
constructor(props) {
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
});
this.state = {
region: annotations[0],
annotations: annotations,
dataSource: ds.cloneWithRows(annotations)
};
}
handleClick(field) {
if (this.previousField) {
this.previousField.active = false;
}
this.previousField = field;
field.active = true;
this.setState({
region: field,
});
}
renderField(field) {
let color = (field.active == true)?'blue':'yellow';
return (
<TouchableOpacity onPress={this.handleClick.bind(this,field)}>
<Text style={{backgroundColor:color,borderWidth:1}}>{field.title}</Text>
</TouchableOpacity>
);
}
render() {
return (
<View style={{flex:1,flexDirection:'column',alignSelf:'stretch'}}>
<MapView
style={{flex:0.5,alignSelf:'stretch',borderWidth:1}}
region={this.state.region}
annotations={this.state.annotations}
/>
<ListView
dataSource={this.state.dataSource}
renderRow={(field) => this.renderField(field)}
/>
</View>
);
}
}
AppRegistry.registerComponent('SampleApp', () => SampleApp);
The Problem
When you set
field.active = true;
orthis.previousField.active = false;
, you're modifying an object (field
) that is present in the datasource of yourListView
. TheListView
throws the error because it freezes its datasource when you create it usingcloneWithRows
. This is to ensure that the datasource can't be modified outside of the normal React component lifecycle (likesetState
). Instead,ListView.DataSource
objects are designed to be changed withcloneWithRows
, which returns a copy of the existing datasource.If you're familiar with the Redux library, it's very similar to the philosophy of having reducer functions return a copy of the state, rather than modifying the existing state.
Cloning the DataSource
To solve this problem, instead of mutating
field
objects in yourhandleClick
function, what you really want to do is create a new data array with values already set (likeactive
), and then callsetState
with a new datasource for yourListView
created withcloneWithRows
. If you do this, you actually don't even need theannotations
key in your state at all.Code is probably more helpful than words here:
I hope this helps! Here's a code snippet containing the complete code you posted, with my modifications. It's working for me just as you described it should on iOS using React Native 0.29. You tagged the question android-mapview, so I'm assuming you're running Android, but the platform shouldn't really make a difference in this case.