web-dev-qa-db-fra.com

Vous avez tenté de définir la clé sur un objet qui est censé être immuable et a été gelé

Dans l'exemple suivant:

  • MapView affiche les éléments d'un ListView sous la forme d'annotations
  • Cliquer sur sur un élément ListView devrait avoir pour résultat de le peindre en bleu Couleur.
  • Bonus si les MapView et ListView utilisent efficacement l'objet state

La modification de DataSource de ListView semble provoquer le conflit lorsque l'attribut active est modifié:

Vous avez tenté de définir la clé "active" avec la valeur "false" sur un objet qui est censé être immuable et a été gelé.

enter image description here

Quelle est la bonne façon de définir l'État?

Exemple RNPlay

'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);
15
Peter G.

Le problème

Lorsque vous définissez field.active = true; ou this.previousField.active = false;, vous modifiez un objet (field) qui est présent dans la source de données de votre ListView. ListView renvoie l'erreur car il fige sa source de données lorsque vous la créez à l'aide de cloneWithRows. Ceci permet de garantir que la source de données ne peut pas être modifiée en dehors du cycle de vie normal du composant React (comme setState). Au lieu de cela, ListView.DataSource les objets sont conçus pour être modifiés avec cloneWithRows, qui renvoie une copie de la source de données existante.

Si vous connaissez la bibliothèque Redux , elle est très similaire à la philosophie selon laquelle les fonctions de réduction retournent un copie de l'état, plutôt que de modifier l'état existant.

Clonage de la source de données

Pour résoudre ce problème, au lieu de muter les objets field dans votre fonction handleClick, ce que vous voulez vraiment faire, c'est créer un nouveau tableau de données avec des valeurs déjà définies (comme active) , puis appelez setState avec une nouvelle source de données pour votre ListView créée avec cloneWithRows. Si vous faites cela, vous n'avez même pas du tout besoin de la clé annotations dans votre état.

Le code est probablement plus utile que les mots ici:

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),
  });
}

J'espère que ça aide! Voici un extrait de code contenant le code complet que vous avez publié, avec mes modifications. Cela fonctionne pour moi comme vous l'avez décrit sur iOS en utilisant React Native 0.29. Vous avez marqué la question Android-mapview, donc je suppose que vous utilisez Android, mais la plate-forme ne devrait pas '' t vraiment faire une différence dans ce cas.

'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);
22
Michael Helvey