php - How do I loop through a JSON array with React Native?

I've recently started on a React Native project and have been struggling with mapping JSON objects A LOT. I've looked through a lot of posts but have received the same error each time.

Below is a few snippets of the code I'm working with on mapping the JSON

  getUserNotifications = () => {
    var uname = this.state.userName;
    fetch('https://examp.le/endpoint?getNotifications&username=' + uname) // api url
    .then((response) => response.json())
    .then((responseJson) => {
          this.setState({notifications: responseJson});
          console.log("Notification response:" + this.state.notifications);
        //   alert(responseJson);
          }).catch((error) => {
            console.error(error);
          });
  }

Which returns something like the JSON snippet below

[
   {
      "data":{
         "from":"user",
         "time":"13 hours ago",
         "fromPfp":"users/287/avatar/application_1588872189.jpg",
         "type":"commentLike"
      }
   },
   {
      "data":{
         "from":"user",
         "time":"13 hours ago",
         "fromPfp":"users/287/avatar/application_1588872189.jpg",
         "type":"comment_mention"
      }
   }
]

Here's my mapping code

        {this.state.notifications.data.map(data => (
                <View style= {{ display: "flex", flex: 1, flexDirection: "row", backgroundColor: "#2a2a2a", padding: 15 }}>
                    <Text style={{ color: "#fff", fontSize: 12, display: "flex", flexDirection: "row", marginTop: 6, marginLeft: 8 }}>{data.from} liked your post</Text>
                </View>
                <Text style={{ color: "#ddd", fontSize: 9, display: "flex", flexDirection: "column", marginLeft: 18, marginBottom: 20 }}>{data.time}</Text>
            </View>  
        ))}

I keep receiving TypeError: undefined is not an object (evaluating 'this.state.notifications.data.map') as my error.

-- snippets --

Notification response in getUserNotifications displays this in console:

Notification response:[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]

JSON.stringify in render:

json after the render is finished: [
   {
      "from":"user",
      "time":"2 hours ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"like"
   },
   {
      "from":"user",
      "time":"2 hours ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"like"
   },
   {
      "from":"user",
      "time":"20 hours ago",
      "fromPfp":"users/287/avatar/application_1588872189.jpg",
      "type":"commentLike"
   },
   {
      "from":"user",
      "time":"20 hours ago",
      "fromPfp":"users/287/avatar/application_1588872189.jpg",
      "type":"commentLike"
   },
   {
      "from":"user",
      "time":"20 hours ago",
      "fromPfp":"users/287/avatar/application_1588872189.jpg",
      "type":"comment_mention"
   },
   {
      "from":"user",
      "time":"20 hours ago",
      "fromPfp":"users/291/avatar/application_1593241576.jpg",
      "type":"comment_mention"
   },
   {
      "from":"user",
      "time":"21 hours ago",
      "fromPfp":"users/271/avatar/application_1592193853.jpg",
      "type":"like"
   },
   {
      "from":"user",
      "time":"21 hours ago",
      "fromPfp":"users/271/avatar/application_1592193853.jpg",
      "type":"like"
   },
   {
      "from":"user",
      "time":"21 hours ago",
      "fromPfp":"users/271/avatar/application_1592193853.jpg",
      "type":"like"
   },
   {
      "from":"user",
      "time":"21 hours ago",
      "fromPfp":"users/271/avatar/application_1592193853.jpg",
      "type":"like"
   },
   {
      "from":"user",
      "time":"21 hours ago",
      "fromPfp":"users/271/avatar/application_1592193853.jpg",
      "type":"post"
   },
   {
      "from":"user",
      "time":"1 day ago",
      "fromPfp":"users/291/avatar/application_1593241576.jpg",
      "type":"comment_mention"
   },
   {
      "from":"user",
      "time":"1 day ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"comment_mention"
   },
   {
      "from":"user",
      "time":"1 day ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"like"
   },
   {
      "from":"user",
      "time":"1 day ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"like"
   },
   {
      "from":"user",
      "time":"2 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"post"
   },
   {
      "from":"user",
      "time":"2 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"like"
   },
   {
      "from":"user",
      "time":"2 days ago",
      "fromPfp":"users/287/avatar/application_1588872189.jpg",
      "type":"comment_mention"
   },
   {
      "from":"user",
      "time":"2 days ago",
      "fromPfp":"users/287/avatar/application_1588872189.jpg",
      "type":"commentLike"
   },
   {
      "from":"user",
      "time":"3 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"like"
   },
   {
      "from":"user",
      "time":"4 days ago",
      "fromPfp":"users/287/avatar/application_1588872189.jpg",
      "type":"like"
   },
   {
      "from":"user",
      "time":"4 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"post"
   },
   {
      "from":"user",
      "time":"5 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"post"
   },
   {
      "from":"user",
      "time":"6 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"post"
   },
   {
      "from":"user",
      "time":"6 days ago",
      "fromPfp":"users/270/avatar/application_1588987915.png",
      "type":"post"
   }
]

