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.
Following the instructions from
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
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.
We’ll render the data in the App.tsx
file for convenience. To handle the Axios call, we’ll make a couple of files:
fetchers.tsx
file within a ‘fetchers’ folder that will house our Axios call.
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[] }
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.
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…
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!):
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: