Article card header with dark trippy flowy colors with blue, grey, green shades
How to Use Web3React in Your Next Project
July 26, 2021
A how-to guided tour for everything you need to get started right away

July 26, 2021

William Schwab &Gerhard Steenkamp

How to Use Web3React in Your Next Project

A how-to guided tour for everything you need to get started right away

14 min read

Here at Linum Labs we've built up a good amount of experience integrating wallets into apps. The go-to interface for doing this is almost certainly Pedro Gomes's web3modal, which is an excellent resource. We've been working a lot with another interface we hear mentioned less often - Uniswap's Noah Zinmeister's web3react library. There are a lot of touches in it that we really enjoy. There isn't much written about how to use it, though. We wrote some internal docs, and then realized that it would be great to share with the larger ecosystem.

If you are from the web3react repo, we'd love to contribute our docs however suits you best. Please get in touch!

This article was initially written with a table of contents and links, especially since it's also intended as a reference of the web3react API, but due to the nature of the framework it's rendered in, all the internal links had to be taken out. If you're interested in a Markdown version of the article with a table of contents and internal links for reference, there is a HackMD version of the article here.

Initial Setup

The first thing you'll need to do is install the web3react packages. You'll always need the core package, and will also need the web3react connectors for any wallets you plan on integrating. We'll talk about the wallet connectors separately. For core, if you're using Yarn, you'd use:

yarn add @web3-react/core

In order to make use of web3react inside your React app, you'll need to do a few things inside the App component:

