Hussain Arif Hussain is a CS student in Pakistan whose biggest interest is learning and teaching programming to make the world a better place.

Data fetching with React Native

9 min read 2759

Data Fetching with React Native

React Native is an amazing library for developers looking to build mobile apps with ease. It provides an efficient way of displaying information to the frontend. But how do we get the data so that our components can render it?

In this article, you will learn how to fetch data from an API and display it to the user. We’ll cover several approaches with comprehensive code samples to help you determine the best method for your app.

We’ll cover the following options for fetching data in React Native:

  • Using the built-in Fetch API
  • Fetching data with Apisauce
  • Using render props to render data
  • Data fetching with GraphQL and Apollo Client
  • Fetching data with class components

To show data procurement in React Native, we’ll construct a basic app that fetches a list of items from Coffee API. Moreover, we will use the NativeBase UI library for rendering our data to the client.

In the end, your example app will look like this:

Data Fetching in React Native Final Product

You can get the full source code for this application from this GitHub repository.

Getting started

Project initialization

To scaffold a React Native project with Expo, run the following terminal command:

expo init reactnative-data-fetching

Getting dependencies

Here, we will install the following modules:

  • @apollo/client : for making GraphQL queries
  • graphql: peer dependency for Apollo Client
  • native-base, styled-components, styled-system: to use the NativeBase library

To acquire these packages, write the following terminal command:

We made a custom demo for .
No really. Click here to check it out.

npm i @apollo/client graphql native-base styled-components styled-system 

As the next step, install NativeBase’s peer dependencies like so:

expo install react-native-svg
expo install react-native-safe-area-context

When that’s done, it’s time to demonstrate data fetching.

When do you need to fetch data?

There are three reasons you’d need to fetch data:

  • Loading data on the component’s first render
  • Fetching the data and rendering it when the user clicks a button
  • Loading data at separate time intervals

We will write code for each of these use cases.

Using the built-in Fetch API

The Fetch API is the most common method of retrieving data because it comes bundled with React.

Data fetching on mount

In your components folder, create a file called CoffeeAutonomous.js. There, start by writing the following code:

import React, { useState, useEffect } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Text } from "native-base";

export default function CoffeeAutonomous() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  const fetchData = async () => {
    const resp = await fetch("https://api.sampleapis.com/coffee/hot");
    const data = await resp.json();
    setData(data);
    setLoading(false);
  };

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };

Let’s dissect this code piece by piece.

In the beginning, we created two Hooks called data and loading. The data Hook will hold fetched data and loading will tell the user if the data is on its way.

Furthermore, the fetchData method will use the fetch method to get the response from the server and then store it into the data Hook. In the end, we set the loading Hook to false. Other than that, the renderItem function will display each item’s title field.

Now we need to render it. To do so, add the following code in the same file:

useEffect(() => {
  fetchData();
}, []);

return (
  <NativeBaseProvider>
    <Center flex={1}>
    <Box> Fetch API</Box>
      {loading && <Box>Loading..</Box>}
      {data && (
        <FlatList
          data={data}
          renderItem={renderItem}
          keyExtractor={(item) => item.id.toString()}
        />
      )}
    </Center>
  </NativeBaseProvider>
);
}

Notice that we left the useEffect dependency array empty. This means that React will call the fetchData method on the first render. Next, we used the FlatList component to display the contents of the data array.

Finally, go to App.js and render the CoffeeAutonomous component:

import React from "react";
import CoffeeAutonomous from "./components/CoffeeAutonomous";
export default function App() {
  return <CoffeeAutonomous />;
}

This will be the output:

Fetch API Fetching on Mount

In the next section, you will learn how to render data when the user clicks a button.
In the end, CoffeeAutonomous.js should look like this:

import React, { useState, useEffect } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Text } from "native-base";

export default function CoffeeAutonomous() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  const fetchData = async () => {
    const resp = await fetch("https://api.sampleapis.com/coffee/hot");
    const data = await resp.json();
    setData(data);
    setLoading(false);
  };

  useEffect(() => {
    fetchData();
  }, []);

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };

  return (
    <NativeBaseProvider>
      <Center flex={1}>
      <Box> Fetch API</Box>
        {loading && <Box>Loading..</Box>}
        {data && (
          <FlatList
            data={data}
            renderItem={renderItem}
            keyExtractor={(item) => item.id.toString()}
          />
        )}
      </Center>
    </NativeBaseProvider>
  );
}

