You attempted to set the key on an object that is meant to be immutable and has been frozen

后端 未结 1 1029
深忆病人
深忆病人 2021-02-18 18:00

In the following example:

  • MapView displays elements of a ListView as annotations
  • Clicking on a
相关标签:
1条回答
  • 2021-02-18 18:46

    The Problem

    When you set field.active = true; or this.previousField.active = false;, you're modifying an object (field) that is present in the datasource of your ListView. The ListView throws the error because it freezes its datasource when you create it using cloneWithRows. This is to ensure that the datasource can't be modified outside of the normal React component lifecycle (like setState). Instead, ListView.DataSource objects are designed to be changed with cloneWithRows, 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 your handleClick function, what you really want to do is create a new data array with values already set (like active), and then call setState with a new datasource for your ListView created with cloneWithRows. If you do this, you actually don't even need the annotations key in your state at all.

    Code is probably more helpful than words here:

    handleClick(field) {
    
      //iterate over annotations, and update them.
      //I'm taking 'title' as a unique id property for each annotation, 
      //for the sake of the example.
      const newAnnotations = annotations.map(a => {
        //make a copy of the annotation.  Otherwise you'll be modifying
        //an object that's in your listView's datasource,
        //and therefore frozen.
        let copyA = {...a};
        if (copyA.title === field.title) {
          copyA.active = true;
        } else {
          copyA.active = false;
        }
        return copyA;
      });
    
      this.setState({
        region: {...field, active: true},
        dataSource: this.state.dataSource.cloneWithRows(newAnnotations),
      });
    }
    

    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.

    '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],
          dataSource: ds.cloneWithRows(annotations)
        };
      }
    
      handleClick(field) {
    
        //iterate over annotations, and update them.
        //I'm taking 'title' as a unique id property for each annotation, 
        //for the sake of the example.
        const newAnnotations = annotations.map(a => {
          //make a copy of the annotation.  Otherwise you'll be modifying
          //an object that's in your listView's datasource,
          //and therefore frozen.
          let copyA = {...a};
          if (copyA.title === field.title) {
            copyA.active = true;
          } else {
            copyA.active = false;
          }
          return copyA;
        });
    
        this.setState({
          region: {...field, active: true},
          dataSource: this.state.dataSource.cloneWithRows(newAnnotations),
        });
      }
    
      renderField(field) {
        console.log(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);

    0 讨论(0)
提交回复
热议问题