reactjsreact-nativepromiseasync-awaitreact-native-contacts

Unhandled rejection on only first render - React Native


I'm attempting to build an app that imports contacts using react-native-contacts and displays that in a list.

My issue is that I'm getting an unhandled promise rejection only on the first render. Here's my code for the component, with the error happening when I log to console in the effect:

function ContactsList() {
  const [contacts, setContacts] = useState({});

  // I think this function is fine, but including it for context because it might not be
  function sortData(data) {
    const dataSorted = {
      'A': [],
      'B': [],
      'C': [],
      \\etc...
    };
    data.sort((a, b) => (a['name'] > b['name']) ? 1 : -1);
    for (let x in data) {
      const first = data[x]['name'][0];
      var letters = /^[A-Za-z]+$/;
      if (first.match(letters)) {
        dataSorted[first].push(data[x]);
      } else {
        dataSorted['#'].push(data[x]);
      }
    }
    setContacts(dataSorted);
  }

  useEffect(() => {
    (async () => {
      const { status } = await Contacts.requestPermissionsAsync();
      console.log('permission ' + status);
      if (status === 'granted') {
        const { data } = await Contacts.getContactsAsync().catch((error) => {console.error(error)});
        if (data.length > 0) {
          sortData(await data);
          console.log(contacts['A'][0]); // THIS IS THE LINE THAT THROWS AN ERROR
        }
      } else {
        Alert.alert(
          "Contacts Permission",
          "Sorry, we can't load your contacts because permission was not granted.",
          [{ text: "OK", onPress: () => console.log("OK Pressed") }],
          { cancelable: false }
        );
      }
    })();
  }, []);

  return (
    <View>
      <Text> These are contacts: </Text>
      {contacts['A'] && // I'm using && to stop it from rendering the list before the data gotten
      <View/> // I'll add my section list here
      }
    </View>
  );
}

When I reload my app, I get this error:

[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'contacts['A'][0]')]

I thought the "if (data.length > 0)" part would stop it from trying if data is empty as shown in this documentation: https://docs.expo.io/versions/latest/sdk/contacts/

If I do a hot reload, it works fine and I don't get any warnings. The unhandled promise rejection only happens on the first render.

I've read up on more examples and gone through similar questions, but I still can't find what I'm missing. I'm new to react native, so any help would be appreciated. I could certainly be misunderstanding something that should be obvious. Thanks!


Solution

  • The issue is that when you do

    setContacts(dataSorted)
    

    that won't be immediately available in the state - it works like setState did in that

    Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

    https://reactjs.org/docs/react-component.html#setstate

    What you could do is just return dataSorted and then log what the state will be:

    function sortData(data) {
        // ...
        setContacts(dataSorted);
        return dataSorted;
    }
    
    // ......
    if (data.length > 0) {
        const nextContacts = sortData(await data);
        console.log(nextContacts['A'][0]); 
    }