Data fetching on button click

Create a file called CoffeeClick.js and write the following code:

import React, { useState } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Button } from "native-base";

export default function CoffeeClick() {

  const [data, setData] = useState(null);
  const [visible, setVisible] = useState(true);

  const fetchData = async () => {
    const resp = await fetch("https://api.sampleapis.com/coffee/hot");
    const data = await resp.json();
    setData(data);
    setVisible(false);
  };

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };
}

The first part of this code is similar to that of CoffeeAutonomous. The only difference is that we have declared a visible Hook. Other than that, in the fetchData function, we told React that if the data is now present, then set the visible Hook to false.

To render the UI, append the following code:

return (
    <NativeBaseProvider>
      <Center flex={1}>
        {visible && <Button onPress={() => fetchData()}>Press</Button>} 
        {data && (
          <FlatList
            data={data}
            renderItem={renderItem}
            keyExtractor={(item) => item.id.toString()}
          />
        )}
      </Center>
    </NativeBaseProvider>
  );
}

On line 4, we used conditional rendering. This will hide the component when clicked.

Fetch API Fetching on Button Click

In the next section, you will learn how to fetch data in regular intervals.

Our CoffeeClick.js file should look like this:

import React, { useState } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Button } from "native-base";

export default function CoffeeClick() {
  const [data, setData] = useState(null);
  const [visible, setVisible] = useState(true);

  const fetchData = async () => {
    const resp = await fetch("https://api.sampleapis.com/coffee/hot");
    const data = await resp.json();
    setData(data);
    setVisible(false);
  };

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };

  return (
    <NativeBaseProvider>
      <Center flex={1}>
        {visible && <Button onPress={() => fetchData()}>Press</Button>}
        {data && (
          <FlatList
            data={data}
            renderItem={renderItem}
            keyExtractor={(item) => item.id.toString()}
          />
        )}
      </Center>
    </NativeBaseProvider>
  );
}

Fetching data in intervals

This step is straightforward. Create a file called CoffeeInterval.js.

After that, copy the code from CoffeeAutonomous.js and paste it. We will make changes to add interval functionality.

In CoffeeInterval.js, change your useEffect handler:

useEffect(() => {
  fetchData();
  const dataInterval = setInterval(() => fetchData(), 5 * 1000);

  return () => clearInterval(dataInterval);
}, []);

In this piece of code, we used the setInterval function to run the fetchData method every 5 seconds. Later on, we specified that if this component gets deleted from the tree, then clear this interval. This will prevent memory leaks.

That’s all there is to it! Your code should run without any issues:

Fetching Data in Intervals

Your CoffeeInterval.js file should look like this:

import React, { useState, useEffect } from "react";
import { Box, FlatList, Center, NativeBaseProvider, Text } from "native-base";

export default function CoffeeInterval() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  const fetchData = async () => {
    const resp = await fetch("https://api.sampleapis.com/coffee/hot");
    const data = await resp.json();
    setData(data);
    setLoading(false);
  };

  useEffect(() => {
    fetchData();
    const dataInterval = setInterval(() => fetchData(), 5 * 1000);
    return () => clearInterval(dataInterval);
  }, []);

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };

  return (
    <NativeBaseProvider>
      <Center flex={1}>
        {loading && <Box>Loading..</Box>}
        {data && (
          <FlatList
            data={data}
            renderItem={renderItem}
            keyExtractor={(item) => item.id.toString()}
          />
        )}
      </Center>
    </NativeBaseProvider>
  );
}

Fetching data with Apisauce

One alternative to Fetch is Axios. But since Axios is not compatible with React Native, we can use Apisauce instead. It is a wrapper for Axios and can even allow you to make POST, PUT, and DELETE requests.

To install the package, run this terminal command:

npm i apisauce 

The simple way to fetch data with Apisauce

This is how you can make requests with the Apisauce library:

import React from "react";
import { useEffect } from "react";
import { create } from "apisauce";

