How to fetch and nicely update data from  Ethereum with React and SWR
Tags: javascript, blockchain, frontend, webdev
Published: 2020-06-08 21:47:04


Ethereum allows us to build Decentralized Applications (DApps). The main difference between a typical application and a DApp is that you don't need to deploy a backend. At least as long as you take advantage of the other smart contracts deployed in the Ethereum MainNet.

Because of that, the Frontend plays a major role. It is in charge of marshalling and unmarshalling the data from the smart contracts, handling the interactions with the wallet (hardware or software) and, as usual, managing the UX. Not only that, by design, a DApp uses JSON-RPC calls and it can open a socket connection to receive updates. 

As you can see there are a few things to orchestrate but don't worry, the ecosystem has matured quite a lot in the last few months.

## Prerequisites
During this tutorial, I will assume you already have the the following: 

### A wallet connect to a Geth node to connect
 
The simplest is [installing](https://metamask.io/download.html) MetaMask so that you can use Infura infrastructure out of the box. 

### Some Ether in your account
 
When you are developing with Ethereum I strongly advise you to switch to a test net and use fake Ethers. If you need funds for testing purpose you can use a faucet e.g. [https://faucet.rinkeby.io/](https://faucet.rinkeby.io/) 

### Basic understand of React

I will guide you step by step but I will assume you know how React works (including hooks). If something seems unfamiliar consult the [React](https://reactjs.org/) documentation.

### A working React playground 
I wrote this tutorial with Typescript but just few things are typed so with minimal changes you can use it as it is in javascript as well. I used [Parcel.js](https://parceljs.org/) but feel free to use [Create React App](https://create-react-app.dev/) too or other web application bundler.

## Connect to Ethereum Mainet  

Once you have Metamask ready we are going to use [web3-react](https://github.com/NoahZinsmeister/web3-react) to handle the interaction with the network. It will give you a quite handy hook `useWeb3React` which contains many useful utilities for playing with Ethereum. 

```
yarn add @web3-react/core @web3-react/injected-connector
```

Then you need a Provider. A Provider abstracts a connection to the Ethereum blockchain, for issuing queries and sending signed state-changing transactions.
We will use `Web3Provider` from [Ether.js](https://github.com/ethers-io/ethers.js/). 

It seems already a few libraries, but when interacting with Etherum you need to translate Javascript data types to Solidity ones. And, you are also required to sign the transactions when you want to execute an action. Ether.js elegantly provide these functionalities.

```
yarn add @ethersproject/providers
```

*notice:* the above Ether.js package is the v5 currently in [beta](https://github.com/ethers-io/ethers.js/tree/ethers-v5-beta)

After that we are ready to jot down a minimal hello world to check if we have everything we need:

```typescript
import React from 'react'
import { Web3ReactProvider } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { useWeb3React } from '@web3-react/core'
import { InjectedConnector } from '@web3-react/injected-connector'

export const injectedConnector = new InjectedConnector({
  supportedChainIds: [
    1, // Mainet
    3, // Ropsten
    4, // Rinkeby
    5, // Goerli
    42, // Kovan
  ],
})

function getLibrary(provider: any): Web3Provider {
  const library = new Web3Provider(provider)
  library.pollingInterval = 12000
  return library
}

export const Wallet = () => {
  const { chainId, account, activate, active } = useWeb3React<Web3Provider>()

  const onClick = () => {
    activate(injectedConnector)
  }

  return (
    <div>
      <div>ChainId: {chainId}</div>
      <div>Account: {account}</div>
      {active ? (
        <div>✅ </div>
      ) : (
        <button type="button" onClick={onClick}>
          Connect
        </button>
      )}
    </div>
  )
}

export const App = () => {
  return (
    <Web3ReactProvider getLibrary={getLibrary}>
      <Wallet />
    </Web3ReactProvider>
  )
}

```

If you did your homework you should have something like this:
![step-1-helloworld](https://dev-to-uploads.s3.amazonaws.com/i/wqgi60hwj1iwoyxyngzy.png)
Here what we did so far: [GIT - step-1](https://github.com/aboutlo/swr-eth-tutorial/tree/step-1)

## How to fetch data from the MainNet

I will use [SWR](https://swr.now.sh/) to manage the data fetching.  
This is what I want to achieve. 

```typescript
const { data: balance } = useSWR(["getBalance", account, "latest"])
```
Quite cool :) 

Let's unveil the trick! `SWR` means Stale-While-Revalidate, an HTTP cache invalidation strategy popularized by [RFC 5861](https://tools.ietf.org/html/rfc5861).

> SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.
> SWR accepts a `key` and behind the scenes will manage to resolve

To do that SWR allows passing a `fetcher` capable of resolving the `key` by returning a promise. The hello world of SWR is based on REST API requests with a fetcher based on `fetch` API or `Axios`. 

What is brilliant about SWR is that the only requirement to create a fetcher is it must return a promise. 

So here is my first implementation of a fetcher for Ethereum:

```typescript
const fetcher = (library) => (...args) => {
  const [method, ...params] = args
  console.log(method, params)
  return library[method](...params)
}
```

As you can see, it is a `partially applied function`. In that way, I can inject the `library` ( my `Web3Provider`) when I configure the fetcher. Later, every time a `key` changes, the function can be resolved by returning the required promise. 

Now I can create my `<Balance/>` component

```typescript
export const Balance = () => {
  const { account, library } = useWeb3React<Web3Provider>()
  const { data: balance } = useSWR(['getBalance', account, 'latest'], {
    fetcher: fetcher(library),
  })
  if(!balance) {
    return <div>...</div>
  }
  return <div>Balance: {balance.toString()}</div>
}
```

The balance object returned is a `BigNumber`. 

![step-2-balance](https://dev-to-uploads.s3.amazonaws.com/i/dqtkrim7ok2d2v86ke2f.png)

As you can see, the number is not formated and extremely large. This is because Solidity uses Integer up to 256 bits. 
To display the number in a human readable format, the solution is using one of the aforementioned utilities from Ether.js utilities: `formatEther(balance)`

```
yarn install @ethersproject/units
```

Now that I can rework my `<Balance/>` component to handle and format the BitInt in a human readable form: 

```typescript
export const Balance = () => {
  const { account, library } = useWeb3React<Web3Provider>()
  const { data: balance } = useSWR(['getBalance', account, 'latest'], {
    fetcher: fetcher(library),
  })
  if(!balance) {
    return <div>...</div>
  }
  return <div>Ξ {parseFloat(formatEther(balance)).toPrecision(4)}</div>
}
```

![step-2-balance-formated](https://dev-to-uploads.s3.amazonaws.com/i/tdygx0v4f761yr5pesaq.png)

this what we did so far: [GIT step-2](https://github.com/aboutlo/swr-eth-tutorial/tree/step-2)

## How to update the data in real-time

SWR exposes a `mutate` function to update its internal cache. 

```typescript
  const { data: balance, mutate } = useSWR(['getBalance', account, 'latest'], {
    fetcher: fetcher(library),
  })

const onClick = () => {
  mutate(new BigNumber(10), false)
}

```

The `mutate` function is automatically bound to the key (e.g. `['getBalance', account, 'latest']` from which it has been generated. It accepts two parameters. The new data and if a validation should be triggered. If it should, SWR will automatically use the fetcher to update the cache 💥 

As anticipated, Solidity events give a tiny abstraction on top of the EVM’s logging functionality. Applications can subscribe and listen to these events through the RPC interface of an Ethereum client.

Ether.js has a simple API to subscribe to an event:

```typescript
const { account, library } = useWeb3React<Web3Provider>()
library.on("blockNumber", (blockNumber) => {
    console.log({blockNumber})
})
```

Now let's combine both approaches in the new `<Balance/>` component

```typescript
export const Balance = () => {
  const { account, library } = useWeb3React<Web3Provider>()
  const { data: balance, mutate } = useSWR(['getBalance', account, 'latest'], {
    fetcher: fetcher(library),
  })

  useEffect(() => {
    // listen for changes on an Ethereum address
    console.log(`listening for blocks...`)
    library.on('block', () => {
      console.log('update balance...')
      mutate(undefined, true)
    })
    // remove listener when the component is unmounted
    return () => {
      library.removeAllListeners('block')
    }
    // trigger the effect only on component mount
  }, [])

  if (!balance) {
    return <div>...</div>
  }
  return <div>Ξ {parseFloat(formatEther(balance)).toPrecision(4)}</div>
}
```

Initially, SWR will fetch the account balance, and then every time it receives a `block` event it will use `mutate` to trigger a re-fetch.

*notice:* We used `mutate(undefined, true)` because we can't retrieve from the current event the actual balance we just trigger a re-fetch of the balance.

Below is quick demo with two wallets that are exchanging some Ether.

![step-3-demo](https://dev-to-uploads.s3.amazonaws.com/i/fk8eqbxobx6jj1g5esfl.gif)

Here what we did so far: [GIT step-3](https://github.com/aboutlo/swr-eth-tutorial/tree/step-3)

## How to interact with a smart contract

So far we illustrated the basics of using SWR and how to make a basic call via a `Web3Provider`. Let's now discover how to interact with a smart contract.

Ether.js handles smart contract interaction using the Contract Application Binary Interface (ABI) ABI generated by the Solidity Compiler. 

> The Contract Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction.

For example, given the below simple smart contract:

```solidity
pragma solidity ^0.5.0;

contract Test {
  constructor() public { b = hex"12345678901234567890123456789012"; }
  event Event(uint indexed a, bytes32 b);
  event Event2(uint indexed a, bytes32 b);
  function foo(uint a) public { emit Event(a, b); }
  bytes32 b;
}
```

this is the ABI generated

```json
[
  {
    "type": "event",
    "inputs": [
      { "name": "a", "type": "uint256", "indexed": true },
      { "name": "b", "type": "bytes32", "indexed": false }
    ],
    "name": "Event"
  },
  {
    "type": "event",
    "inputs": [
      { "name": "a", "type": "uint256", "indexed": true },
      { "name": "b", "type": "bytes32", "indexed": false }
    ],
    "name": "Event2"
  },
  {
    "type": "function",
    "inputs": [{ "name": "a", "type": "uint256" }],
    "name": "foo",
    "outputs": []
  }
]
```

To use the ABIs, we can simply copy them directly into your code and import them where it is required. In this demo, we will use a standard [ERC20](https://github.com/liteflow-services/ethereum-erc20/blob/master/erc20-abi.json) ABI because we want to retrieve the balances of two tokens: DAI and MKR. 

Next step is creating the `<TokenBalance/>` component

```typescript
export const TokenBalance = ({ symbol, address, decimals }) => {
  const { account, library } = useWeb3React<Web3Provider>()
  const { data: balance, mutate } = useSWR([address, 'balanceOf', account], {
    fetcher: fetcher(library, ERC20ABI),
  })

  useEffect(() => {
    // listen for changes on an Ethereum address
    console.log(`listening for Transfer...`)
    const contract = new Contract(address, ERC20ABI, library.getSigner())
    const fromMe = contract.filters.Transfer(account, null)
    library.on(fromMe, (from, to, amount, event) => {
      console.log('Transfer|sent', { from, to, amount, event })
      mutate(undefined, true)
    })
    const toMe = contract.filters.Transfer(null, account)
    library.on(toMe, (from, to, amount, event) => {
      console.log('Transfer|received', { from, to, amount, event })
      mutate(undefined, true)
    })
    // remove listener when the component is unmounted
    return () => {
      library.removeAllListeners(toMe)
      library.removeAllListeners(fromMe)
    }
    // trigger the effect only on component mount
  }, [])

  if (!balance) {
    return <div>...</div>
  }
  return (
    <div>
      {parseFloat(formatUnits(balance, decimals)).toPrecision(4)} {symbol}
    </div>
  )
}
```

Let's zoom in. There are two main differences:

#### Key definition 

The key, used by `useSWR([address, 'balanceOf', account])`), needs to start with an Ethereum `address` rather than a `method`. Because of that, the fetcher can recognize what we want to achieve and use the ABI. 

Let's refactor the fetcher accordingly:

```typescript
const fetcher = (library: Web3Provider, abi?: any) => (...args) => {
  const [arg1, arg2, ...params] = args
  // it's a contract
  if (isAddress(arg1)) {
    const address = arg1
    const method = arg2
    const contract = new Contract(address, abi, library.getSigner())
    return contract[method](...params)
  }
  // it's a eth call
  const method = arg1
  return library[method](arg2, ...params)
}
``` 

Now we have a general-purpose fetcher capable of interacting with the JSON-RPC calls of Ethereum. 🙌 

#### Log filters

The other aspect in `<TokenBalance/>` is how to listen for the ERC20 events. `Ether.js` provides a handy way to configure a filter based on the topics and name of the event. More info about what is a topic can be found in the [Solidity Doc](https://solidity.readthedocs.io/en/develop/contracts.html?highlight=topic#events) 

```typescript
const contract = new Contract(address, ERC20ABI, library.getSigner())
const fromMe = contract.filters.Transfer(account, null)
```

Once you have built a contract instance with the ABI, then you can pass the filter to the library instance. 

Warning: 
> You could be tempted to use the `amount` in the ERC20 event directly to increase or decrease the balance. 
> Be aware of the dragon. When you setup the fetcher, you passed a closjure as callback to the `on` function, which contained the balance value at the time.  
> This could be fixed using a `useRef` but for the sake of somplicity let's revalidate  the cache to ensure the balances are fresh: `mutate(undefined, true)` 

We now have all of the pieces required, the last bit is a bit of glue. 

I configured a few constants in order to have a nice way to map my TokenBalance component to a list of tokens depending on the network where we are working: 

```typescript
export const Networks = {
  MainNet: 1,
  Rinkeby: 4,
  Ropsten: 3,
  Kovan: 42,
}

export interface IERC20 {
  symbol: string
  address: string
  decimals: number
  name: string
}

export const TOKENS_BY_NETWORK: {
  [key: number]: IERC20[]
} = {
  [Networks.Rinkeby]: [
    {
      address: "0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
      symbol: "DAI",
      name: "Dai",
      decimals: 18,
    },
    {
      address: "0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85",
      symbol: "MKR",
      name: "Maker",
      decimals: 18,
    },
  ],
}
```

Once we have the constants it's easy to map the configured tokens to my `<TokenList/>` component: 

```typescript
export const TokenList = ({ chainId }) => {
  return (
    <>
      {TOKENS_BY_NETWORK[chainId].map((token) => (
        <TokenBalance key={token.address} {...token} />
      ))}
    </>
  )
}
``` 

All set! Now we have an Ethereum Wallet that loads Ether and Token balances. And If the user sends or receives funds the wallet UI is updated. 

![step-4](https://dev-to-uploads.s3.amazonaws.com/i/ilv71b1q73utxqxtpn38.gif)

Here what we did so far: [GIT step-4](https://github.com/aboutlo/swr-eth-tutorial/tree/step-4)

## Refactoring

Let's move every component in a separated file and make the fetcher a globally available using SWRConfig provider. 

```typescript
<SWRConfig value={{ fetcher: fetcher(library, ERC20ABI) }}>
    <EthBalance />
    <TokenList chainId={chainId} />
<SWRConfig/>
```

With `SWRConfig` we can configure some options as always available, so that we can have a more convenient usage of SWR. 

```typescript
const {data: balance, mutate} = useSWR([address, 'balanceOf', account])
```

Here after the refactoring: [GIT step-5](https://github.com/aboutlo/swr-eth-tutorial/tree/step-5)

## Wrap Up 
[SWR](https://swr.now.sh/) and [Ether.js](https://docs.ethers.io/ethers.js/html/) are two nice libraries to work with if you want to streamline your data fetching strategy with Ethereum Dapp. 

### Key Advantages
- Declarative approach
- Data always fresh via web sockets or SWR options
- Avoid reinventing the wheel for state management with custom React context


If you use multiple smart contracts in your DApp and you liked this tutorial I generalised the web3 fetcher into a small util: [swr-eth](https://github.com/aboutlo/ether-swr) (Stars are appreciated 👻)

Feel free to use it and let me know what you think about this approach in the comments below

And finally, here is the full GIT repo: (https://github.com/aboutlo/swr-eth-tutorial)