NothingtoSay
NothingtoSay

Reputation: 159

React Native fetch API response not displaying

I am creating an app using expo. You can check the snack here
I am also giving the code here:

    import React, {Component} from 'react';
import { ActivityIndicator, Text, View, StyleSheet, FlatList, Alert, TouchableOpacity } from 'react-native';
import {Avatar, Card, Button, Divider, ListItem, Image} from 'react-native-elements';
import Icon from 'react-native-vector-icons/FontAwesome';
import Constants from 'expo-constants';
import HTML from 'react-native-render-html';
import UserAvatar from 'react-native-user-avatar';
import { StackNavigator } from 'react-navigation';
import { createAppContainer} from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

class HomeScreen extends React.Component{
  static navigationOptions =
   {
      title: '',

   };
  constructor(props){
    super(props);
    this.state = {
      Loading : true,
      data : []

    }
  }
  fetchLeash(){
     fetch('https://lishup.com/app/')
      .then((response) => response.json())
      .then((responseJson) => {
        this.setState({ data: responseJson, Loading:false });
      }).catch((error) => {
        Alert.alert('error!');
      });
  }
fetchImage(getimg){
  fetch('https://lishup.com/app/fetch-image.php', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
   image: getimg
  })
}).then((response) => response.json())
      .then((responseJson) => {
      return (<Text>responseJson.url</Text>);
      }).catch((error) => {
        Alert.alert('error');
      });
}

  componentDidMount(){
    this.fetchLeash();
}

renderLeash = ({ item }) => (
    <View>
      <Card style={{ height:100, justifyContent: 'center', alignItems: 'center' }}>
      <ListItem
  leftAvatar={{
    title: item.user,
    source: { uri: item.userpic },
  }}
  title={item.user}
   subtitle={item.time} 
  chevron
/>  

      <Divider style={{margin:5, backgroundColor:'white'}} />
        <HTML html={item.text} />
        {this.fetchImage(item.images)}

      </Card>
    </View>
  )
   render(){
     if(this.state.Loading == true){
        return(
          <ActivityIndicator size="large" style={{marginTop:100}} color="#0000ff" />
        );
       }else{
         return(
       <View>
        <FlatList style={{width:400}}
        data={this.state.data}
        renderItem={this.renderLeash} />
       </View>
     );
       }

   }

}

const styles = StyleSheet.create({

});

const RootStack = createStackNavigator(
{
Home: { screen: HomeScreen },
},
{
initialRouteName: 'Home',
}
);

export default createAppContainer(RootStack);

If you run the snack in your device, you will see that the posts(fetchLeash() function) is working fine. But the fetchImage() is returning nothing.

My fetch-image.php file is here:

<?php
// Importing DBConfig.php file.
include 'DB.php';
 header('Content-Type: application/json');
// Creating connection.
 $con = mysqli_connect($HostName,$HostUser,$HostPass,$DatabaseName);

 // Getting the received JSON into $json variable.
 $json = file_get_contents('php://input');

 // decoding the received JSON and store into $obj variable.
 $obj = json_decode($json,true);

// Populate User email from JSON $obj array and store into $email.
$image = $obj['image'];
if($image == "") {
$blank[] = array("url"=>"");
echo json_encode($blank);
}else{

//query to get image url with the code received
$Sql_Query = "SELECT * FROM `leash_img` WHERE `pid`= '".$image."' ";

// Executing SQL Query.
$check = mysqli_query($con,$Sql_Query);


if($check){
while($row=mysqli_fetch_assoc($check)){

 $SuccessLoginMsg[] = array("url"=> $row['image']);

}
 // Converting the message into JSON format.
$SuccessLoginJson = json_encode($SuccessLoginMsg);


echo $SuccessLoginJson;
 }
}

?>

This returns like the following:

[{"url":"link here"}]

The PHP file is working fine. But the react native fetchImage() is not working.

I am totally new to react native. So forgive my problems.
I am just out of my ideas. Please help me.

Upvotes: 2

Views: 4059

Answers (1)

Drew Reese
Drew Reese

Reputation: 202575

You can't asynchronously render UI from the render function, you need to fetch the data outside it in one of the lifecycle functions and conditionally render UI while it is being fetched.

Once the data has been fetched you should go ahead and fetch the image urls. Use Promise.all and map each response item to a fetch request. This will allow all image url fetches to resolve asynchronously and maintain index order.

fetchLeash() {
  fetch('https://lishup.com/app/')
    .then((response) => response.json())
    .then((responseJson) => {
      this.setState({ data: responseJson });

      Promise.all(responseJson.map(({ images }) => this.fetchImage(images)))
        .then((images) => {
          this.setState({ imageUrls: images.map(url => ({ uri: url })) })
        });
    })
    .catch((error) => {
      Alert.alert('error!');
    })
    .finally(() => {
      this.setState({ Loading: false });
    });
}

The other important change is that the image response is an array of length 1, so need to access correctly.

fetchImage(image) {
  return fetch('https://lishup.com/app/fetch-image.php', {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ image }),
  })
    .then((response) => response.json())
    .then((responseJson) => responseJson[0].url);
}

Now you can conditionally render an Image if the url at that index exists.

renderLeash = ({ item, index }) => (
  <View>
    <Card
      style={{ height: 100, justifyContent: 'center', alignItems: 'center' }}>
      <ListItem
        leftAvatar={{
          title: item.user,
          source: { uri: item.userpic },
        }}
        title={item.user}
        subtitle={item.time}
        chevron
      />

      <Divider style={{ margin: 5, backgroundColor: 'white' }} />
      <HTML html={item.text} />
      <Text>
        {this.state.imageUrls[index] && this.state.imageUrls[index].uri}
      </Text>
      {this.state.imageUrls[index] && (
        <Image
          source={this.state.imageUrls[index]}
          style={{ width: 100, height: 100 }}
          PlaceholderContent={<ActivityIndicator />}
        />
      )}
    </Card>
  </View>
);

Expo Snack

EDIT Allow display of all fetched image URLs. Instead of grabbing and returning just the first URL, return an array of URLs. Below I mapped the URLs to a new array before returning them, and these can be set directly in state now. Update the render function to use an additional guard (array length check) and render null if array doesn't exist. (Could also use another FlatList here if you wanted to)

fetchLeash() {
  return fetch('https://lishup.com/app/')
    .then((response) => response.json())
    .then((responseJson) => {
      this.setState({ data: responseJson });

      Promise.all(
        responseJson.map(({ images }) => this.fetchImage(images))
      ).then((imageUrls) => this.setState({ imageUrls }));
    })
    .catch((error) => {
      Alert.alert('error!');
    })
    .finally(() => {
      this.setState({ Loading: false });
    });
}

fetchImage(image) {
  return fetch('https://lishup.com/app/fetch-image.php', {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ image }),
  })
    .then((response) => response.json())
    .then((responseJson) =>
      // Filter elements with empty string URLs, then app just the URL
      responseJson.filter(({ url }) => url).map(({ url }) => url)
    );
}

...

{this.state.imageUrls[index] && this.state.imageUrls[index].length
  ? this.state.imageUrls[index].map((uri) => (
      <Image
        source={{ uri }}
        style={{ width: 100, height: 100 }}
        PlaceholderContent={<ActivityIndicator />}
      />
    ))
  : null}

Upvotes: 1

Related Questions