import `Web3ReactProvider` from `@web3-react/core
  • wrap the app with a special Web3ReactProvider component
  • add a getLibrary function

Your App component might look something like this:

import { Web3ReactProvider } from '@web3-react/core'
import { Web3Provider } from "@ethersproject/providers";

function getLibrary(provider, connector) {
  return new Web3Provider(provider);
}

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

(This example is loosely based on the web3react docs.) The code above assumes that you're using Ethers.js (specifically the Web3Provider from Ethers), but the same can be done in web3.js.

The idea is that you need to import a provider (generally via Ethers or Web3.js in the JavaScript/TypeScript space), and then instantiate and return it in the getLibrary function.

Wallet and Provider Connectors

Wallets and web3 providers (such as Infura) connect to an app in web3react via a connector. If you wish to add a new wallet or provider, there is likely already a web3react package to aid the process.

There is a list of packages on the landing page of the GitHub repo.

Connecting Wallets

We'll describe the process of connecting a wallet using web3-react, and also use MetaMask and Portis as examples with code snippets.

First you'll need to get the connector for the desired wallet from the web3-react repo. You can find the code for the connectors in this Github repo, though each is also its own npm package, and should be added to your project using Yarn or NPM, installing each separately as needed.

If we wanted MetaMask, we would get the package using yarn add @web3-react/injected-connector. (The injected-connector isn't specific to MetaMask - any wallet which works by injecting itself into the browser (like Brave) will also use this package.) For Portis, it would be yarn add @web3react/portis-connector.

Each wallet needs its own unique instantiation details. We won't give a guide for each wallet here other than giving our WalletConnect example.

If you want to add something else, head over to the package details in the web3-react repo and see what you'll need there.

We'll start with the Portis example since most other wallets follow the same pattern.

We'd head over to the portis-connector directory. In the src/ directory there we can see an index.ts file, which has a TypeScript interface called PortisConstructorArguments. These are the arguments you'll need when creating a Portis connector inside your app:

interface PortisConnectorArguments {
  dAppId: string;
  networks: Network[];
  config?: any;
}

MetaMask (and other injected wallets) work a bit differently than most of the other connectors, as its only argument is an array called supportedchainIds, which, as its name implies, is a list of chainId numbers the dapp should support.

export interface AbstractConnectorArguments {
  supportedChainIds?: number[]
}

We'll need this information in a minute.

To connect MetaMask in your app, import the injected connector:

import { InjectedConnector } from '@web3-react/injected-connector'

For Portis:

import { PortisConnector } from '@web3-react/portis-connector'

This is something you'll want to have run when your app instantiates (most likely), and you'd like it to be available anywhere in your app that interacts with the blockchain. As a result, in a global state store or high up in the tree make sense.

As mentioned before, the only argument is an array of supportedChainIds. If we're testing on Goerli, this means all we'd need to add is:

export const injectedProvider = new InjectedConnector({ supportedChainIds: [5] });
chainId 5 is the Goerli testnet, you can replace that with whatever chain you want to work with or add several if your app is deployed on more than one network.

Portis has more arguments. For one, you'll need to set up a dApp ID at Portis. The optional config argument is specified in their docs; it allows you to set a number of options, and is worth checking out if you're building with Portis. network works identically to supportedChainIds in the injected connector - it's an array of chainId numbers to be supported.

export const portis = new PortisConnector({
  dAppId: "YOUR_DAPP_ID",
  network: [5]
});

Next, if you are instantiating the connector in one place, but need access to the wallet in another, import your wallet wherever you need it like this:

import { injectedProvider, portis } from "wherever/you/put/the/connector/instantiation";
Connecting with the Wallet

In order to connect with the wallet we've added, you'll need to form a handleConnect function, which takes a connector as an argument. We'll give an example of how you might want a function like this to look.

We'll assume you're using web3.js, and that you have a connection to Infura, Alchemy, or a similar provider in order to instantiate a provider, and that you're storing your API key using dotenv. (If you don't, go and set up a free account by either.)

If you're using this snippet, make sure the entire endpoint URL is in the .env file, like:

https://eth-goerli.alchemyapi.io/v2/deadbeef\_deadbeef\_deadbeef.
const handleConnect = (connector: any) => {
   // read-only
  let web3 = new Web3(new Web3.providers(proccess.env.API_ENDPOINT));
  await activate(connector);
  let { provider } = await connector.activate();
   // signer
  web3 = new Web3(provider);
}

If you're using ethers:

import { Web3Provider } from '@ethersproject/providers';

const handleConnect = (connector: any) => {
    // read-only
    let ethersProvider = new ethers.providers.JsonRpcProvider(proccess.env.API_ENDPOINT);
    let { provider } = await connector.activate();
    // signer
    const signer = provider.getSigner();
    ethersProvider = new Web3Provider(signer);
  }

This is a very basic function - there's a good chance that you'll want to add some functionality to it, like reloading contracts once there's a signer attached, or a try/catch for disconnecting the wallet on a failed connection attempt. From a UI perspective, there are also intermediate states that you'll want to handle, like connecting (to provide a spinner) and the like.

Interacting With Wallets and Providers
Once you have a connected provider and/or wallet, web3react gives you a number of modules for interacting with the wallet.

In any component interacting with the wallet, first import useWeb3React:

  import { useWeb3React } from "@web3-react/core";

Then destructure the relevant modules from web3react like so:

    const {
        account,
        activate,
        active,
        chainId,
        connector,
        deactivate,
        error,
        provider,
        setError,
    } = useWeb3React();

We'll give a detailed rundown of what you can do with this in the section called Web3React API Reference. Before we get to that, we'll detail how to disconnect a wallet.

Disconnecting a Wallet

web3react provides two different functions for disconnecting a wallet: deactivate and close.

You can see deactivate in the code above - it can be destructured directly from the object you get when you call userWeb3React. close is a custom function that most, but not all, wallets have. Unfortunately, it can be hard to know which to call when.

In addition, MetaMask and the injected connectors do not have a close function, which can cause an error if it is called, and some wallets will not be cleared from the DOM unless close is called, meaning that you cannot rely on deactivate.

As a result, you may want to implement a generic disconnectWallet function which ensures that the right functions are called for disconnecting the wallet fully.

Web3React API Reference

From here we'll do our best to describe any function in web3react that we're aware of, what it does, and how to use it.

It's worth noting that since we're using web3react, all wallet interactions should now go through web3react using web3react's syntax, as opposed to using the wallet's own API.

1. Connector

This object represents that connector that is connected right now. It extends Abstract-Connector.

A number of the functions that connector exposes are also available directly through useWeb3React(), even without using connector directly. The rest of the functions that connector expose can change from wallet to wallet - not everything here will necessarily be implemented in every wallet, and the functions might behave differently from wallet to wallet.

One particularly pertinent example is the connector.close() functions, which removes a wallet from the DOM. This can mean needing various checks when calling these functions, such as a try/catch or an if checking which wallet is connected (if connector === walletConnect, for example). We'll discuss disconnecting wallets in more detail later.

If a supportedChainIds array was provided when the wallet was instantiated (highly recommended), it is also available through connector.supportedChainIds.

In addition, there are a number of outputs sent to the console from connector. You can see these in the abstract class in this example.

2. Account

Also accessible as

connector.getAccount()

This is the address of the connected wallet - anytime you need the user's address, you can use account.

const displayAddress = () => (
  <div>Your address is: {account}</div>
)
3. Activate

Also accessible as

connector.activate(<WALLET_NAME>)

Used when connecting a new wallet. If you have a walletConnect built from WalletConnectConnector:

activate(walletConnect);

This is used in the example above for connecting a wallet.

4. Active

A boolean showing if the user has a connected wallet, useful in conditional rendering.

if(active){
    ...
  } // or:
 active && <PaymentModal />
5. ChainId

Also accessible as

connector.getChainId()

Number representation of the chainId the user is connected to. Useful for only displaying modals when the user is connected to supported networks, or displaying an alert when they are not.

For a comprehensive list of chains (both production and test networks), see ChainId Network.

if(chainId === 1){
  return <PaymentModal />
} else {
  return  <>Please connect to Ethereum's Mainnet</>
}
6. Deactivate

Also accessible as

connector.deactivate()

Deactivate disconnects the wallet from the app. It is important to note that if the wallet has injected an iframe (like Portis, for example), it will not be removed from the DOM, which can lead to issues if the user tries to reconnect. In these cases, it is better to use connector.close(). We'll discuss disconnecting the user separately.

const disconnect = () => (<button onClick={deactivate}>Disconnect</button>)
7. Error

The error library exposes a number of potential errors that it recognizes. It does not stop execution when they are triggered, but rather gives the architect room to catch each error and customize how the application should react to it.

Most of these errors vary from wallet to wallet, and unfortunately not all are documented. We'll give one example to give an idea of how they work and then list errors from the supported wallets (MetaMask/injected providers, Portis, and Torus).

Most dApps support some chains (generally mainnet and some testnets), but not all. Properly identifying if the user is on a supported chain is a critical part of good UX in a dApp. web3-react exposes a UnsupportedChainIdError that allows you to detect when a user is on an unsupported chain.

This error relies on an array of supported chains being supplied when the wallet is instantiated. You may recall from the WalletConnect example above that supportedChainIds is an optional argument, an array of numbers. This, or something like it, is either a mandatory or optional argument for every connector/wallet that web3-react recognizes. This example assumes you've passed in an array with supported chains.

First, import the error:

import { UnsupportedChainIdError } from '@web3-react/core';

Then destructure it from the useWeb3React object:

const {
  // whichever other web3-react libraries you need,
  chainId,
  error
} = useWeb3React();

Now if you wanted to have a specific reaction in the app, you can now use it. (Remember that supportedChainIds is an optional argument.)

Here's one example:

if(Boolean(connector.supportedChainIds) && !connector.supportedChainIds.includes(chainId)) {
  throw new UnsupportedChainIdError(chainId, connector.supportedChainIds);
}

The other globally available error is StaleConnectorError.

There are also custom errors depending on your wallet. We'll list MetaMask's:

  • NoEthereumProviderError: used when an injected provider is expected to be there but isn't (example)
  • UserRejectedRequestError: used if the user rejects a transaction through the injected provider

Portis does not have any custom errors.

8. Provider

Also accessible as

connector.getProvider()

This is the same as the concept of a provider in web3.js. It also contains a signer unlike the Ethers paradigm where the provider and signer are two separated entities.

This means that when using web3, the provider is passed in at contract instantiation, but with Ethers a simple provider (for example, using ethers.getDefaultProvider()) is sufficient for instantiating a contract, but the web3react provider will need to be connected to send transactions.

(This might be a bit confusing. There is a dedicated section for working with contracts in Warp Core later.)

myContract.connect(provider).myFunction{ value: 1 }(); //ethers
myContract = new web3.Contract(abi, address, provider); // web3
9. SetError

setError_ takes an _Error_ as an argument. _Error is a built-in TypeScript type:

interface Error {
  name: string;
  message: string;
  stack?: string;
}

When called, it reloads web3react. This is used internally in the library when errors happen - web3react will call setError and try to reload.

You can also use this manually if you'd like to create a custom error which will then be available throughout the project.

You can set a new error type like this:

const { setError } = useWeb3React();
setError({ name: "fooError", message: "you got a foo error"});

Then you can use that error anywhere in the app:

const { error } = useWeb3React();

if(error && error.name === "fooError"){
   doSomething();
}
Custom Functions

In addition to the global functions above, some wallets (connectors) have their own unique functions exposed by web3react.

These can be very important. close(), for example, will fully clear DOM-based wallets from the DOM, which deactivate() will not.

If you'd like to find the custom functions for a given wallet, as of this writing you will need to actually look through the code of the connector to see what's there. Here is the directory of all the connectors.

For example, if we're looking to see if there are any special functions for Portis, we'd go to the Portis connector, where we'd see the global (public) functions (activate, getProvider, getChainId, getAccount, deactivate), and then also a changeNetwork and close function.

Conclusion

That's a wrap!

We've detailed how you to integrate Web3React into your project, how to use it with the wallets you'd like, and what you can do with it once you're up and running.

We think Web3React is an excellent library, and hope this article helps people get started with it in their own projects!

Thanks!

We would like to thank everyone who responded with feedback in the GitHub issue!