@ckb-ccc/connector-react
React connector with Provider component and hooks for CKB wallet integration.
@ckb-ccc/connector-react is the recommended integration layer for React and Next.js applications. It provides a context Provider, a useCcc hook that exposes the full connector state, and a useSigner hook that returns the active signer whenever a wallet is connected.
Installation
npm install @ckb-ccc/connector-reactQuick start
Place Provider at the root of your application (above any component that
needs wallet access).
import { ccc } from "@ckb-ccc/connector-react";
export default function App({ children }) {
return (
<ccc.Provider>
{children}
</ccc.Provider>
);
}Call useCcc() inside any descendant component to open the wallet picker
and read the connected wallet state.
import { ccc } from "@ckb-ccc/connector-react";
export function ConnectButton() {
const { open, wallet, signerInfo } = ccc.useCcc();
return (
<div>
<button onClick={open}>
{wallet ? `Connected: ${wallet.name}` : "Connect Wallet"}
</button>
{signerInfo && <Address />}
</div>
);
}
function Address() {
const { signerInfo } = ccc.useCcc();
const [address, setAddress] = React.useState("");
React.useEffect(() => {
signerInfo?.signer.getRecommendedAddress().then(setAddress);
}, [signerInfo]);
return <p>{address}</p>;
}Provider props
function CccProvider({ children }: { children: React.ReactNode }) {
const [clientOptions, setClientOptions] =
useState<{ name: string; client: ccc.Client }[]>();
useEffect(() => {
const owner = ccc.OwnerAggregated.from([
ccc.ClientPublicTestnet.open(),
ccc.ClientPublicMainnet.open(),
] as const);
const [testnet, mainnet] = owner.value;
// The clients must be opened after commit to avoid leaking aborted renders.
// eslint-disable-next-line react-hooks/set-state-in-effect
setClientOptions([
{ name: "Testnet", client: testnet },
{ name: "Mainnet", client: mainnet },
]);
return () => void owner.dispose().catch(() => {});
}, []);
if (!clientOptions) return null;
return (
<ccc.Provider
hideMark={false}
name="My App"
icon="https://example.com/icon.png"
signerFilter={async (signerInfo, wallet) => true}
clientOptions={clientOptions}
>
{children}
</ccc.Provider>
);
}| Prop | Type | Description |
|---|---|---|
children | ReactNode | Your application tree |
connectorProps | HTMLAttributes<{}>? | Additional props to pass to the connector element |
hideMark | boolean? | Hide the "Powered by CCC" mark in the connector UI |
name | string? | App name shown in the wallet picker |
icon | string? | App icon URL shown in the wallet picker |
signerFilter | (signerInfo, wallet) => Promise<boolean> | Filter which wallet/signer combinations appear |
signersController | ccc.SignersController? | Custom signers controller (advanced) |
defaultClient | ccc.Client? | Borrowed initial client; takes precedence over clientOptions[0] |
clientOptions | { icon?, client, name }[]? | Borrowed network options; the first is the default when defaultClient is omitted |
Styling the connector
Pass CSS custom properties through connectorProps.style. The Provider starts
with a complete light theme, so you only need to override the values your theme
changes.
<ccc.Provider
connectorProps={{
style: {
color: "#e6eef2",
"--background": "#11181c",
"--btn-primary": "#171d21",
"--btn-primary-hover": "#5bcefa",
"--btn-color": "#e6eef2",
"--btn-color-hover": "#070a0c",
"--tip-color": "#76858d",
"--tip-color-hover": "#31515f",
} as React.CSSProperties,
}}
>
{children}
</ccc.Provider>See the @ckb-ccc/connector styling reference
for the complete variable list, defaults, and fallback behavior.
useCcc() hook
const {
isOpen, // boolean — whether the wallet picker modal is open
open, // () => void — open the wallet picker
close, // () => void — close the wallet picker
disconnect, // () => void — disconnect the current wallet
setClient, // (owner: ccc.Owner<ccc.Client>) => void — transfer and switch client
client, // ccc.Client — current network client
wallet, // ccc.Wallet | undefined — connected wallet
signerInfo, // ccc.SignerInfo | undefined — connected signer
} = ccc.useCcc();The hook throws if called outside a <ccc.Provider> tree.
Clients passed through defaultClient or clientOptions remain owned by the
caller. setClient(owner) transfers ownership to the Provider, which disposes
the owner on the next setClient call or when the Provider unmounts. Passing a bare Client to
setClient remains supported for compatibility, but is deprecated.
useBorrowedOrOwned() hook
Use this hook for resources that may be borrowed from a parent but need an internally owned fallback when the parent does not provide one:
function openTestnetClient() {
return ccc.ClientPublicTestnet.open();
}
function ClientConsumer({ client: borrowed }: { client?: ccc.Client }) {
const client = ccc.useBorrowedOrOwned(borrowed, openTestnetClient);
if (!client) return null; // The fallback opens after the component commits.
return <App client={client} />;
}The hook never disposes a borrowed value. Its fallback Owner is independent of
the borrowed value and remains available for reuse. It is reopened only when
the open function changes and is disposed when replaced or when the component
unmounts. Keep open referentially stable.
Full example
"use client"; // Required for Next.js App Router
import { ccc } from "@ckb-ccc/connector-react";
import { useState, useEffect } from "react";
function Layout({ children }: { children: React.ReactNode }) {
return (
<ccc.Provider name="My CKB App">
<Header />
{children}
</ccc.Provider>
);
}
function Header() {
const { open, disconnect, wallet, signerInfo, client } = ccc.useCcc();
const [address, setAddress] = useState("");
useEffect(() => {
if (!signerInfo) {
setAddress("");
return;
}
signerInfo.signer.getRecommendedAddress().then(setAddress);
}, [signerInfo]);
return (
<header>
{wallet ? (
<>
<span>{address}</span>
<button onClick={disconnect}>Disconnect</button>
</>
) : (
<button onClick={open}>Connect</button>
)}
</header>
);
}Filtering wallets
Use signerFilter to show only specific wallet types:
import { ccc } from "@ckb-ccc/connector-react";
// Show only CKB-native wallets
<ccc.Provider
signerFilter={async (signerInfo, wallet) => {
return signerInfo.signer.type === ccc.SignerType.CKB;
}}
>
{children}
</ccc.Provider>Next.js (App Router)
CCC's connector uses React context and browser APIs, so it only works on the client side. Add "use client" to any file that imports from @ckb-ccc/connector-react or renders <ccc.Provider>.
"use client";
import { ccc } from "@ckb-ccc/connector-react";Forgetting "use client" in Next.js App Router will cause a runtime error:
TypeError: (0, react....createContext) is not a function.
Last updated on