How to Add Multi-Wallet Connect to a React dApp (RainbowKit + wagmi)
Step-by-step guide to integrating multi-wallet connect in a React dApp using RainbowKit and wagmi, supporting MetaMask, WalletConnect, and Coinbase Wallet.
Most dApps lose users the moment they show a blank modal with a single "Connect MetaMask" button. Your users are on mobile, using Coinbase Wallet, or running Rabby — and you've already locked them out. A proper multi-wallet connect flow takes one afternoon to set up and immediately broadens who can actually use your product.
This guide walks through a production-ready multi-wallet integration using RainbowKit and wagmi in a React application. The approach works with Next.js or plain Vite.
Why Multi-Wallet Connect Matters More Than You Think
The injected window.ethereum provider covers MetaMask on desktop Chrome. That's it. Users on Safari, Firefox, mobile browsers, or hardware wallets hit a dead end. WalletConnect v2 extends your reach to every wallet that supports the protocol — and that is now the majority of wallets in active use.
RainbowKit wraps wagmi's connection logic with a polished UI and sensible defaults. It handles:
- Injected providers (MetaMask, Brave, Rabby)
- WalletConnect v2 QR code and deep link flows
- Coinbase Wallet SDK
- Safe (multisig) wallet detection
- Recent wallet persistence across sessions
Setting Up the Dependencies
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-queryYou need a WalletConnect project ID from cloud.walletconnect.com. Registration is free. Store the ID in an environment variable — never hard-code it in source.
Configuring wagmi and RainbowKit
Create a wagmi.config.ts at the root of your project:
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { mainnet, polygon, base } from 'wagmi/chains';
export const config = getDefaultConfig({
appName: 'Your App Name',
projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
chains: [mainnet, polygon, base],
ssr: true, // set false for Vite
});Keep your chain list intentional. Every chain you add appears in the network switcher — users will try to switch to it, and if your contracts aren't deployed there, you'll see support tickets.
Wrapping Your App
In your root layout or _app.tsx:
import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from './wagmi.config';
import '@rainbow-me/rainbowkit/styles.css';
const queryClient = new QueryClient();
export default function App({ children }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
{children}
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}Adding the Connect Button
import { ConnectButton } from '@rainbow-me/rainbowkit';
export function Header() {
return (
<nav>
<ConnectButton
showBalance={false}
chainStatus="icon"
accountStatus="address"
/>
</nav>
);
}The ConnectButton component is fully customizable via the ConnectButton.Custom render prop if your design system requires a bespoke look.
Reading Wallet State in Components
Once connected, wagmi hooks give you typed access to the wallet state:
import { useAccount, useChainId, useDisconnect } from 'wagmi';
export function WalletStatus() {
const { address, isConnected, connector } = useAccount();
const chainId = useChainId();
const { disconnect } = useDisconnect();
if (!isConnected) return null;
return (
<div>
<p>Connected via {connector?.name}</p>
<p>Address: {address}</p>
<p>Chain: {chainId}</p>
<button onClick={() => disconnect()}>Disconnect</button>
</div>
);
}Common Integration Pitfalls
Hydration mismatch on SSR. Wallet state is client-only. Wrap anything that reads isConnected in a mounted check or use wagmi's useHydrated pattern, or you will see React hydration errors on Next.js.
Missing chain configuration. If a user is on a chain you haven't listed in chains, wagmi will report isConnected: false even though the wallet shows as connected. Always prompt users to switch to a supported chain before attempting contract calls.
Stale session after wallet lock. Subscribe to the disconnect event from useAccount to clear any local auth state when the user locks or switches wallets. Failing to do this leaves ghost sessions that look logged in but reject every transaction.
Hard-coded project IDs. WalletConnect rate-limits by project ID. If you ship with a shared or example ID, your users will see WalletConnect sessions fail under load.
Testing Across Wallets
Use a dedicated test browser profile with MetaMask installed. Test the WalletConnect flow on a real mobile device — the QR scan experience in a simulator is not representative. Confirm that disconnect clears state correctly in all three paths: user disconnects in-wallet, user disconnects in-dApp, and session expires.
If you want a production-ready multi-wallet integration shipped into your existing stack without the integration tax, Start a build with Clixo. We've wired up wallet connect flows for consumer apps and DeFi protocols alike.