## OpChan React SDK (packages/react) — Building Forum UIs This guide shows how to build a forums-based app using the React SDK. It covers project wiring, wallet connection/disconnection, key delegation, waiting for Waku/network readiness, loading content, posting/commenting, voting, moderation, bookmarks, and user display utilities. The examples assume you install and use the `@opchan/react` and `@opchan/core` packages. --- ### 1) Install and basic setup ```bash npm i @opchan/react @opchan/core ``` Create an app-level provider using `OpChanProvider`. The provider already wraps `WagmiProvider` and React Query; no AppKit is required. Mount it directly at the app root. ```tsx import React from 'react'; import { OpChanProvider } from '@opchan/react'; export function AppProviders({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` OpChanProvider uses wagmi connectors (Injected/WalletConnect/Coinbase) for wallet management. --- ### 2) High-level hook Use `useForum()` for a single entry point to the main hooks: ```tsx import { useForum } from '@opchan/react'; function Example() { const { user, content, permissions, network } = useForum(); // user: auth + delegation // content: cells/posts/comments/bookmarks + actions // permissions: derived booleans + reasons // network: Waku readiness + refresh return null; } ``` --- ### 3) Wallets — connect/disconnect & verification API: `useAuth()` ```tsx import { useAuth } from '@opchan/react'; function WalletControls() { const { currentUser, verificationStatus, connect, disconnect, verifyOwnership } = useAuth(); return (
{currentUser ? ( <>
Connected: {currentUser.displayName}
Status: {verificationStatus}
) : ( <> )}
); } ``` Notes: - `connect()` opens the selected wagmi connector (e.g., Injected/WalletConnect). Upon successful connection, OpChan automatically syncs the wallet state and creates a user session. - `verifyOwnership()` refreshes identity and sets `EVerificationStatus` appropriately (checks ENS). --- ### 4) Key delegation — create, check, clear API: `useAuth()` → `delegate(duration)`, `delegationStatus()`, `clearDelegation()`; also `delegationInfo` in-session. ```tsx import { useAuth } from '@opchan/react'; function DelegationControls() { const { delegate, delegationInfo, delegationStatus, clearDelegation } = useAuth(); return (
Delegated: {String(delegationInfo.isValid)} {delegationInfo.expiresAt && `, expires ${delegationInfo.expiresAt.toLocaleString()}`}
); } ``` Behavior: - The library generates a browser keypair and requests a wallet signature authorizing it until a selected expiry. - All messages (cells/posts/comments/votes/moderation/profile updates) are signed with the delegated browser key and verified via the proof. --- ### 5) Network (Waku) — readiness and manual refresh API: `useNetwork()` ```tsx import { useNetwork } from '@opchan/react'; function NetworkStatus() { const { isConnected, statusMessage, refresh } = useNetwork(); return (
{isConnected ? 'Connected' : 'Connecting...'} • {statusMessage}
); } ``` Notes: - The store wires Waku health events to `network.isConnected` and `statusMessage`. - `refresh()` triggers a lightweight cache refresh; live updates come from the Waku subscription. --- ### 6) Loading content — cells, posts, comments, bookmarks API: `useContent()` ```tsx import { useContent } from '@opchan/react'; function Feed() { const { cells, posts, comments, postsByCell, commentsByPost, bookmarks, lastSync } = useContent(); return (
Cells: {cells.length}
Posts: {posts.length}
Comments: {comments.length}
Bookmarks: {bookmarks.length}
Last sync: {lastSync ? new Date(lastSync).toLocaleTimeString() : '—'}
); } ``` Derived helpers: - `postsByCell[cellId]: Post[]` - `commentsByPost[postId]: Comment[]` (sorted oldest→newest) - `cellsWithStats`: adds `postCount`, `activeUsers`, `recentActivity` for UI - `userVerificationStatus[address]`: identity verification snapshot for weighting/relevance - `pending.isPending(id)`: show optimistic “syncing…” indicators --- ### 7) Creating content — cells, posts, comments All actions reflect to the local cache immediately and then propagate over the network. ```tsx import { useContent } from '@opchan/react'; function Composer({ cellId, postId }: { cellId?: string; postId?: string }) { const { createCell, createPost, createComment } = useContent(); const onCreateCell = async () => { await createCell({ name: 'My Cell', description: 'Description', icon: '' }); }; const onCreatePost = async () => { if (!cellId) return; await createPost({ cellId, title: 'Hello', content: 'World' }); }; const onCreateComment = async () => { if (!postId) return; await createComment({ postId, content: 'Nice post!' }); }; return (
); } ``` Permissions: see `usePermissions()` below to gate UI. --- ### 8) Voting ```tsx import { useContent } from '@opchan/react'; function VoteButtons({ targetId }: { targetId: string }) { const { vote, pending } = useContent(); const isSyncing = pending.isPending(targetId); return (
{isSyncing && syncing…}
); } ``` --- ### 9) Moderation (cell owner) ```tsx import { useContent, usePermissions } from '@opchan/react'; function Moderation({ cellId, postId, commentId, userAddress }: { cellId: string; postId?: string; commentId?: string; userAddress?: string }) { const { moderate } = useContent(); const { canModerate } = usePermissions(); if (!canModerate(cellId)) return null; return (
{postId && ( <> )} {commentId && ( <> )} {userAddress && ( <> )}
); } ``` --- ### 10) Bookmarks ```tsx import { useContent } from '@opchan/react'; function BookmarkControls({ post, comment }: { post?: any; comment?: any }) { const { bookmarks, togglePostBookmark, toggleCommentBookmark, removeBookmark, clearAllBookmarks } = useContent(); return (
Total: {bookmarks.length}
{post && } {comment && }
); } ``` --- ### 11) Permissions helper API: `usePermissions()` exposes booleans and user-friendly reasons for gating UI. ```tsx import { usePermissions } from '@opchan/react'; function ActionGates({ cellId }: { cellId: string }) { const p = usePermissions(); return ( ); } ``` --- ### 12) User display/identity helpers API: `useUserDisplay(address)` returns resolved user identity for UI labels. ```tsx import { useUserDisplay } from '@opchan/react'; function AuthorName({ address }: { address: string }) { const { displayName, ensName, isLoading } = useUserDisplay(address); if (isLoading) return Loading…; return {displayName}; } ``` --- ### 13) End-to-end page skeleton ```tsx import React from 'react'; import { OpChanProvider, useForum } from '@opchan/react'; function Home() { const { user, content, permissions, network } = useForum(); return (
Network: {network.isConnected ? 'Ready' : 'Connecting…'}
{user.currentUser ? (
Welcome {user.currentUser.displayName}
) : ( )} {permissions.canPost && content.cells[0] && ( )}
); } export default function App() { return ( ); } ``` --- ### 14) Notes and best practices - Always gate actions with `usePermissions()` to provide clear UX reasons. - Use `pending.isPending(id)` to show optimistic “syncing…” states for content you just created or voted on. - The store is hydrated from IndexedDB on load; Waku live messages keep it in sync. - Identity (ENS/Call Sign) is resolved and cached; calling `verifyOwnership()` or updating the profile will refresh it.