//file name: SauceExample.js
//extra code removed for brevity purposes

//The baseURL will be our starting point.
const api = create({
  baseURL: "https://api.sampleapis.com/coffee",
});

const fetchData = () => {
  //make request to baseURL + '/hot'
  api
    .get("/hot")
    .then((response) => response.data)
    .then((data) => console.log(data));
};

useEffect(() => {
  fetchData();
}, []);

In the end, we told React to execute the fetchData function on the first render. This will log out the API’s response to the terminal.

This will be the output:

Apisauce FetchData

We will use Hooks to render this data.

Using Apisauce with Hooks

In components/SauceExample.js, write the following code:

import { FlatList, Box, NativeBaseProvider, Center } from "native-base";
import React from "react";
import { useEffect } from "react";
import { create } from "apisauce";
import { useState } from "react";

export default function SauceExample() {
  const [data, setData] = useState([]);
  const api = create({
    baseURL: "https://api.sampleapis.com/coffee",
  });

  const fetchData = () => {
    //make request to baseURL + 'hot'
    api
      .get("/hot")
      .then((response) => response.data)
      .then((data) => setData(data));
  };

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <NativeBaseProvider>
      <Center flex={1}>
      <Box> Using Apisauce </Box>
        {data && (
          <FlatList
            data={data}
            renderItem={renderItem}
            keyExtractor={(item) => item.id.toString()}
          />
        )}
      </Center>
    </NativeBaseProvider>
  );
}

This will be the output:

Using Apisauce with Hooks

If you want to get data from the iced route, all you need to do is alter a line within your fetchData function:

const fetchData = () => {
  //make request to baseURL + 'iced'
  api
    .get("/iced")
    .then((response) => response.data)
    .then((data) => setData(data));
};

Using Apisauce Iced

Using async/await with Apisauce

Want to use async and await in your code? No problem. You can write your code like this:

const fetchData = async () => {
  //make request to baseURL + 'iced'
  const response = await api.get("/iced");
  setData(response.data);
};

Using render props to render data

Props in React allow for modularity and cleanliness in our code. For example, to render data, we wrote the following:

return (
  <NativeBaseProvider>
    <Center flex={1}>
      {data && (
        <FlatList
          data={data}
          renderItem={renderItem}
          keyExtractor={(item) => item.id.toString()}
        />
      )}
    </Center>
  </NativeBaseProvider>
);

To shorten this block, you can use render props.

Create a custom component called DataRenderer.js:

import { FlatList, Box } from "native-base";
import React from "react";

export default function DataRenderer({ data }) {

  const renderItem = ({ item }) => {
    return (
      <Box px={5} py={2} rounded="md" bg="primary.300" my={2}>
        {item.title}
      </Box>
    );
  };

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item.id.toString()}
    />
  );
}

Use it in your other files like so:

 return (
    <NativeBaseProvider>
      <Center flex={1}>{data && <DataRenderer data={data} />}</Center>
    </NativeBaseProvider>
  );

Our code looks cleaner! The output should be the same as before.

Rendering Data with Props

Data fetching with GraphQL and Apollo client

Why use GraphQL?

GraphQL is a technology that is completely different from REST while still maintaining ease of use.

For example, to make a standard request to our Coffee API using REST, you would do the following:

const response = await fetch(https://api.sampleapis.com/coffee/hot)
//further code ...

This would give the following response:

Coffee API Using REST

Even though this is fine, there is a minor flaw. Our app only needs the title field. The rest of the data is unnecessary to us.

This is where GraphQL comes in. To retrieve only the title and data fields of our items, we will perform the following query:

query HotCoffees{
  allHots {
    title
    id
  }
}

This will be the response from the server:

To summarize, GraphQL only gives you the data that you require.

GraphQL sample usage

In your components folder, create a file called CoffeeGraphQL.js. Here, start by writing the following piece of code:

import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  useQuery,
  gql,
} from "@apollo/client";
import { Box, Center, NativeBaseProvider } from "native-base";
import React from "react";
import DataRenderer from "./DataRenderer";

//connect to GraphQL server:
const client = new ApolloClient({
  uri: "https://api.sampleapis.com/coffee/graphql",
  cache: new InMemoryCache(),
});

