--- name: dojo-react description: | Use for React integration patterns and best practices in dojo.js. Triggers: dojo react, react hooks, effect atoms, Result.match, infinite scroll dojo, DojoSdkProvider, useDojoSDK, react patterns --- # 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: ```tsx import { StarknetConfig } from "@starknet-react/core"; import { DojoSdkProvider } from "@dojoengine/sdk/react"; function Providers({ children }) { return ( {children} ); } ``` ## useDojoSDK Hook ```tsx 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 ```tsx 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 }) => ( ), onFailure: (error) =>
Error: {error.message}
, onInitial: () =>
Loading...
}); } ``` ### Entity Updates Atom (Real-time) ```tsx import { createEntityUpdatesAtom } from "@dojoengine/react/effect"; const updatesAtom = createEntityUpdatesAtom( runtime, KeysClause(["game-Player"], [], "VariableLen").build() ); ``` ### Combined Query + Updates ```tsx import { createEntityQueryWithUpdatesAtom } from "@dojoengine/react/effect"; const livePlayersAtom = createEntityQueryWithUpdatesAtom( runtime, query, clause ); ``` ## Infinite Scroll ```tsx 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 }) => ( <> {value.hasMore && ( )} ), onFailure: (error) =>
Error: {error.message}
, onInitial: () =>
Loading...
}); } ``` ## Token Balance Atoms ```tsx 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: ```tsx 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 ```tsx 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: ```tsx 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 {score}; } ``` ## useEntityId Hook ```tsx 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 ```tsx import { ErrorBoundary } from "react-error-boundary"; function GameErrorFallback({ error, resetErrorBoundary }) { return (

Something went wrong: {error.message}

); } function App() { return ( ); } ``` ## Common Pitfalls 1. **Missing provider**: Ensure DojoSdkProvider wraps all Dojo-using components 2. **Effect runtime**: Create runtime once at app startup, not in components 3. **Selector stability**: Use stable selector functions to prevent re-renders 4. **Cleanup**: Effect atoms handle cleanup, but manual subscriptions need cleanup 5. **Result.match**: Always handle all three cases (success, failure, initial)