Web3-React (V8)

This tutorial is a step-by-step guide on how to integrate a wallet such as Phantom into your dApp using the Web3-React (v8) library. Web3-React is an EVM library that exposes hooks for common web3 primitives such as wallets, addresses, and the like.

We will be going through step by step how to go from zero to a fully integrated web3-react-powered Phantom connect button.

Prerequisites

  • Node version >=16.12.0

  • A text editor/IDE (such as VSCode)

  • Some Knowledge of React

  • Yarn (v1)

Creating The App

We recommend using Vite to create new react applications.

To create a new React application using Vite, run the following command in your terminal:

yarn create vite
  1. This will ask you for a project name. Provide it a name here. For purposes of this tutorial I used "Web3-React-V8-Sandbox".

  2. It will then ask you to select a framework. Select "React" here.

  3. Next it will ask for a variant. Select "Typescript" here.

Now change directory into your project and run:

yarn install

And make sure your app runs by running the command:

yarn dev

Configuring TypeScript

You will need to change the moduleResolution keys to be switched from "bundler" to "node".

Each file should look as follows.

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

With that out of the way we can install all of our dependencies.

Installing Dependencies

To install the appropriate packages, run the following:

yarn add web3-react-phantom @web3-react/core eventemitter3 @web3-react/types

Now you can use the web3-react and web3-react-phantom packages in your project and integrate Phantom as a wallet in the app.

Initializing The Connector

Web3-react is entirely based around the idea of connectors, and how it handles them. In a nutshell, you configure all of the wallets you'll need up front, and then web3-react works as a huge global state machine, that allows you to interact with any wallet anywhere in your application.

To begin, create a file in the root of your directory called connectors.ts it should look like the following

// connectors.ts
import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
import { Connector, Web3ReactStore } from '@web3-react/types'
import { Phantom } from 'web3-react-phantom'

const phantom = initializeConnector<Phantom>((actions) => new Phantom({ actions }))

const connectors: [Connector, Web3ReactHooks, Web3ReactStore][] = [phantom]

export default connectors

Here you Initialize all of the connectors that you want in your app, and then you can pass them all to the connectors array, and export it for consumption in your app.

Initializing Web3-React

First we will need to import the packages into our project. At the top of the main.tsx file add these imports:

// main.tsx
import { Web3ReactProvider, Web3ReactHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'

import allConnections from './connectors'

Next, we will configure web3-react like so.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { Web3ReactProvider, Web3ReactHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'

import allConnections from './connectors'

const connections: [Connector, Web3ReactHooks][] = allConnections.map(([connector, hooks]) => [connector, hooks])

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <Web3ReactProvider connectors={connections}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Web3ReactProvider>
)

This will wrap our Vite app in this <Web3ReactProvider> JSX component which will give us global access to the hooks that we need to interact with Phantom.

We also map through all of the arrays as the connector has 3 elements by default, and the <Web3ReactProvider> element only expects 2, so we want to pull the 3rd one out before passing it to the wrapper.

We have everything we need done to add a connect wallet button to the app. But before we do, let's create a Card component

Card Component

In the root of your app you can create a folder called components and inside of that folder, create a file and name it Card.tsx

//Card.tsx
import { useEffect, useState } from 'react'
import { Web3ReactSelectedHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'

export default function Card({connector, hooks, name}: {connector: Connector, hooks: Web3ReactSelectedHooks, name: string}) {
  const {useSelectedAccount, useSelectedChainId, useSelectedIsActive, useSelectedIsActivating } = hooks
  const isActivating = useSelectedIsActivating(connector)
  const isActive = useSelectedIsActive(connector)
  const account = useSelectedAccount(connector)
  const chain = useSelectedChainId(connector)

  const [error, setError] = useState<Error | undefined>(undefined)
  const [connectionStatus, setConnectionStatus] = useState('Disconnected')

  const handleToggleConnect = () => {
    setError(undefined) // clear error state

    if (isActive) {
      if(connector?.deactivate) {
        void connector.deactivate()
      } else {
        void connector.resetState()
      }
    }
    else if (!isActivating) {
      setConnectionStatus('Connecting..')
        Promise.resolve(connector.activate(1))
        .catch((e) => {
          connector.resetState()
          setError(e)
        }) 
      }
  }
  useEffect(() => {
    if(isActive) {
      setConnectionStatus('Connected')
    } else {
      setConnectionStatus('Disconnected')
    }
  }
  ,[isActive])

  return (
    <div>
      <p>{name.toUpperCase()}</p>
      <h3>Status - {(error?.message) ? ("Error: " + error.message) : connectionStatus}</h3>
      <h3>Address - {account ? account : "No Account Detected"}</h3>
      <h3>ChainId -  {chain ? chain : 'No Chain Connected'}</h3>
      <button onClick={handleToggleConnect} disabled={false}>
        {isActive ? "Disconnect" : "Connect"}
      </button>
    </div>
  )
}

While this may look a bit complicated, we can break it down a bit.

What we will be passing the component are connector, hooks, and name props.

We can run through what each of them is going to do.

Prop
Purpose

connector

You can think of this as the wallet itself. It will be directly responsible for connecting, disconnecting, and sending requests to the blockchain.

hooks

These are an assortment of convenience hooks that will grab information about the wallet/connection that you are handling.

name

This is a string that we'll use to label the name of the connection to display in the UI.

The rest of the component is calling the hooks to get the relevant status connection, address, and chain id. Then we add some state management to display whether or not the wallet is connected, disconnected, or in the process of connecting.

Then we display all of the relevant information, and and show connect/disconnect buttons.

Adding The Card To The App

At the top of your App.tsx file you can import the Card component we just made and the useWeb3React hook that we will use to access the values we stored in the wrapper up in main.tsx.

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

Next, you will call the useWeb3React hook at the top of your App component

function App() {
const { connector, hooks } = useWeb3React(); // <-- This line
  //return ( ... )
}

Then delete the useState hook, it's import, and the button that increments it as we do not need the counter anymore.

Then add the Card component and pass in the connector and hooks into it. Where the counter button used to be.

<Card connector={connector} hooks={hooks} name='phantom' />

Your entire App.tsx file should look like so

import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
import { useWeb3React } from "@web3-react/core";
import Card from "./components/Card";

function App() {
  const { connector, hooks } = useWeb3React();

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="App">
      <h1>Web3-React Connector Playbox</h1>
      <div className="card">
        <Card connector={connector} hooks={hooks} name='phantom' />
      </div>
    </div>
    </>
  );
}

export default App;

Now when you run everything locally it should look like this!

And then once you click the connect button, it will pop open phantom's connect screen. Once you hit approve all of your information should be displayed like so

Conclusion

You've now added your very own Phantom connect button using Web3-React!

You can use this as the basis for your own dApp if you're looking to build out something, or use it for reference to add a Phantom connect button to your existing dApp.

Either way, we hope you found this useful.

Last updated