with one click
dojo-react
// 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
// 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
Use this skill when the user asks to "test dojo contracts", "run integration tests", "start dojo stack", "test with torii", "verify contract behavior", mentions integration testing for Dojo/Cairo contracts, or needs to set up local katana/torii infrastructure.
Update @dojoengine/grpc proto files from Torii repository
Use when debugging and troubleshooting dojo.js applications. Triggers: dojo error, torii connection, entity not found, type mismatch, debug dojo, troubleshoot, subscription error, sync issues
Use when querying, fetching, or subscribing to game entities in dojo.js. Triggers: entity query, fetch entities, useModel, useEntityQuery, ToriiQueryBuilder, subscribe entities, getEntities, entity subscription, model data
Use when subscribing to game events and tracking tokens in dojo.js. Triggers: subscribe events, game events, token balance, event query, achievements, event subscription, onTokenBalanceUpdated, activity tracking
Use when setting up dojo.js SDK in a frontend project. Triggers: setup dojo, initialize dojo, configure dojo, dojoengine setup, sdk init, DojoSdkProvider, world address, torii url, dojo config
| 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 |
Use this skill when:
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>
);
}
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 provide robust async state management:
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>
});
}
import { createEntityUpdatesAtom } from "@dojoengine/react/effect";
const updatesAtom = createEntityUpdatesAtom(
runtime,
KeysClause(["game-Player"], [], "VariableLen").build()
);
import { createEntityQueryWithUpdatesAtom } from "@dojoengine/react/effect";
const livePlayersAtom = createEntityQueryWithUpdatesAtom(
runtime,
query,
clause
);
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>
});
}
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
);
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);
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())
});
}
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>;
}
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");
}
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>
);
}