React hooks, components, and examples for seamless Flow blockchain integration. Connect wallets, execute transactions, and query blockchain state with ease.
@onflow/fcl and @onflow/types in your React project. See installation guide below.Connect and manage Flow wallet connections
// hooks/useFlowWallet.ts
import { useState, useEffect, useCallback } from 'react'
import * as fcl from '@onflow/fcl'
export interface FlowUser {
addr: string | null
cid: string | null
loggedIn: boolean
services: any[]
}
export function useFlowWallet() {
const [user, setUser] = useState<FlowUser>({
addr: null,
cid: null,
loggedIn: false,
services: []
})
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
// Configure FCL
fcl.config({
'accessNode.api': process.env.NEXT_PUBLIC_FLOW_ACCESS_NODE || 'https://rest-testnet.onflow.org',
'discovery.wallet': process.env.NEXT_PUBLIC_FLOW_WALLET_DISCOVERY || 'https://fcl-discovery.onflow.org/testnet/authn',
'app.detail.title': 'FlowDevKit App',
'app.detail.icon': 'https://flowdevkit.com/logo.png',
})
// Subscribe to user changes
const unsubscribe = fcl.currentUser.subscribe(setUser)
return () => unsubscribe()
}, [])
const login = useCallback(async () => {
setIsLoading(true)
setError(null)
try {
await fcl.authenticate()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to authenticate')
} finally {
setIsLoading(false)
}
}, [])
const logout = useCallback(async () => {
setIsLoading(true)
try {
await fcl.unauthenticate()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to logout')
} finally {
setIsLoading(false)
}
}, [])
const signMessage = useCallback(async (message: string) => {
if (!user.loggedIn) {
throw new Error('User not authenticated')
}
try {
const signature = await fcl.currentUser.signUserMessage(message)
return signature
} catch (err) {
throw new Error('Failed to sign message')
}
}, [user.loggedIn])
return {
user,
isLoading,
error,
login,
logout,
signMessage,
isAuthenticated: user.loggedIn
}
}// Import and use in your component
import { useFlowWallet } from '@/components/useFlowWallet'
export function MyApp() {
return (
<div>
<useFlowWallet
contractAddress="0x01"
contractName="FlowDevKitNFT"
/>
</div>
)
}Wallet connection button with status display
// components/WalletConnect.tsx
'use client'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Wallet, LogOut, Loader2 } from 'lucide-react'
import { useFlowWallet } from '@/hooks/useFlowWallet'
interface WalletConnectProps {
className?: string
showDetails?: boolean
}
export function WalletConnect({ className, showDetails = false }: WalletConnectProps) {
const { user, isLoading, error, login, logout, isAuthenticated } = useFlowWallet()
if (showDetails && isAuthenticated) {
return (
<Card className={className}>
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Wallet className="h-4 w-4" />
Connected Wallet
</CardTitle>
<CardDescription>
Your Flow wallet is connected and ready to use
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Address:</span>
<Badge variant="secondary" className="font-mono text-xs">
{user.addr?.slice(0, 8)}...{user.addr?.slice(-6)}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Status:</span>
<Badge variant="default">Connected</Badge>
</div>
<Button
variant="outline"
size="sm"
onClick={logout}
disabled={isLoading}
className="w-full bg-transparent"
>
{isLoading ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<LogOut className="h-4 w-4 mr-2" />
)}
Disconnect
</Button>
</CardContent>
</Card>
)
}
return (
<Button
onClick={isAuthenticated ? logout : login}
disabled={isLoading}
className={className}
variant={isAuthenticated ? "outline" : "default"}
>
{isLoading ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<Wallet className="h-4 w-4 mr-2" />
)}
{isAuthenticated ? 'Disconnect' : 'Connect Wallet'}
</Button>
)
}// Import and use in your component
import { WalletConnect } from '@/components/WalletConnect'
export function MyApp() {
return (
<div>
<WalletConnect
contractAddress="0x01"
contractName="FlowDevKitNFT"
/>
</div>
)
}Full application setup with FCL configuration
// app/layout.tsx
import { FlowProvider } from '@/components/FlowProvider'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<FlowProvider>
{children}
</FlowProvider>
</body>
</html>
)
}
// components/FlowProvider.tsx
'use client'
import { useEffect } from 'react'
import * as fcl from '@onflow/fcl'
interface FlowProviderProps {
children: React.ReactNode
}
export function FlowProvider({ children }: FlowProviderProps) {
useEffect(() => {
fcl.config({
'accessNode.api': process.env.NEXT_PUBLIC_FLOW_ACCESS_NODE || 'https://rest-testnet.onflow.org',
'discovery.wallet': process.env.NEXT_PUBLIC_FLOW_WALLET_DISCOVERY || 'https://fcl-discovery.onflow.org/testnet/authn',
'app.detail.title': 'FlowDevKit App',
'app.detail.icon': 'https://flowdevkit.com/logo.png',
'service.OpenID.scopes': 'email email_verified name zoneinfo',
})
}, [])
return <>{children}</>
}// Import and use in your component
import { Complete App Setup } from '@/components/Complete App Setup'
export function MyApp() {
return (
<div>
<Complete App Setup
contractAddress="0x01"
contractName="FlowDevKitNFT"
/>
</div>
)
}# Install Flow Client Library
npm install @onflow/fcl @onflow/types
# Install UI components (optional)
npm install @radix-ui/react-* class-variance-authority# .env.local
NEXT_PUBLIC_FLOW_ACCESS_NODE=https://rest-testnet.onflow.org
NEXT_PUBLIC_FLOW_WALLET_DISCOVERY=https://fcl-discovery.onflow.org/testnet/authn// lib/flow.ts
import * as fcl from '@onflow/fcl'
fcl.config({
'accessNode.api': process.env.NEXT_PUBLIC_FLOW_ACCESS_NODE,
'discovery.wallet': process.env.NEXT_PUBLIC_FLOW_WALLET_DISCOVERY,
'app.detail.title': 'Your App Name',
'app.detail.icon': 'https://yourapp.com/logo.png',
})// app/page.tsx
import { WalletConnect } from '@/components/WalletConnect'
import { NFTBalance } from '@/components/NFTBalance'
export default function Home() {
return (
<div className="space-y-6">
<WalletConnect showDetails />
<NFTBalance
contractAddress="0x01"
contractName="FlowDevKitNFT"
/>
</div>
)
}