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.
A wallet connection flow needs to account for desktop extensions, mobile wallets, and network selection. This example uses RainbowKit and wagmi to add connection controls and read wallet state in a React application.
Connection Methods
RainbowKit provides a connection interface around wagmi. Its integrations include:
- 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
The examples use a particular generation of RainbowKit and wagmi APIs. Check package compatibility and the migration guides before using them with current releases.
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
});List only the chains your application supports. Check that the contracts and RPC configuration are available for each listed chain.
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. Configure server/client state consistently. Wagmi documents ssr: true and optional cookie-backed hydration; follow the SSR guide for the installed version.
Missing chain configuration. Check the connected chain independently from connection status and prompt for a supported chain before contract calls.
Wallet and application sessions diverge. Define how account changes and disconnections affect your application session. A wallet connection is not a substitute for server-side authentication or session expiry.
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.