Claude-skill-registry dojo-state
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-state" ~/.claude/skills/majiayu000-claude-skill-registry-dojo-state && rm -rf "$T"
manifest:
skills/data/dojo-state/SKILL.mdsource content
Dojo.js State Management
When to Use
Use this skill when:
- Setting up the Zustand store for game state
- Accessing entities from the store
- Implementing optimistic updates
- Working with historical entity data
Store Setup
import { createDojoStore } from "@dojoengine/sdk/react"; import type { GameState } from "@dojoengine/state/zustand"; // Create typed store const useDojoStore = createDojoStore<typeof schema>();
GameState Interface
interface GameState<T> { entities: Record<string, ParsedEntity<T>>; historical_entities: Record<string, ParsedEntity<T>[]>; pendingTransactions: Record<string, PendingTransaction>; // Entity operations setEntities: (entities: ParsedEntity<T>[]) => void; mergeEntities: (entities: ParsedEntity<T>[]) => void; updateEntity: (entity: Partial<ParsedEntity<T>>) => void; // Historical operations setHistoricalEntities: (entities: ParsedEntity<T>[]) => void; mergeHistoricalEntities: (entities: ParsedEntity<T>[]) => void; // Optimistic updates applyOptimisticUpdate: (transactionId: string, updateFn: (draft) => void) => void; revertOptimisticUpdate: (transactionId: string) => void; confirmTransaction: (transactionId: string) => void; // Queries getEntity: (entityId: string) => ParsedEntity<T> | undefined; getEntities: (filter?: (entity) => boolean) => ParsedEntity<T>[]; getEntitiesByModel: (namespace: keyof T, model: keyof T[keyof T]) => ParsedEntity<T>[]; getHistoricalEntities: (entityId: string) => ParsedEntity<T>[]; // Subscriptions subscribeToEntity: (entityId: string, listener: (entity) => void) => () => void; waitForEntityChange: (entityId: string, predicate: (entity) => boolean, timeout?: number) => Promise<ParsedEntity<T> | undefined>; // Cleanup resetStore: () => void; }
Accessing Store Data
import { useDojoSDK } from "@dojoengine/sdk/react"; function GameComponent() { const { useDojoStore } = useDojoSDK(); // Get single entity const player = useDojoStore(state => state.getEntity(entityId)); // Get all entities with filter const activePlayers = useDojoStore(state => state.getEntities(e => e.models?.game?.Player?.isActive) ); // Get entities by model const allPlayers = useDojoStore(state => state.getEntitiesByModel("game", "Player") ); }
Optimistic Updates
Optimistic updates show changes immediately while the transaction confirms:
function movePlayer(direction: string) { const { useDojoStore, provider } = useDojoSDK(); const store = useDojoStore.getState(); const transactionId = `move-${Date.now()}`; // 1. Apply optimistic update immediately store.applyOptimisticUpdate(transactionId, (draft) => { const entity = draft.entities[entityId]; if (entity?.models?.game?.Position) { entity.models.game.Position.x += direction === "right" ? 1 : -1; } }); try { // 2. Execute the actual transaction await provider.execute(account, { contractName: "actions", entrypoint: "move", calldata: [direction] }, "game"); // 3. Confirm on success (removes pending state) store.confirmTransaction(transactionId); } catch (error) { // 4. Revert on failure store.revertOptimisticUpdate(transactionId); } }
Entity Subscriptions
function useEntitySubscription(entityId: string) { const { useDojoStore } = useDojoSDK(); const store = useDojoStore.getState(); useEffect(() => { const unsubscribe = store.subscribeToEntity(entityId, (entity) => { console.log("Entity changed:", entity); }); return () => unsubscribe(); }, [entityId]); }
Waiting for Entity Changes
async function waitForMove(entityId: string) { const { useDojoStore } = useDojoSDK(); const store = useDojoStore.getState(); const entity = await store.waitForEntityChange( entityId, (entity) => entity?.models?.game?.Position?.x > 10, 30000 // 30 second timeout ); if (entity) { console.log("Player reached position:", entity.models.game.Position); } }
Historical Entities
function PlayerTimeline({ entityId }) { const { useDojoStore } = useDojoSDK(); const history = useDojoStore(state => state.getHistoricalEntities(entityId) ); return ( <ul> {history.map((snapshot, i) => ( <li key={i}> Position: ({snapshot.models.game.Position.x}, {snapshot.models.game.Position.y}) </li> ))} </ul> ); }
Merging Entities from Subscriptions
const { sdk, useDojoStore } = useDojoSDK(); await sdk.subscribeEntityQuery({ query: query, callback: ({ data }) => { if (data) { // Merge updates into store useDojoStore.getState().mergeEntities([data]); } } });
Common Pitfalls
- Forgetting to confirm/revert: Always handle both success and error cases for optimistic updates
- Stale closures: Use
for callbacks to avoid stale stateuseDojoStore.getState() - Over-subscribing: Use selectors to minimize re-renders
- Missing cleanup: Always unsubscribe from entity subscriptions on unmount