Errors

Warning: Each child in a list should have a unique "key" prop.%s%s See LINK for more information.%s, 

Check the render method of `NotifsScreen`., , 
    in Unknown (at NotifsScreen.js:201)

The following error repeats itself 25 times:

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s%s, undefined,  You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports

Check your code at NotifsScreen.js:201.

Line 201 is the first


import React, { Component, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ActivityIndicator, Image, ScrollView, Text, View, TouchableOpacity, TextInput, RefreshControl, Fragment } from 'react-native';
import { NavigationEvents, useTheme } from 'react-navigation';
import { gStyle, colors } from '../constants';

// import { userInfo } from './HomeScreen';
import AsyncStorage from '@react-native-community/async-storage'



class NotifsScreen extends Component {
  constructor(props){
    super(props);
    this.state = {
      userName: '',
      firstName: '',
      lastName: '',
      bio: '',
      followers: '',
      following: '',
      postcount: '',
      avatar: '',
      verified: '',
      response: '',
      notifications: ''
    };
  }

  
  getUserNotifications = () => {
    var uname = this.state.userName;
    var uname = uname.replace(/\"/g, "")
    fetch('https://examp.le/endpoint?getNotifications&username=' + uname) // api url
    .then((response) => response.json())
    .then((responseJson) => {
          let newArr = [];

          for (const item of responseJson) {
            newArr.push(item.data);
          }
          this.setState({notifications: newArr});
          console.log("Notification response: " + this.state.notifications);
        //   alert(responseJson);
          }).catch((error) => {
            console.error(error);
          });
  }

  // async component did mount (basically the same as document did load)
  async componentDidMount() {  
      console.log("Component mounted on notifications");
    
    try{
      let value = await AsyncStorage.getItem("userName");
      let firstname = await AsyncStorage.getItem("firstName");
      let lastname = await AsyncStorage.getItem("lastName");
      let bio = await AsyncStorage.getItem("bio");
      let followers = await AsyncStorage.getItem("followerCount");
      let following = await AsyncStorage.getItem("followingCount");
      let postcount = await AsyncStorage.getItem("postCount");
      let avatar = await AsyncStorage.getItem("avatar");
      let verified = await AsyncStorage.getItem("verified");
      if(verified !== undefined) { 
        this.setState({ userName: value, avatar: avatar, firstName: firstname, lastName: lastname, followers: followers, following: following, postcount: postcount, verified: verified, bio: bio }, function () {   });
      } else {
        this.setState({ userName: value, avatar: avatar, firstName: firstname, lastName: lastname, followers: followers, following: following, postcount: postcount, verified: "no", bio: bio }, function () {   });
      }
      console.log("USER SET: " + value + " ON NOTIFICATIONS");
      console.log("Attempting to get notifications...");
      this.getUserNotifications();
      // return value.json();
    }
  catch(e){
      console.log('caught error', e);
      // Handle exceptions
  }
  }

  refreshGetUserInfo = () =>{
    // Simple GET request using fetch
    var username = this.state.userName;
    var username = username.replace(/\"/g, "")
    fetch('https://examp.le/endpoint?username=' + username) // api url
      .then((response) => response.json())
      .then((responseJson) => {
        AsyncStorage.setItem("userName", responseJson.userName)
        AsyncStorage.setItem("firstName",responseJson.firstName)
        AsyncStorage.setItem("lastName", responseJson.lastName)
        AsyncStorage.setItem("bio", responseJson.bio)
        AsyncStorage.setItem("postCount", responseJson.postCount)
        AsyncStorage.setItem("followerCount", responseJson.followerCount)
        AsyncStorage.setItem("followingCount", responseJson.followingCount)
        AsyncStorage.setItem("verified", responseJson.verified)
        AsyncStorage.setItem("moderator", responseJson.moderator)
        AsyncStorage.setItem("avatar", responseJson.avatar)
        
        console.log("[L1] Successfully got information from profile page.");

          // the code below is a working json example
  
          // now i'm going to add the array to async storage so that i will be able to access this data
          // from other screens.


          let value = AsyncStorage.getItem("userName");
          let firstname = AsyncStorage.getItem("firstName");
          let lastname = AsyncStorage.getItem("lastName");
          let bio = AsyncStorage.getItem("bio");
          let followers = AsyncStorage.getItem("followerCount");
          let following = AsyncStorage.getItem("followingCount");
          let postcount = AsyncStorage.getItem("postCount");
          let avatar = AsyncStorage.getItem("avatar");
          let verified = AsyncStorage.getItem("verified");
          if(verified !== undefined) { 
            this.setState({ userName: value, avatar: avatar, firstName: firstname, lastName: lastname, followers: followers, following: following, postcount: postcount, verified: verified, bio: bio }, function () {   });
          } else {
            this.setState({ userName: value, avatar: avatar, firstName: firstname, lastName: lastname, followers: followers, following: following, postcount: postcount, verified: "no", bio: bio }, function () {   });
          }
          console.log("USER SET: " + value + " ON NOTIFICATIONS");
          // return value.json();
          var uname = this.state.userName;
          var uname = uname.replace(/\"/g, "")
            this.props.navigation.setParams({
              Title: uname,
              Badge: this.state.verified
          });
          this.setState({refreshing: false}); 


        })
  }

  onRefresh() {
    // get latest data
    // this.refreshGetUserInfo();
    this.setState({refreshing: false}); 
  }


  static navigationOptions = ({ navigation }) => {
        return {
          headerTitleStyle: gStyle.headerTitleStyle,
          headerTitle: (
            <View style={{ flex: 1, flexDirection: "row", alignItems: 'center', justifyContent: 'center' }}>
              <Text style={{ color: "#ffffff", fontSize: 16, fontWeight: 'bold', marginRight: 5 }}>Notifications</Text>                
            </View>
          ),
          headerStyle: {
            backgroundColor: colors.darkColor,
            color: colors.white20,
            borderBottomColor: colors.darkColor,
          }
        };

  };


  render() {
    const user = this.state.userName;
    const avatar = "https://examp.le/" + this.state.avatar;
    var fixedAvatar = avatar.replace(/\"/g, "");
    var fname = this.state.firstName;
    var fname = fname.toString().replace(/\"/g, "")
    var lname = this.state.lastName;
    var lname = lname.toString().replace(/\"/g, "")
    var uname = this.state.userName;
    var uname = uname.toString().replace(/\"/g, "")
    var bio = this.state.bio;
    var bio = bio.toString().replace(/\"/g, "")
    var json = JSON.stringify(this.state.notifications);
    console.log("json after the render is finished: " + JSON.stringify(this.state.notifications));
    if (!this.state.notifications) {
      return <View                  
      contentContainerStyle={gStyle.contentContainer}
      style={gStyle.container.dark}
      /> 
   }
  return (
    <ScrollView
      contentContainerStyle={gStyle.contentContainer}
      style={gStyle.container.dark}
      refreshControl={
        <RefreshControl refreshing={this.state.refreshing} onRefresh={this.onRefresh.bind(this)} />
      }>
      {/* <Text>{JSON.stringify(this.state.notifications)}</Text> */}

      {
  this.state.notifications.map((data) => (
    <Fragment>
      <View
        style={{
          display: "flex",
          flex: 1,
          flexDirection: "row",
          backgroundColor: "#2a2a2a",
          padding: 15,
        }}
      >
        <Text
          style={{
            color: "#fff",
            fontSize: 12,
            display: "flex",
            flexDirection: "row",
            marginTop: 6,
            marginLeft: 8,
          }}
        >
          {data.from} liked your post
        </Text>

        <Text
          style={{
            color: "#ddd",
            fontSize: 9,
            display: "flex",
            flexDirection: "column",
            marginLeft: 18,
            marginBottom: 20,
          }}
        >
          {data.time}
        </Text>
      </View>
    </Fragment>
  ))
}
    </ScrollView>
  );
};
}



export default NotifsScreen;

Answer

Solution:

Can you try this

import React, { Component, useEffect, useState, Fragment } from 'react';
..... 


{
  this.state.notifications.map((data) => (
    <Fragment>
      <View
        style={{
          display: "flex",
          flex: 1,
          flexDirection: "row",
          backgroundColor: "#2a2a2a",
          padding: 15,
        }}
      >
        <Text
          style={{
            color: "#fff",
            fontSize: 12,
            display: "flex",
            flexDirection: "row",
            marginTop: 6,
            marginLeft: 8,
          }}
        >
          {stuff.from} liked your post
        </Text>

        <Text
          style={{
            color: "#ddd",
            fontSize: 9,
            display: "flex",
            flexDirection: "column",
            marginLeft: 18,
            marginBottom: 20,
          }}
        >
          {stuff.time}
        </Text>
      </View>
    </Fragment>
  ));
}

Answer

Solution:

As the error information points out, initially the this.state.notification is undefined. You have to take into account that fetch is an async call so probably during the first render, this.state.notification is not filled with any information. You can test this adding a console.log before returning the mapping code you have written.

To make sure you only access the notifications object inside this.state when it is already filled with the API call information, you could do something like this:

if (!this.state.notifications) {
   return <View/> 
}

return (
   <EnterYourCodeHere/>
)

This way, you return an empty View if there is no notifications coming from the API call. But when the information is received, you can map the json coming from it with your existing code.

Hope this helps!

Source