我该如何解决:警告:遇到两个具有相同密钥`%s`

时间:2019-04-12 12:46:21

标签: react-native expo

我是本机反应的新手,这不是我编写此应用程序的人。

有人可以帮助我解决此错误吗,我认为是导致此问题的单位列表,因为仅当我加载页面或在列表中搜索内容时才会发生。我知道有关此错误的问题很多,但我找不到适合我的解决方案。

Warning: Encountered two children with the same key,%s . Keys should be unique so that components maintain their identity across updates.

ContactScreen.js

import React from 'react';
import { Button, View, FlatList, Alert, StyleSheet, KeyboardAvoidingView } from 'react-native';
import { ListItem, SearchBar } from 'react-native-elements';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Contacts } from 'expo';
import * as Api from '../rest/api';
import theme from '../styles/theme.style';
import { Contact, ContactType } from '../models/Contact';

class ContactsScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    return {
      headerTitle: "Contacts",
      headerRight: (
        <Button
          onPress={() => navigation.popToTop()}
          title="Déconnexion"
        />
      ),
    }
  };

  constructor(props) {
    super(props);

    this.state = {
      contacts: [],
      search: '',
      isFetching: false,
      display_contacts: []
    }
  }

  async componentDidMount() {
    this.getContactsAsync();
  }

  async getContactsAsync() {
    const permission = await Expo.Permissions.askAsync(Expo.Permissions.CONTACTS);
    if (permission.status !== 'granted') { return; }

    const contacts = await Contacts.getContactsAsync({
        fields: [
          Contacts.PHONE_NUMBERS,
          Contacts.EMAILS,
          Contacts.IMAGE
        ],
        pageSize: 100,
        pageOffset: 0,
    });


    const listContacts = [];
    if (contacts.total > 0) {
      for(var i in contacts.data) {
        let contact = contacts.data[i];
        let id = contact.id;
        let first_name = contact.firstName;
        let middle_name = contact.middleName;
        let last_name = contact.lastName;
        let email = "";
        if ("emails" in  contact && contact.emails.length > 0) {
          email = contact.emails[0].email;
        }
        let phone = "";
        if ("phoneNumbers" in contact && contact.phoneNumbers.length > 0) {
          phone = contact.phoneNumbers[0].number;
        }
        listContacts.push(new Contact(id, first_name, middle_name, last_name, email, phone, ContactType.UP));
      }
    }

    const soemanContacts = await Api.getContacts();
    if (soemanContacts.length > 0) {
      for(var i in soemanContacts) {
        let contact = soemanContacts[i];
        let id = contact.contact_id.toString();
        let first_name = contact.contact_first_name
        let last_name = contact.contact_last_name;
        let email = contact.contact_email;
        let phone = contact.contact_phone.toString();
        listContacts.push(new Contact(id, first_name, "", last_name, email, phone, ContactType.DOWN));
      }
    }

    listContacts.sort((a, b) => a.name.localeCompare(b.name));
    this.setState({contacts: listContacts});
    this.setState({ isFetching: false });
    this.updateSearch(null);
  }

  async addContactAsync(c) {
    const contact = {
      [Contacts.Fields.FirstName]: c.firstName,
      [Contacts.Fields.LastName]: c.lastName,
      [Contacts.Fields.phoneNumbers]: [
        {
          'number': c.phone
        },
      ], 
      [Contacts.Fields.Emails]: [
        {
          'email': c.email
        }
      ]
    }
    const contactId = await Contacts.addContactAsync(contact);
  }

  onRefresh() {
    this.setState({ isFetching: true }, function() { this.getContactsAsync() });
  }

  updateSearch = search => {
    this.setState({ search });
    if(!search) {
      this.setState({display_contacts: this.state.contacts});
    }
    else {

      const res = this.state.contacts.filter(contact => contact.name.toLowerCase().includes(search.toLowerCase()));
      console.log(res);
      this.setState({display_contacts: res});
      console.log("contact display "+ this.state.display_contacts);
    }
  };

  toggleContact(contact) {
    switch(contact.type) {
      case ContactType.SYNC:
        break;
      case ContactType.DOWN:
        this.addContactAsync(contact);
        break;
      case ContactType.UP:
        Api.addContact(contact);
        break;
    }
    /*Alert.alert(
      'Synchronisé',
      contact.name + 'est déjà synchronisé'
    );*/
  }

  renderSeparator = () => (
    <View style={{ height: 0.5, backgroundColor: 'grey', marginLeft: 0 }} />
  )

  render() {
    return (
      <View style={{ flex: 1 }}>
      <KeyboardAvoidingView style={{ justifyContent: 'flex-end' }} behavior="padding" enabled>
        <SearchBar
          platform="default"
          lightTheme={true}
          containerStyle={styles.searchBar}
          inputStyle={styles.textInput}
          placeholder="Type Here..."
          onChangeText={this.updateSearch}
          value={this.state.search}
          clearIcon
        />
        <FlatList
          data={this.state.display_contacts}
          onRefresh={() => this.onRefresh()}
          refreshing={this.state.isFetching}
          renderItem={this.renderItem}
          keyExtractor={contact => contact.id}
          ItemSeparatorComponent={this.renderSeparator}
          ListEmptyComponent={this.renderEmptyContainer()}
        />
      </KeyboardAvoidingView>
      </View>
    );
  }

  renderItem = (item) => {
    const contact = item.item;
    let icon_name = '';
    let icon_color = 'black';
    switch(contact.type) {
      case ContactType.SYNC:
        icon_name = 'ios-done-all';
        icon_color = 'green';
        break;
      case ContactType.DOWN:
        icon_name = 'ios-arrow-down';
        break;
      case ContactType.UP:
        icon_name = 'ios-arrow-up';
        break;
    }
    return (
      <ListItem
        onPress={ () => this.toggleContact(contact) }
        roundAvatar
        title={contact.name}
        subtitle={contact.phone}
        //avatar={{ uri: item.avatar }}
        containerStyle={{ borderBottomWidth: 0 }}
        rightIcon={<Ionicons name={icon_name} size={20} color={icon_color}/>}
      />
    );
  }

  renderEmptyContainer() {
    return (
      <View>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  searchBar: {
    backgroundColor: theme.PRIMARY_COLOR
  },
  textInput: {
    backgroundColor: theme.PRIMARY_COLOR,
    color: 'white'
  }
});

