1

I am trying to learn how to connect APIs in React Native. I am using a sample API: https://reactnative.dev/movies.json

This is my code:

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      dataSource: [],
    };
  }
  componentDidMount() {
    return fetch("https://reactnative.dev/movies.json")
      .then((response) => response.json())
      .then((responseJson) => {
        this.setState({
          loading: false,
          dataSource: responseJson.movies,
        });
      })
      .catch((error) => console.log(error)); //to catch the errors if any
  }
  render() {
    if (this.state.isLoading) {
      return (
        <View style={styles.container}>
          <ActivityIndicator size="large" color="#0c9" />
        </View>
      );
    } else {
      let products = this.state.dataSource.map((val, key) => {
        return (
          <View key={key} style={styles.item}>
            <Text>{val}</Text>
          </View>
        );
      });
      return (
        <View style={styles.container}>
          <Text>{products.title}</Text>
        </View>
      );
    }
  }
}

The problem occurs with my "products" variable. In debug mode, I was able to see the key and value pairs which were correct from the API. However, the products array is populated with objects rather than strings which are structured like this: Object {$$typeof: Symbol(react.element), type: "RCTView", key: "0", …}

My code returns the following error: this.state.dataSource.map is not a function

EDIT: The answer below worked for the API I was using. Now I am trying a different API structured like this:

{"prods":
    {
    "86400":{"slug":"86400","url":"/86400"},
    "23andme":{"slug":"23andme","url":"/23andme"}
}}

I am having trouble with the mapping again. This returns an error:

return dataSource.map((val, key) => (
      <View key={key} style={styles.item}>
        <Text>{val.slug}</Text>
      </View>
    ));

3 Answers 3

1

First, there is a small typo in your example. In your component's constructor you specify a loading state variable, but in your render function you're using isLoading. Second, you're not mapping over your data correctly. It just looks like you need to specify what aspects of each movie you care about in your render function. JSX can't handle displaying a full javascript object which is what <Text>{val}</Text> ends up being in your code. There are a few ways you can fix this. It's very common to just map over your results and display them directly.

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      dataSource: []
    };
  }
  
  componentDidMount() {
    return fetch("https://reactnative.dev/movies.json")
      .then(response => response.json())
      .then(responseJson => {
        this.setState({
          loading: false,
          dataSource: responseJson.movies
        });
      })
      .catch(error => console.log(error));
  }

  render() {
    const { loading, dataSource } = this.state;

    if (loading) {
      return (
        <View style={styles.container}>
          <ActivityIndicator size="large" color="#0c9" />
        </View>
      );
    }

    return dataSource.map((movie, index) => (
      <View key={movie.id} style={styles.item}>
        <Text>{movie.title}</Text>
      </View>
    ));
  }
}

You could also pull this out to a renderMovies method, which might help since you are trying to display these in a styled container.

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      dataSource: []
    };
  }
  
  componentDidMount() {
    return fetch("https://reactnative.dev/movies.json")
      .then(response => response.json())
      .then(responseJson => {
        this.setState({
          loading: false,
          dataSource: responseJson.movies
        });
      })
      .catch(error => console.log(error));
  }

  renderMovies() {
    const { dataSource } = this.state;

    return dataSource.map((movie, index) => (
      <View key={movie.id} style={styles.item}>
        <Text>{movie.title}</Text>
      </View>
    ));
  }

  render() {
    const { loading } = this.state;

    if (loading) {
      return (
        <View style={styles.container}>
          <ActivityIndicator size="large" color="#0c9" />
        </View>
      );
    }

    return (
      <View style={styles.container}>
        {this.renderMovies()}
      </View>
    );
  }
}
Sign up to request clarification or add additional context in comments.

5 Comments

@anonymous I also created a small code sandbox in case that's helpful. It's in the context of a web app rather than react native, but it shouldn't make too much of a difference in this scenario. https://codesandbox.io/s/simple-ajax-nv0ew
Thanks for your help! Could you possibly look at my edit for a follow-up question?
@anonymous sure! That response is coming back in the form of an object that is indexed by slug values. Since it's an object, the typical Array.map function won't work. Are you using lodash or any other library to provide helper functions? Or, are you trying to do this with vanilla javascript?
No, I am very new React & React Native so I'm not very familiar with all the libraries. Would you recommend using Lodash?
@anonymous lodash is a common library to add to projects, but it's all a matter of preference. I've updated my codesandbox with some code and an explanation to hopefully help out. In the sandbox link posted above, check out the util.js file. If you want to use plain JS, Object.values will be sufficient for your use case.
0

I have used Object.values() to restructure the object into an array

  componentDidMount() {
    return fetch("https://reactnative.dev/movies.json")
      .then((response) => response.json())
      .then((responseJson) => {
        this.setState({
          loading: false,
          dataSource: Object.values(responseJson.movies),       //changed this
        });
      })
      .catch((error) => console.log(error));
  }

Comments

0

Try simple way. This code uses modern React practice and helps you to brush up your React skills in general. Give a try.

import React, {useState, useEffect} from 'react';
import { Text, View, StyleSheet } from 'react-native';
import axios from 'axios'; //for fetching data 

export default function App() {
 //React Hook for state
const [ data, setData ] = useState ([]);

//React Hook Instead Of ComponentDidMount
useEffect(() => {
 const fetchData = async () => {
   const result = await axios(
     "https://reactnative.dev/movies.json",
   );

   setData(result.data.movies);
 };

 fetchData();
 }, []);

return (
 <View>
   <Text>{JSON.stringify(data)}</Text>
 </View>
);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.