Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sdk reusable query hooks #402

Merged
merged 1 commit into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/good-eagles-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@dojoengine/sdk": minor
"template-vite-ts": minor
"@dojoengine/core": minor
"@dojoengine/create-burner": minor
"@dojoengine/create-dojo": minor
"@dojoengine/predeployed-connector": minor
"@dojoengine/react": minor
"@dojoengine/state": minor
"@dojoengine/torii-client": minor
"@dojoengine/torii-wasm": minor
"@dojoengine/utils": minor
"@dojoengine/utils-wasm": minor
---

feat: add reusable sdk react hooks
3 changes: 1 addition & 2 deletions examples/example-nodejs-bot/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"outDir": "dist",
"types": ["bun-types"]
"outDir": "dist"
},
"include": [
"./src/**/*",
Expand Down
4 changes: 2 additions & 2 deletions examples/example-vite-react-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"@starknet-react/core": "catalog:",
"@types/uuid": "^10.0.0",
"immer": "^10.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "catalog:",
"react-dom": "catalog:",
"starknet": "catalog:",
"uuid": "^10.0.0",
"vite-plugin-top-level-await": "^1.5.0",
Expand Down
96 changes: 24 additions & 72 deletions examples/example-vite-react-sdk/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { useEffect, useMemo } from "react";
import { KeysClause, ToriiQueryBuilder } from "@dojoengine/sdk";
import { getEntityIdFromKeys } from "@dojoengine/utils";
import { AccountInterface, addAddressPadding, CairoCustomEnum } from "starknet";

import { ModelsMapping } from "./typescript/models.gen.ts";
import { useSystemCalls } from "./useSystemCalls.ts";
import { useAccount } from "@starknet-react/core";
import { WalletAccount } from "./wallet-account.tsx";
import { HistoricalEvents } from "./historical-events.tsx";
import { useDojoSDK, useModel } from "@dojoengine/sdk/react";
import {
useDojoSDK,
useEntityId,
useEntityQuery,
useModel,
} from "@dojoengine/sdk/react";
import { addAddressPadding, CairoCustomEnum } from "starknet";
import { Events } from "./events.tsx";

/**
* Main application component that provides game functionality and UI.
Expand All @@ -17,79 +21,26 @@ import { useDojoSDK, useModel } from "@dojoengine/sdk/react";
* @param props.sdk - The Dojo SDK instance configured with the game schema
*/
function App() {
const { useDojoStore, client, sdk } = useDojoSDK();
const { useDojoStore, client } = useDojoSDK();
const { account } = useAccount();
const state = useDojoStore((state) => state);
const entities = useDojoStore((state) => state.entities);

const { spawn } = useSystemCalls();

const entityId = useMemo(() => {
if (account) {
return getEntityIdFromKeys([BigInt(account.address)]);
}
return BigInt(0);
}, [account]);

// This is experimental feature.
// Use those queries if you want to be closer to how you should query your ecs system with torii
// useEffect(() => {
// async function fetchToriiClause() {
// const res = await sdk.client.getEntities(
// new ToriiQueryBuilder()
// .withClause(
// new ClauseBuilder()
// .keys([], [undefined], "VariableLen")
// .build()
// )
// .withLimit(2)
// .addOrderBy(ModelsMapping.Moves, "remaining", "Desc")
// .build()
// );
// return res;
// }
// fetchToriiClause().then(console.log);
// });

useEffect(() => {
let unsubscribe: (() => void) | undefined;

const subscribe = async (account: AccountInterface) => {
const [initialData, subscription] = await sdk.subscribeEntityQuery({
query: new ToriiQueryBuilder()
.withClause(
// Querying Moves and Position models that has at least [account.address] as key
KeysClause(
[ModelsMapping.Moves, ModelsMapping.Position],
[addAddressPadding(account.address)],
"VariableLen"
).build()
)
.includeHashedKeys(),
callback: ({ error, data }) => {
if (error) {
console.error("Error setting up entity sync:", error);
} else if (data && data[0].entityId !== "0x0") {
state.updateEntity(data[0]);
}
},
});

state.setEntities(initialData);

unsubscribe = () => subscription.cancel();
};

if (account) {
subscribe(account);
}

return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [sdk, account, state]);
const entityId = useEntityId(account?.address ?? "0");

useEntityQuery(
new ToriiQueryBuilder()
.withClause(
// Querying Moves and Position models that has at least [account.address] as key
KeysClause(
[ModelsMapping.Moves, ModelsMapping.Position],
[addAddressPadding(account?.address ?? "0")],
"FixedLen"
).build()
)
.includeHashedKeys()
);

const moves = useModel(entityId as string, ModelsMapping.Moves);
const position = useModel(entityId as string, ModelsMapping.Position);
Expand Down Expand Up @@ -253,6 +204,7 @@ function App() {
</table>
</div>

<Events />
{/* // Here sdk is passed as props but this can be done via contexts */}
<HistoricalEvents />
</div>
Expand Down
40 changes: 40 additions & 0 deletions examples/example-vite-react-sdk/src/events.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { KeysClause, ToriiQueryBuilder } from "@dojoengine/sdk";
import { useEntityId, useEventQuery, useModel } from "@dojoengine/sdk/react";
import { useAccount } from "@starknet-react/core";
import { addAddressPadding } from "starknet";
import { ModelsMapping } from "./typescript/models.gen";

export function Events() {
const { account } = useAccount();
const entityId = useEntityId(account?.address ?? "0");
useEventQuery(
new ToriiQueryBuilder()
.withClause(
KeysClause(
[],
[addAddressPadding(account?.address ?? "0")],
"VariableLen"
).build()
)
.includeHashedKeys()
);
const moved = useModel(entityId, ModelsMapping.Moved);
if (!account) {
return (
<div className="mt-6">
<h2 className="text-white">Please connect your wallet</h2>
</div>
);
}
return (
<div className="mt-6">
<h2 className="text-white">
Player Last Movement : {moved && moved.direction}{" "}
</h2>

{/* {events.map((e: ParsedEntity<SchemaType>, key) => {
return <Event event={e} key={key} />;
})} */}
</div>
);
}
57 changes: 12 additions & 45 deletions examples/example-vite-react-sdk/src/historical-events.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,20 @@
import { KeysClause, ParsedEntity, ToriiQueryBuilder } from "@dojoengine/sdk";
import { useAccount } from "@starknet-react/core";
import { SchemaType } from "./typescript/models.gen";
import { AccountInterface, addAddressPadding } from "starknet";
import { useEffect, useState } from "react";
import { Subscription } from "@dojoengine/torii-client";
import { useDojoSDK } from "@dojoengine/sdk/react";
import { addAddressPadding } from "starknet";
import { useHistoricalEventsQuery } from "@dojoengine/sdk/react";

export function HistoricalEvents() {
const { account } = useAccount();
const { sdk } = useDojoSDK();
const [events, setEvents] = useState<ParsedEntity<SchemaType>[]>([]);
const [subscription, setSubscription] = useState<Subscription | null>(null);

useEffect(() => {
async function subscribeHistoricalEvent(account: AccountInterface) {
try {
const [e, s] = await sdk.subscribeEventQuery({
query: new ToriiQueryBuilder().withClause(
KeysClause(
[],
[addAddressPadding(account.address)],
"VariableLen"
).build()
),
callback: ({ data, error }) => {
if (data && data.length > 0) {
console.log(data);
}
if (error) {
console.error(error);
}
},
historical: true,
});
setEvents(e as unknown as ParsedEntity<SchemaType>[]);
setSubscription(s);
} catch (error) {
setEvents([]);
if (subscription) {
subscription.free();
}
console.error(error);
}
}

if (account) {
subscribeHistoricalEvent(account);
}
}, [account, setEvents, sdk]);

const events = useHistoricalEventsQuery(
new ToriiQueryBuilder().withClause(
KeysClause(
[],
[addAddressPadding(account?.address ?? "0")],
"VariableLen"
).build()
)
);
if (!account) {
return (
<div className="mt-6">
Expand All @@ -59,6 +25,7 @@ export function HistoricalEvents() {
return (
<div className="mt-6">
<h2 className="text-white">Player Events :</h2>
{/* @ts-ignore */}
{events.map((e: ParsedEntity<SchemaType>, key) => {
return <Event event={e} key={key} />;
})}
Expand Down
1 change: 1 addition & 0 deletions examples/example-vite-react-sdk/src/starknet-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function StarknetProvider({ children }: PropsWithChildren) {
explorer={voyager}
autoConnect
>
{/* @ts-ignore react version mismatch */}
{children}
</StarknetConfig>
);
Expand Down
2 changes: 1 addition & 1 deletion examples/example-vite-react-sdk/tsconfig.app.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"root":["./src/app.tsx","./src/historical-events.tsx","./src/main.tsx","./src/starknet-provider.tsx","./src/usesystemcalls.ts","./src/vite-env.d.ts","./src/wallet-account.tsx","./src/typescript/contracts.gen.ts","./src/typescript/models.gen.ts"],"version":"5.7.3"}
{"root":["./src/app.tsx","./src/events.tsx","./src/historical-events.tsx","./src/main.tsx","./src/starknet-provider.tsx","./src/usesystemcalls.ts","./src/vite-env.d.ts","./src/wallet-account.tsx","./src/typescript/contracts.gen.ts","./src/typescript/models.gen.ts"],"version":"5.7.3"}
72 changes: 15 additions & 57 deletions examples/example-vite-react-sql/src/hooks/usePlayerActions.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,20 @@
import { useEffect, useMemo } from "react";

import { KeysClause, ParsedEntity, ToriiQueryBuilder } from "@dojoengine/sdk";
import { useDojoSDK } from "@dojoengine/sdk/react";
import { getEntityIdFromKeys } from "@dojoengine/utils";
import { ModelsMapping, SchemaType } from "@/typescript/models.gen";
import { KeysClause, ToriiQueryBuilder } from "@dojoengine/sdk";
import { useEntityId, useEntityQuery } from "@dojoengine/sdk/react";
import { ModelsMapping, type SchemaType } from "@/typescript/models.gen";
import { addAddressPadding } from "starknet";

export function usePlayerActions(address: string | undefined) {
const { sdk, useDojoStore } = useDojoSDK();
const state = useDojoStore((state) => state);

const entityId = useMemo(() => {
if (address) {
return getEntityIdFromKeys([BigInt(address)]);
}
return BigInt(0);
}, [address]);

useEffect(() => {
let unsubscribe: (() => void) | undefined;

const subscribe = async (address: string) => {
const [entities, subscription] = await sdk.subscribeEntityQuery({
query: new ToriiQueryBuilder()
.withClause(
KeysClause(
[ModelsMapping.Moves, ModelsMapping.Position],
[addAddressPadding(address)],
"VariableLen"
).build()
)
.includeHashedKeys(),
callback: ({ error, data }) => {
if (error) {
console.error("Error setting up entity sync:", error);
} else if (
data &&
(data[0] as ParsedEntity<SchemaType>).entityId !== "0x0"
) {
state.updateEntity(data[0] as ParsedEntity<SchemaType>);
}
},
});
state.setEntities(entities);

unsubscribe = () => subscription.cancel();
};

if (address) {
subscribe(address);
}

return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [sdk, address]);

const entityId = useEntityId(address ?? "0");
useEntityQuery<SchemaType>(
new ToriiQueryBuilder()
.withClause(
KeysClause(
[ModelsMapping.Moves, ModelsMapping.Position],
[addAddressPadding(address ?? "0")],
"VariableLen"
).build()
)
.includeHashedKeys()
);
return entityId;
}
1 change: 1 addition & 0 deletions examples/example-vite-react-sql/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function RootComponent() {
<div className="min-h-screen bg-background">
<Header />
<main className="container py-6 mx-auto my-0">
{/* @ts-ignore */}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

❓ Verification inconclusive

Avoid using @ts-ignore without explanation

Using @ts-ignore suppresses TypeScript errors without addressing the underlying issue. This can lead to unexpected runtime errors and makes it harder to maintain the codebase in the long term.

Consider either:

  1. Adding a comment explaining why the ignore is necessary
  2. Properly typing the component to resolve the TypeScript error
  3. If it's a library issue, adding a reference to the relevant issue
-{/* @ts-ignore */}
+{/* @ts-ignore - Working around issue with Outlet type definitions in @tanstack/react-router - see [ISSUE_URL] */}

Alternatively, investigate and fix the actual type issue:


🏁 Script executed:

#!/bin/bash
# Check the version of @tanstack/react-router being used
grep -r "@tanstack/react-router" --include="package.json" .

# Check if this pattern appears in other files
rg "@ts-ignore.*Outlet" --type tsx

Length of output: 214


Below is a revised review comment. Note that the previous command encountered an error with the file type filter; please re-run the search manually (or use the snippet below) to confirm that no additional instances require fixing. For example:

#!/bin/bash
# Verify the @ts-ignore usage in the specific file
rg '@ts-ignore' examples/example-vite-react-sql/src/routes/__root.tsx

Action Required: Annotate the Suppressed TypeScript Error in __root.tsx

  • File & Location:
    examples/example-vite-react-sql/src/routes/__root.tsx at line 24

  • Current Code:

    {/* @ts-ignore */}
  • Recommended Revision:

    -{/* @ts-ignore */}
    +{/* @ts-ignore - Working around issue with Outlet type definitions in @tanstack/react-router v1.97.0 - see [ISSUE_URL] */}
  • Next Steps:
    Either document why the suppression is needed (for example, a known issue with Outlet types in the current version of @tanstack/react-router) or resolve the underlying typing conflict.

Please manually verify there are no other occurrences of unexplained @ts-ignore comments that might hide type errors.

<Outlet />
</main>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const Player = (props: any) => {

return (
<>
{/* @ts-ignore */}
<Cone
castShadow
key="player"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ export const ThreeGrid = () => {
const initialCameraPosition = new Vector3(0, 100.0, 100); // Desired initial position

return (
// @ts-ignore
<Canvas shadows>
<CameraAdjuster position={initialCameraPosition} />
<Stage shadows="contact" adjustCamera={false}>
{/* @ts-ignore */}
<OrbitControls makeDefault />
<ambientLight />
<directionalLight
Expand Down
Loading
Loading