All Insights
Feb 202612 min read

MetaMask Integration in React

Everything you need to know about connecting wallets to your React application. From basic setup to handling edge cases like network switching and account changes.

Gabriel Njoabozia
Gabriel NjoaboziaFounder & Lead Engineer

Introduction

Wallet connection is the entry point for most Web3 applications. MetaMask remains the dominant choice, with over 30 million monthly active users. Getting this integration right is crucial for user onboarding.

In this guide, we'll cover:

  • Setting up a React project with wagmi and viem
  • Creating a reusable wallet connection button
  • Handling network switching gracefully
  • Managing account changes and disconnections
  • Best practices for production applications

Project Setup

We'll use wagmi v2 with viem for Ethereum interactions. This combination provides type-safe hooks and excellent developer experience.

npm install wagmi viem @tanstack/react-query

# For TypeScript, also install types
npm install -D @types/node

Configuring the Wagmi Provider

First, we need to set up the WagmiProvider and QueryClientProvider. These should wrap your entire application.

// app/providers.tsx
'use client'

import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config } from '@/lib/wagmi'

const queryClient = new QueryClient()

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        {children}
      </QueryClientProvider>
    </WagmiProvider>
  )
}

Wagmi Config

Configure the chains and transports your app supports. Always include mainnet for broad compatibility.

// lib/wagmi.ts
import { http, createConfig } from 'wagmi'
import { mainnet, polygon, optimism } from 'wagmi/chains'

export const config = createConfig({
  chains: [mainnet, polygon, optimism],
  transports: {
    [mainnet.id]: http(),
    [polygon.id]: http(),
    [optimism.id]: http(),
  },
})

The Connect Button Component

Here's a production-ready wallet button that handles loading, errors, and disconnected states elegantly.

'use client'

import { useAccount, useConnect, useDisconnect } from 'wagmi'
import { useState } from 'react'

export function WalletButton() {
  const [showDropdown, setShowDropdown] = useState(false)
  const { address, isConnected, isConnecting } = useAccount()
  const { connect, connectors, isPending, error } = useConnect()
  const { disconnect } = useDisconnect()

  if (isConnected && address) {
    return (
      <div className="relative">
        <button
          onClick={() => setShowDropdown(!showDropdown)}
          className="px-4 py-2 bg-white/10 rounded-full text-sm"
        >
          {address.slice(0, 6)}...{address.slice(-4)}
        </button>
        {showDropdown && (
          <div className="absolute top-full right-0 mt-2 bg-black border border-white/10 rounded-lg p-2">
            <button
              onClick={() => disconnect()}
              className="w-full text-left px-4 py-2 hover:bg-white/5 rounded text-sm"
            >
              Disconnect
            </button>
          </div>
        )}
      </div>
    )
  }

  return (
    <div className="relative">
      <button
        onClick={() => setShowDropdown(!showDropdown)}
        disabled={isConnecting}
        className="px-6 py-3 bg-white text-black rounded-full font-medium disabled:opacity-50"
      >
        {isConnecting ? 'Connecting...' : 'Connect Wallet'}
      </button>
      {showDropdown && (
        <div className="absolute top-full right-0 mt-2 bg-black border border-white/10 rounded-lg p-2 min-w-[200px]">
          {connectors.map((connector) => (
            <button
              key={connector.uid}
              onClick={() => connect({ connector })}
              disabled={isPending}
              className="w-full text-left px-4 py-2 hover:bg-white/5 rounded text-sm disabled:opacity-50"
            >
              {connector.name}
            </button>
          ))}
          {error && (
            <p className="px-4 py-2 text-red-500 text-xs">
              {error.message}
            </p>
          )}
        </div>
      )}
    </div>
  )
}

Handling Network Switching

Users often need to switch networks. Always prompt them to add or switch when they try to use a feature on an unsupported chain.

import { useSwitchChain } from 'wagmi'

export function NetworkSwitcher({ targetChainId }: { targetChainId: number }) {
  const { switchChain, isPending, error } = useSwitchChain()

  return (
    <button
      onClick={() => switchChain({ chainId: targetChainId })}
      disabled={isPending}
      className="px-4 py-2 bg-orange-500/20 text-orange-400 rounded-full text-sm"
    >
      {isPending ? 'Switching...' : 'Switch to Polygon'}
    </button>
  )
}

Listening for Account Changes

MetaMask can disconnect unexpectedly. Use the useAccount hook's listeners to handle these events gracefully.

import { useAccount, useDisconnect } from 'wagmi'

export function AccountListener() {
  const { address } = useAccount()
  const { disconnect } = useDisconnect()

  // Handle account changes
  useEffect(() => {
    if (!address) {
      // Clear app state when disconnected externally
      localStorage.removeItem('user_session')
    }
  }, [address])

  // Handle disconnection from MetaMask popup
  window.ethereum?.on('accountsChanged', (accounts: string[]) => {
    if (accounts.length === 0) {
      disconnect()
    }
  })
}

Production Considerations

  • Always show loading states — Wallet connections take time
  • Handle rejection gracefully — Users can cancel at any point
  • Persist connection state — Use localStorage to remember recent connections
  • Support multiple wallets — Don't assume MetaMask is installed
  • Test on mobile — WalletConnect or Coinbase Wallet may be preferred

Conclusion

Wallet integration is more than just connecting. It requires handling edge cases, providing clear feedback, and respecting user experience at every step. The patterns above have served us well across dozens of Web3 projects.

Need help integrating wallets into your product? We're experienced with wallet connections across React, Vue, and vanilla JS applications.

Building a Web3 product? Let's talk →