export default ContactsScreen;

我在此应用程序中使用react-native和expo。

6 个答案:

答案 0 :(得分:2)

只需在您的待办事项列表中这样做

keyExtractor={(item, index) => String(index)}

答案 1 :(得分:2)

只需在您的单位列表中完成

 keyExtractor={(id) => { id.toString(); }}

答案 2 :(得分:2)

我认为您的一些contact.id是相同的。因此您可以收到此警告。如果在FlatList中设置列表的索引号,则无法显示。

keyExtractor={(contact, index) => String(index)}

答案 3 :(得分:2)

不要动态使用索引来构建密钥。如果要构建密钥,则应尽可能在渲染之前进行。

如果您的联系人具有保证的唯一ID,则应使用该ID。如果没有,则应使用产生唯一键的函数在数据进入视图之前构建键

示例代码:

  // Math.random should be unique because of its seeding algorithm.
  // Convert it to base 36 (numbers + letters), and grab the first 9 characters
  // after the decimal.
  const keyGenerator = () => '_' + Math.random().toString(36).substr(2, 9)
  // in component
  key={contact.key}

答案 4 :(得分:0)

您可以尝试以下方法:

listKey={(item, index) => 'index' + index.toString()}

答案 5 :(得分:0)

我遇到了同样的错误,并且在这种情况下已解决:

不要以这种方式编码(使用async)-每个项目都会重复渲染很多次(我不知道为什么)

Stub_Func = async () => {
  const status = await Ask_Permission(...);
  if(status) {
    const result = await Get_Result(...);
    this.setState({data: result});
  }
}
componentDidMount() {
  this.Stub_Func();
}

尝试类似的操作(使用then):

Stub_Func = () => {
  Ask_Permission(...).then(status=> {
    if(status) {
      Get_Result(...).then(result=> {
        this.setState({data:result});
      }).catch(err => {
        throw(err);
      });
    }
  }).catch(err => {
    throw(err)
  });
}
componentDidMount() {
  this.Stub_Func();
}