function RenderQuery() {
//define our query
  const query = gql`
    query HotCoffees {
      allHots {
        title
        id
      }
    }
  `;
  //make a query
  const { loading, error, data } = useQuery(query);
  if (error) console.log(error);

  return (
    <NativeBaseProvider>
      <Center flex={1}>
        <Box> Using GraphQL </Box>
        {loading && <Box>Loading data.. please wait</Box>}
        {data && <DataRenderer data={data.allHots} />}
      </Center>
    </NativeBaseProvider>
  );
}

A few inferences from this code:

  • The client variable connects our application to the GraphQL server
  • Later on, we made a query to get the title and id fields
  • If an error occurs, log it out to the console
  • When data has loaded, display it to the UI

As a final step, we now have to bind our RenderQuery component to our GraphQL client.

To do so, add the following code in CoffeeGraphQL.js:

export default function CoffeeGraphQL() {
  return (
    <ApolloProvider client={client}>
      <RenderQuery />
    </ApolloProvider>
  );
}

This will be the result:

GraphQL Coffee API

Your CoffeeGraphQL.js file should look like this:

import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  useQuery,
  gql,
} from "@apollo/client";
import { Box, Center, NativeBaseProvider } from "native-base";
import React from "react";
import DataRenderer from "./DataRenderer";

//connect to GraphQL server:
const client = new ApolloClient({
  uri: "https://api.sampleapis.com/coffee/graphql",
  cache: new InMemoryCache(),
});CoffeeClass

function RenderQuery() {
  const query = gql`
    query HotCoffees {
      allHots {
        title
        id
      }
    }
  `;
  //make a query
  const { loading, error, data } = useQuery(query);
  if (error) console.log(error);
  return (
    <NativeBaseProvider>
      <Center flex={1}>
        <Box>Using GraphQL</Box>
        {loading && <Box>Loading data.. please wait</Box>}
        {data && <DataRenderer data={data.allHots} />}
      </Center>
    </NativeBaseProvider>
  );
}

export default function CoffeeGraphQL() {
  return (
    <ApolloProvider client={client}>
      <RenderQuery />
    </ApolloProvider>
  );
}

Fetching data with class components

While modern React favors the use of functional components, the option to build your app with class components is still present. This is useful in maintaining legacy React Native code.

Create a class called CoffeeClass.js and write the following block:

import { Center, NativeBaseProvider, Box } from "native-base";
import React, { Component } from "react";
import DataRenderer from "./DataRenderer";

export default class CoffeeClass extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [],
    };
  }
  componentDidMount() {
    this.fetchUsersAsync();
  }

  fetchUsersAsync() {
    const URL = "https://api.sampleapis.com/coffee/hot";
    fetch(URL)
      .then((response) => response.json())
      .then((list) => this.setState({ data: list }));
  }
  render() {
    return (
      <NativeBaseProvider>
        <Center flex={1}>
          <Box>Using class component</Box>
          {this.state.data && <DataRenderer data={this.state.data} />}
        </Center>
      </NativeBaseProvider>
    );
  }
}

In this code, we told React to execute fetchCoffee on the first render (componentDidMount). This will fetch data from the API and store its response into the data state variable. In the end, we rendered the data array.

Run the code. This will be the output:

Class Component Coffee API

Some people might think that it is better to fetch data in the componentWillMount function instead, which is executed right before the component is mounted. There are two justifications why you shouldn’t do it:

  • It’s deprecated as of React v17
  • When you use the Fetch API in componentWillMount(), React Native will render your data without waiting for the first render. This will cause a blank screen for the first time. For this reason, the time saved won’t be substantial

Conclusion

In this article, we explored a handful of common strategies to fetch data in React Native. At this moment, I would opt for using Apisauce and Hooks for my project. Not only are they an absolute breeze to use, but they are also robust. Consequently, this brings app security and efficiency to the table.

Thank you so much for reading! Happy coding!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Hussain Arif Hussain is a CS student in Pakistan whose biggest interest is learning and teaching programming to make the world a better place.

One Reply to “Data fetching with React Native”

  1. Awesome Guide, but are you sure Axios is not compatible with react native? Last I used it, it was?

Leave a Reply