Build a React wallet connector
Create a React application that connects to a wallet using the Midnight DApp Connector API.
The following code examples intentionally omit CSS and visual styling to keep the examples focused and easy to read. The actual example app code includes full styling (Tailwind classes and additional CSS) so the components are displayed with a polished, user-friendly appearance.
Also, the code is framework-agnostic and can be used with any React framework. You can use these components and logic in projects created with Vite, Create React App, Next.js, Remix, Gatsby, or custom setups. You might only need to edit the entry file (for example src/main.tsx vs src/index.tsx), routing, or build configuration to match your chosen scaffold.
Prerequisitesβ
The following is required:
- Familiarity with TypeScript/JavaScript
- Basic understanding of React
- Midnight Lace wallet extension installed in your browser.
After using this tutorial, you should:
- Understand basic Midnight wallet connection flow
- Know how to use @midnight-ntwrk/dapp-connector-api
- Have working starter code to build your own applications
Define TypeScript interfacesβ
Let's first define a interface for the props passed between the component we are going to create. Their main purpose is to provide type safety and clear contracts for components. WalletCard describes the connection state and callbacks used by the app to trigger connect/disconnect actions.
The actual implementation of the interfaces shown below can be found in the example app at src/types.ts.
export interface WalletCardProps {
isConnected: boolean;
walletAddress: string | null;
onConnect: () => void;
onDisconnect: () => void;
}
Create the WalletCard componentβ
The WalletCard aggregates UI and actions related to the wallet connection: it shows status, displays the address when available, and exposes connect/disconnect actions via the Button component.
Its main role in the app is to be the single visible component that reflects the wallet connection state and triggers the logic handled by the App component we'll define afterwards.
The WalletCard component as implemented in the example app can be found at src/WalletCard.tsx.
import React from "react";
import type { WalletCardProps } from "./types";
const WalletCard: React.FC<WalletCardProps> = ({
isConnected,
walletAddress,
onConnect,
onDisconnect,
}) => {
return (
<div>
<div>
<h2>Connection Status</h2>
<div className={isConnected ? "text-green-400" : "text-red-400"}>
{isConnected ? "Connected" : "Disconnected"}
</div>
</div>
<div>
{isConnected && walletAddress ? (
<>
<p>Wallet Address:</p>
<p title={walletAddress}>{walletAddress}</p>
</>
) : (
<p>Please connect your wallet to proceed.</p>
)}
</div>
<div>
{isConnected ? (
<button onClick={onDisconnect}>Disconnect Wallet</button>
) : (
<button onClick={onConnect}>Connect Wallet</button>
)}
</div>
</div>
);
};
export default WalletCard;
Create the main App componentβ
We need one last component, the App component, which manages state and business logic: it holds connection state and the current wallet address, and provides callbacks to child components to change that state.
To begin with, the connect handler is a stub producing a dummy wallet address; later on we will call the Midnight DApp Connector. App wires UI (WalletCard) to the connector logic and is the central coordinator for the application.
The App component shown below is implemented in the example app at src/App.tsx.
import React, { useState } from 'react';
import WalletCard from './WalletCard';
const App: React.FC = () => {
const [isConnected, setIsConnected] = useState<boolean>(false);
const [walletAddress, setWalletAddress] = useState<string | null>(null);
const handleConnect = () => {
// We will replace this to actually use the Midnight DApp connector API.
const fakeAddress = `0x${Array(40).fill(0).join("")}`;
setWalletAddress(fakeAddress);
setIsConnected(true);
};
const handleDisconnect = () => {
setWalletAddress(null);
setIsConnected(false);
};
return (
<div>
<header>
<h1>Midnight Wallet Connector</h1>
</header>
<main>
<WalletCard
isConnected={isConnected}
walletAddress={walletAddress}
onConnect={handleConnect}
onDisconnect={handleDisconnect}
/>
</main>
</div>
);
};
export default App;
Create the entry pointβ
At this point we are ready to bootstrap the React application into the DOM. We can now mount the App component and ensure React runs in StrictMode during development.
In the implemented example it also pulls in Tailwind-generated CSS so the UI renders with the intended styling, this can be found at src/main.tsx.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
Lastly, we create the HTML file providing the hosting page for the single-page app and links the global stylesheet. The HTML used by the example app is available at index.html.
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="/index.css">
<title>React Midnight Wallet Connector</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Your application should now be displaying a dummy address, so now we are ready to add the last piece to integrate it with the Midnight DApp Connector, to retrieve the actual wallet's address.
Integrate Midnight DApp Connectorβ
Let's now replace handleConnect function body in the App component with a real implementation using the DApp Connector API.
Start by installing the DApp Connector API package:
npm install @midnight-ntwrk/dapp-connector-api
After that, import the DApp Connector API package in the App component:
import "@midnight-ntwrk/dapp-connector-api";
Then, update the handleConnect function to use the DApp Connector API:
const handleConnect = async () => {
let isConnected = false;
let address = null;
try {
// To authorize a DApp, call the enable() method and wait for
// the user to respond to the request.
const connectorAPI = await window.midnight?.mnLace.enable();
// Let's now check if the DApp is authorized, using the isEnabled() method
const isEnabled = await window.midnight?.mnLace.isEnabled();
if (isEnabled) {
isConnected = true;
console.log("Connected to the wallet:", connectorAPI);
// To get the wallet state, we call the state() API method, that will
// return the DAppConnectorWalletState object, which is where we can get
// the wallet address from.
const state = await connectorAPI.state();
address = state.address;
}
} catch (error) {
console.log("An error occurred:", error);
}
setIsConnected(isConnected);
setWalletAddress(address);
};
The code above demonstrates how to call the Midnight DApp Connector API to enable the wallet, check whether it's enabled, and retrieve the current wallet state (including the address). With this code, the WalletCard displays the real address and connection status:
The DApp Connector API uses the global variable window.midnight.{walletName} to connect the wallet, in this case, window.midnight.mnLace.
Your application should now be running with the Midnight DApp Connector integration. It will prompt the user to authorise the connection from the Lace Preview wallet. Once authorised, the application displays the connection status and the connected wallet address.
Next stepsβ
You can consider implementing the following:
- Allowing the user to transfer coins
- Expose a text input for signing messages with the connected wallet