Claude-skill-registry dojo-react
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dojo-react" ~/.claude/skills/majiayu000-claude-skill-registry-dojo-react && rm -rf "$T"
manifest:
skills/data/dojo-react/SKILL.mdsource content
Dojo.js React Patterns
When to Use
Use this skill when:
- Setting up React provider hierarchy
- Using Effect atoms for complex async state
- Implementing infinite scroll
- Handling loading/error states
Provider Hierarchy
Recommended order:
import { StarknetConfig } from "@starknet-react/core"; import { DojoSdkProvider } from "@dojoengine/sdk/react"; function Providers({ children }) { return ( <StarknetConfig connectors={connectors}> <DojoSdkProvider dojoConfig={dojoConfig} sdk={sdk} clientFn={createClient} > {children} </DojoSdkProvider> </StarknetConfig> ); }
useDojoSDK Hook
import { useDojoSDK } from "@dojoengine/sdk/react"; function GameComponent() { const { sdk, // SDK instance config, // DojoConfig client, // Client from clientFn provider, // DojoProvider useDojoStore // Zustand store hook } = useDojoSDK(); }
Effect Atoms (Recommended)
Effect atoms provide robust async state management:
Entity Query Atom
import { createEntityQueryAtom } from "@dojoengine/react/effect"; import { Result } from "@dojoengine/react/effect"; const playersAtom = createEntityQueryAtom( runtime, new ToriiQueryBuilder().addEntityModel("game-Player").withLimit(100) ); function PlayerList() { const players = useAtomValue(playersAtom); return Result.match(players, { onSuccess: ({ value }) => ( <ul> {value.items.map(p => <li key={p.entityId}>{p.models.game.Player.name}</li>)} </ul> ), onFailure: (error) => <div>Error: {error.message}</div>, onInitial: () => <div>Loading...</div> }); }
Entity Updates Atom (Real-time)
import { createEntityUpdatesAtom } from "@dojoengine/react/effect"; const updatesAtom = createEntityUpdatesAtom( runtime, KeysClause(["game-Player"], [], "VariableLen").build() );
Combined Query + Updates
import { createEntityQueryWithUpdatesAtom } from "@dojoengine/react/effect"; const livePlayersAtom = createEntityQueryWithUpdatesAtom( runtime, query, clause );
Infinite Scroll
import { createEntitiesInfiniteScrollAtom } from "@dojoengine/react/effect"; const infinitePlayersAtom = createEntitiesInfiniteScrollAtom( runtime, new ToriiQueryBuilder().addEntityModel("game-Player"), 20 // page size ); function InfinitePlayerList() { const [state, loadMore] = useAtom(infinitePlayersAtom); return Result.match(state, { onSuccess: ({ value }) => ( <> <ul> {value.items.map(p => ( <li key={p.entityId}>{p.models.game.Player.name}</li> ))} </ul> {value.hasMore && ( <button onClick={loadMore}>Load More</button> )} </> ), onFailure: (error) => <div>Error: {error.message}</div>, onInitial: () => <div>Loading...</div> }); }
Token Balance Atoms
import { createTokenBalanceQueryAtom, createTokenBalanceUpdatesAtom } from "@dojoengine/react/effect"; // One-time query const balanceAtom = createTokenBalanceQueryAtom(runtime, { contractAddresses: ["0x..."], accountAddresses: [playerAddress] }); // Polling updates const liveBalanceAtom = createTokenBalanceUpdatesAtom( runtime, { contractAddresses: ["0x..."], accountAddresses: [playerAddress] }, 5000 // poll every 5 seconds );
Data Formatters
Transform data before rendering:
const formatters = { models: { "game-Player": (player) => ({ ...player, displayName: player.name || "Anonymous", displayScore: `${player.score.toLocaleString()} pts` }) }, fields: { "game-Player.score": (score) => Math.floor(score / 100) } }; const playersAtom = createEntityQueryAtom(runtime, query, formatters);
Custom Subscription Hooks
import { createSubscriptionHook } from "@dojoengine/sdk/react"; const usePlayerSubscription = createSubscriptionHook({ subscribeMethod: (sdk, params) => sdk.subscribeEntityQuery(params), processInitialData: (data) => data.items, processUpdateData: (update) => update }); function PlayerTracker({ entityId }) { const { data, error, isLoading } = usePlayerSubscription({ query: new ToriiQueryBuilder() .withClause(KeysClause(["game-Player"], [entityId]).build()) }); }
Zustand Selectors
Optimize re-renders with selectors:
function PlayerScore({ entityId }) { const { useDojoStore } = useDojoSDK(); // Only re-render when score changes const score = useDojoStore( state => state.entities[entityId]?.models?.game?.Player?.score, (a, b) => a === b // equality function ); return <span>{score}</span>; }
useEntityId Hook
import { useEntityId } from "@dojoengine/sdk/react"; function PlayerCard({ address, gameId }) { // Memoized entity ID computation const entityId = useEntityId(address, gameId); const player = useModel(entityId, "game-Player"); }
Error Boundaries
import { ErrorBoundary } from "react-error-boundary"; function GameErrorFallback({ error, resetErrorBoundary }) { return ( <div> <p>Something went wrong: {error.message}</p> <button onClick={resetErrorBoundary}>Retry</button> </div> ); } function App() { return ( <ErrorBoundary FallbackComponent={GameErrorFallback}> <Game /> </ErrorBoundary> ); }
Common Pitfalls
- Missing provider: Ensure DojoSdkProvider wraps all Dojo-using components
- Effect runtime: Create runtime once at app startup, not in components
- Selector stability: Use stable selector functions to prevent re-renders
- Cleanup: Effect atoms handle cleanup, but manual subscriptions need cleanup
- Result.match: Always handle all three cases (success, failure, initial)