Retrieving latest asset prices in your React dApp
Tags: web3, react, tutorial, javascript
Published: 2022-06-12 09:52:09

What if you need to show in your app today's `BTC/USD` price? Or convert the user's balance from `ETH` to `BTC`? Or are you building an Elon Musk fan site and want to provide the latest Tesla (`TSLA`) stock price updates?
Today we will use [Chainlink](https://docs.chain.link/) to retrieve the latest assets prices in a single call and provide it to your app components using [React Context](https://reactjs.org/docs/context.html)

## Chainlink Data Feeds
The easiest and quickest way to do that is to use the data feed provided by Chainlink. We will use the `ETH/USD` pair as an example.

First, we need to find the contract's address for our pair to call to. There is a handy [address's list](https://docs.chain.link/docs/ethereum-addresses/) for every pair supported by Chainlink.
In our case the address will be `0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419`.
Let's write a function to get the latest price. To do that we need to call a contract by its address and provide the [ABI](https://ethereum.org/en/glossary/#abi). We will use [Ethers.js](https://docs.ethers.io/v5/) library to help us with it (you could also use [Web3.js](https://web3js.readthedocs.io), it doesn't matter here).

```typescript
// getLatestPrice.ts
import { providers, Contract, BigNumber } from 'ethers'

const provider = new providers.JsonRpcProvider("https://mainnet.infura.io/v3/<infura_project_id>")
const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }]
const ETH_USD_RATE_ADDRESS = '0x9326BFA02ADD2366b30bacB125260Af641031331'
const priceFeed = new Contract(ETH_USD_RATE_ADDRESS, aggregatorV3InterfaceABI, provider)

export function getLatestPrice(): Promise<BigNumber[]> {
  const priceFeed = new Contract(ETH_USD_RATE_ADDRESS, aggregatorV3InterfaceABI, provider)
  return priceFeed.latestRoundData()
}
```

## Asset price context
In order to use the latest price data inside our app we need to create a context that will periodically update the price and provide the value to our components.

```typescript
// AssetPriceContext.ts
import { utils } from 'ethers'
import { createContext, useEffect, useRef, useState } from 'react'
import { getLatestPrice } from './getLatestPrice'

interface ContextProps {
  conversionDate: number | null;
  conversionRate: number | null;
}

const UPDATE_INTERVAL_TIMEOUT = 180000 // 3 minutes

export const DEFAULT_CONTEXT: ContextProps = {
  conversionDate: null,
  conversionRate: null,
}

export const AssetPriceContext = createContext<ContextProps>(DEFAULT_CONTEXT)

export const useAssetPrice = (): ContextProps => {
  const [state, setState] = useState<ContextState>(DEFAULT_CONTEXT)
  const updateInterval = useRef<ReturnType<typeof setTimeout>>()

  const updateAssetPrice= async () => {
    let conversionDate = null
    let conversionRate = null

    try {
      const roundData = await getLatestPrice()

      conversionDate = Number(roundData[3].toString()) * 1000
      conversionRate = Number(utils.formatUnits(roundData[1], 8))
    } catch (error) {
      console.log(error) 
    }

    setState({conversionDate, conversionRate })
  }

  const startUpdate = async () => {
    stopUpdate()

    await updateAssetPrice()

    updateInterval.current = setInterval(async () => {
      await updateAssetPrice()
    }, UPDATE_INTERVAL_TIMEOUT)
  }

  const stopUpdate = () => {
    if (updateInterval.current) {
      clearInterval(updateInterval.current)
    }
  }

  useEffect(() => {
    startUpdate()
    return stopUpdate
  }, [])

  return state
}
```

## Use in a component
Now we are ready to use the latest price, let's connect context to our app first:

```jsx
// App.tsx
import { AssetPriceContext, useAssetPrice } from './AssetPriceContext'
import { EthBalance } from './EthBalance'

export default function App() {
  const assetPrice = useAssetPrice()

  return (
    <AssetPriceContext.Provider value={assetPrice}>
      <div>
        <h1>Chainlink Data Feeds example</h1>
        <EthBalance />
      </div>
    </AssetPriceContext.Provider>
  );
}
```

And create a simple component to convert our balance from ETH to USD:
```jsx
// EthBalance.tsx
import React, { useContext } from 'react'
import { AssetPriceContext } from './AssetPriceContext'

const BALANCE_ETH = 1

export const EthBalance: React.FC = () => {
  const { conversionRate, conversionDate } = useContext(AssetPriceContext)

  const balanceUSD = conversionRate ? BALANCE_ETH * conversionRate : '...'
  const updatedAt = conversionDate
    ? new Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'medium' }).format(new Date(conversionDate))
    : '...'

  return (
    <div>
      <p>
        My balance is {BALANCE_ETH} ETH / {balanceUSD} USD
      </p>
      <p>
        Updated at {updatedAt}
      </p>
    </div>
  )
}
```

Which will result something as follows:
```
My balance is 1 ETH / 1557 USD
Updated at Saturday, 11 June 2022 at 22:29:16
```

## Conclusion
Chainlink Data Feeds are relatively simple to use, yet it is a powerful instrument to connect your dApp to real-world data. More read is available [here](https://docs.chain.link/docs/get-the-latest-price/)

Hope you enjoyed this tutorial, stay tuned for more.