Typescript React - Fetch array data using Axios

Share

This is a quick post to help detail how you can fetch data from an API without Typescript shouting at you! It explains how to fetch data using Axios and how to set it to variables with defined Types as required by Typescript.

For this example, our app will be made with Typescript and React. We’ll use Axios to retrieve our data rather than the standard fetch API that comes with Javascript. The data that we want to display will be held within an array – we’ll want to access various elements within this array. While Axios will ultimately be used to fetch external data, for our purposes, we’ve just set it up to read a file from within our repo.

If you’d like to follow along from scratch, the instructions are provided in the collapsible section below; otherwise, download the final repo here.

npx create-react-app axios-example --template typescript

or:

yarn create react-app axios-example --template typescript

NB: npx uses ‘create-react-app’ with 2 hyphens, but yarn uses ‘create react-app’ with only 1 hyphen between react and app. That was annoying…

  • Move into your new app:
cd axios-example
npm install axios

or:

yarn add axios

The Problem

Initial attempts at retrieving an array from a data source resulted in the following error:

Element implicitly has an ‘any’ type because expression of type ‘number’ can’t be used to index type ‘Promise<DailyPrice[]>’.

Ultimately, I got myself in a bit of a tangle with how to handle Promises in Typescript and then accessing an element within an array that should be returned.

The Solution

We’ll render the data in the App.tsx file for convenience. To handle the Axios call, we’ll make a couple of files:

  1. fetchers.tsx file within a ‘fetchers’ folder that will house our Axios call.
  2. use-queries.tsx custom hook within a ‘hooks’ folder that will be called from App.tsx. This will utilize the fetchers function noted above.

We’ll also need some data to work with. For this example, I’ve used the ‘endofdayquotes/history’ endpoint from the MorningStar API held on RapidAPI.com. The data below is a snippet from their test page. If you want to follow along, make a folder in the ‘/public’ directory and create a file named DUMMY_DATA.json. Copy and paste the data into that file:

{ “total”: 2518, “offset”: 0, “results”: [ { “date”: “2010-02-01”, “open”: “28.4”, “high”: “28.48”, “low”: “27.92”, “last”: “28.41”, “volume”: 85931096 }, { “date”: “2010-02-02”, “open”: “28.37”, “high”: “28.5”, “low”: “28.14”, “last”: “28.46”, “volume”: 54413656 }, { “date”: “2010-02-03”, “open”: “28.22”, “high”: “28.79”, “low”: “28.12”, “last”: “28.63”, “volume”: 61397848 }, { “date”: “2010-02-04”, “open”: “28.36”, “high”: “28.5”, “low”: “27.81”, “last”: “27.84”, “volume”: 77849968 } ], “responseStatus”: null }

Given the above data set we’ll also need some Types. ‘DailyPrices’ is of the same format that comes out of the API. We’ve made another Type called DailyPrice, which is an array of daily price details (a subset from DailyPrices). We’ve also included an ‘APIResponse’ type, which is a destructured form of our DailyPrices Type. Make a new file named types.tsx under a new folder named ‘types’:

export interface DailyPrices { total: number, offset: number, results: DailyPrice[], responseStatus: string | null } export interface DailyPrice { date: string, open: number, high: number, low: number, last: number, volume: number } export interface APIResponse { results: DailyPrice[] }

Fetchers.tsx

We’ll use the following code in our fetchers file:

import axios from 'axios';
export async function get<T>(
path: string
): Promise<T> {
const { data } = await axios.get(path);
return data;
};

This is a helper function that makes fetching data a little easier and reusable. The get function takes a path variable that is the location of our data. We can also pass in the Type of the data that we expect to get back. This is shown with the generic <T> that is passed to get and shows up in the Promise that gets returned from the function. By writing the code in this way, we can reuse the get function for other data types.

Data provided back from axios will come in the format of {data: ...}. Therefore, we destructure our response with const { data } = ....

As the process is asynchronous, we need to label the function with async and put await before our axios call so that the application can know to wait for the results to come back. Finally, we return the data.

use-queries.tsx

Our use-queries file will call the axios get function noted above – it has the following code:

import { useState, useEffect } from 'react';
import { DailyPrice, APIResponse } from '../types/types';
import { get } from '../fetchers/fetchers';
export const useGetDailyPrices = () => {
const [data, setData] = useState<DailyPrice[]>([]);
const getData = async () => {
const { results } = await get<APIResponse>('./data/DUMMY_DATA.json');
setData(results)
}
useEffect(() => {
getData()
}, [])
return data;
}

First, we define an element in state that will hold our data. We initialize this as an empty array of ‘DailyPrice[]’ type. This is what we want to pass back to the app - an array of DailyPrice data.

We create an asynchronous function called getData that awaits the get helper function we made above. We provide it with the path to our data and also give a type that we expect to receive. In this instance, it’s an ‘APIResponse’ type that provides an array of daily prices linked to a key of ‘results’. Then, we set the internal state of that component to the value of results.

Without async/await for this function, you may see the following error:

Property ‘results’ does not exist on type ‘Promise<APIResponse>’.

This is because we’re passing back a Promise, rather than the data type that we want to work with, and setting the type of results to any. Here, Async/Await ensures that we wait for the Promise to resolve and set the value of the variable to our desired data type.

The getData function is called within useEffect. This replaces the previously used componentDidMount lifecycle method (among others) that was superseded in React v16.8. useEffect gets called in various stages of the component lifecycle and so, as noted in Chidume Nnamdi’s post, we need to pass in a dependency array to stop the method from being called continuously. This is where the empty array on line 15 comes into play.

So, to summarize, this component fetches the data, sets the return value to its internal state, and then passes that to the where the function was called from. Now, to change App.tsx…

App.tsx

We’ve deleted everything between the <header> elements that come as default within create-react-app. So, all we’re going to do is have two sections that detail the closing price from the first and last entries in the data.

import React from 'react';
import './App.css';
import { useGetDailyPrices } from './hooks/use-queries';
function App() {
const data = useGetDailyPrices()
if (data.length === 0) return null;
return (
<div className="App">
<header className="App-header">
<h2>Stock Prices</h2>
<div className="row">
<p>First</p>
<p>{data[0].last}</p>
</div>
<div className="row">
<p>Last</p>
<p>{data[data.length - 1].last}</p>
</div>
</header>
</div>
);
}
export default App;

In the code above, we’ve created a variable named data that calls our useGetDailyPrices hook (defined above). Since we initially defined the value of our data as an empty array in the hook, we need to include a conditional so that we don’t render anything until we get data back. Otherwise, the app will fall over when we call data[0], and we’ll receive the following error:

TypeError: Cannot read property ‘last’ of undefined

There’s nothing much else to this component. We place the conditional to return nothing if we don’t have any data set; otherwise, we can access the array elements within the data variable. As we’ve set the async/await functionality to our hook, the data in App.tsx is now of a DailyPrice[] type rather than a Promise, which means we avoid the error noted at the start of this post.

For completeness, here’s the final outcome (with some minor CSS involved!):

Rendered output
Rendered output

I hope this helps in getting data from Axios within a Typescript based React app. If you have any questions or suggestions then please let me know and I can address/incorporate them.


If you enjoyed reading this post, please consider reading some of my other React posts:

React - How to convert to Hooks - State

React - How to convert to Hooks - Props


If you enjoyed this type of content and would like to see more, feel free to check out my social media accounts:

Twitter: @sdrobertsCode
LinkedIn
GitHub