Skip to content

Commit

Permalink
Merge pull request #367 from dojoengine/feat/update-react-sdk-example
Browse files Browse the repository at this point in the history
feat: fix react-sdk example + predeployed connector + historical events
  • Loading branch information
MartianGreed authored Jan 6, 2025
2 parents 091d951 + 8dd2ca0 commit d37e8c4
Show file tree
Hide file tree
Showing 24 changed files with 650 additions and 349 deletions.
3 changes: 3 additions & 0 deletions examples/example-vite-react-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
"dependencies": {
"@dojoengine/core": "workspace:*",
"@dojoengine/create-burner": "workspace:*",
"@dojoengine/predeployed-connector": "workspace:*",
"@dojoengine/sdk": "workspace:*",
"@dojoengine/torii-client": "workspace:*",
"@dojoengine/torii-wasm": "workspace:*",
"@dojoengine/utils": "workspace:*",
"@starknet-react/chains": "catalog:",
"@starknet-react/core": "catalog:",
"@types/uuid": "^10.0.0",
"immer": "^10.1.1",
"react": "^18.3.1",
Expand Down
161 changes: 76 additions & 85 deletions examples/example-vite-react-sdk/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,111 @@
import { useEffect, useMemo } from "react";
import { QueryBuilder, SDK, createDojoStore } from "@dojoengine/sdk";
import {
ParsedEntity,
QueryBuilder,
SDK,
createDojoStore,
} from "@dojoengine/sdk";
import { getEntityIdFromKeys } from "@dojoengine/utils";
import { addAddressPadding } from "starknet";
import { AccountInterface, addAddressPadding } from "starknet";

import { Models, Schema } from "./bindings.ts";
import {
Direction,
ModelsMapping,
SchemaType,
} from "./typescript/models.gen.ts";
import { useDojo } from "./useDojo.tsx";
import useModel from "./useModel.tsx";
import { useSystemCalls } from "./useSystemCalls.ts";
import { useAccount } from "@starknet-react/core";
import { WalletAccount } from "./wallet-account.tsx";
import { HistoricalEvents } from "./historical-events.tsx";

/**
* Global store for managing Dojo game state.
*/
export const useDojoStore = createDojoStore<Schema>();
export const useDojoStore = createDojoStore<SchemaType>();

/**
* Main application component that provides game functionality and UI.
* Handles entity subscriptions, state management, and user interactions.
*
* @param props.sdk - The Dojo SDK instance configured with the game schema
*/
function App({ sdk }: { sdk: SDK<Schema> }) {
function App({ sdk }: { sdk: SDK<SchemaType> }) {
const {
account,
setup: { client },
} = useDojo();
const { account } = useAccount();
const state = useDojoStore((state) => state);
const entities = useDojoStore((state) => state.entities);

const { spawn } = useSystemCalls();

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

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

const subscribe = async () => {
const subscribe = async (account: AccountInterface) => {
const subscription = await sdk.subscribeEntityQuery({
query: new QueryBuilder<Schema>()
query: new QueryBuilder<SchemaType>()
.namespace("dojo_starter", (n) =>
n
.entity("Moves", (e) =>
e.eq(
"player",
addAddressPadding(account.account.address)
addAddressPadding(account.address)
)
)
.entity("Position", (e) =>
e.is(
"player",
addAddressPadding(account.account.address)
addAddressPadding(account.address)
)
)
)
.build(),
callback: (response) => {
if (response.error) {
console.error(
"Error setting up entity sync:",
response.error
);
callback: ({ error, data }) => {
if (error) {
console.error("Error setting up entity sync:", error);
} else if (
response.data &&
response.data[0].entityId !== "0x0"
data &&
(data[0] as ParsedEntity<SchemaType>).entityId !== "0x0"
) {
console.log("subscribed", response.data[0]);
state.updateEntity(response.data[0]);
state.updateEntity(data[0] as ParsedEntity<SchemaType>);
}
},
});

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

subscribe();
if (account) {
subscribe(account);
}

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

useEffect(() => {
const fetchEntities = async () => {
const fetchEntities = async (account: AccountInterface) => {
try {
await sdk.getEntities({
query: new QueryBuilder<Schema>()
query: new QueryBuilder<SchemaType>()
.namespace("dojo_starter", (n) =>
n.entity("Moves", (e) =>
e.eq(
"player",
addAddressPadding(account.account.address)
addAddressPadding(account.address)
)
)
)
Expand All @@ -107,7 +119,9 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
return;
}
if (resp.data) {
state.setEntities(resp.data);
state.setEntities(
resp.data as ParsedEntity<SchemaType>[]
);
}
},
});
Expand All @@ -116,53 +130,18 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
}
};

fetchEntities();
}, [sdk, account?.account.address]);
if (account) {
fetchEntities(account);
}
}, [sdk, account]);

const moves = useModel(entityId, Models.Moves);
const position = useModel(entityId, Models.Position);
const moves = useModel(entityId as string, ModelsMapping.Moves);
const position = useModel(entityId as string, ModelsMapping.Position);

return (
<div className="bg-black min-h-screen w-full p-4 sm:p-8">
<div className="max-w-7xl mx-auto">
<button
className="mb-4 px-4 py-2 bg-blue-600 text-white text-sm sm:text-base rounded-md hover:bg-blue-700 transition-colors duration-300"
onClick={() => account?.create()}
>
{account?.isDeploying
? "Deploying Burner..."
: "Create Burner"}
</button>

<div className="bg-gray-800 shadow-md rounded-lg p-4 sm:p-6 mb-6 w-full max-w-md">
<div className="text-lg sm:text-xl font-semibold mb-4 text-white">{`Burners Deployed: ${account.count}`}</div>
<div className="mb-4">
<label
htmlFor="signer-select"
className="block text-sm font-medium text-gray-300 mb-2"
>
Select Signer:
</label>
<select
id="signer-select"
className="w-full px-3 py-2 text-base text-gray-200 bg-gray-700 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={account ? account.account.address : ""}
onChange={(e) => account.select(e.target.value)}
>
{account?.list().map((account, index) => (
<option value={account.address} key={index}>
{account.address}
</option>
))}
</select>
</div>
<button
className="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 text-base rounded transition duration-300 ease-in-out"
onClick={() => account.clear()}
>
Clear Burners
</button>
</div>
<WalletAccount />

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
<div className="bg-gray-700 p-4 rounded-lg shadow-inner">
Expand All @@ -185,7 +164,9 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
: "Need to Spawn"}
</div>
<div className="col-span-3 text-center text-base text-white">
{moves && moves.last_direction}
{moves && moves.last_direction.isSome()
? moves.last_direction.unwrap()
: ""}
</div>
</div>
</div>
Expand All @@ -194,22 +175,22 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
<div className="grid grid-cols-3 gap-2 w-full h-48">
{[
{
direction: "Up" as const,
direction: Direction.Up,
label: "↑",
col: "col-start-2",
},
{
direction: "Left" as const,
direction: Direction.Left,
label: "←",
col: "col-start-1",
},
{
direction: "Right" as const,
direction: Direction.Right,
label: "→",
col: "col-start-3",
},
{
direction: "Down" as const,
direction: Direction.Down,
label: "↓",
col: "col-start-2",
},
Expand All @@ -218,10 +199,10 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
className={`${col} h-12 w-12 bg-gray-600 rounded-full shadow-md active:shadow-inner active:bg-gray-500 focus:outline-none text-2xl font-bold text-gray-200`}
key={direction}
onClick={async () => {
await client.actions.move({
account: account.account,
direction: { type: direction },
});
await client.actions.move(
account!,
direction
);
}}
>
{label}
Expand Down Expand Up @@ -265,6 +246,10 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
entity.models.dojo_starter.Position;
const moves =
entity.models.dojo_starter.Moves;
const lastDirection =
moves?.last_direction?.isSome()
? moves.last_direction?.unwrap()
: "N/A";

return (
<tr
Expand All @@ -278,20 +263,23 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
{position?.player ?? "N/A"}
</td>
<td className="border border-gray-700 p-2">
{position?.vec?.x ?? "N/A"}
{position?.vec?.x.toString() ??
"N/A"}
</td>
<td className="border border-gray-700 p-2">
{position?.vec?.y ?? "N/A"}
{position?.vec?.y.toString() ??
"N/A"}
</td>
<td className="border border-gray-700 p-2">
{moves?.can_move?.toString() ??
"N/A"}
</td>
<td className="border border-gray-700 p-2">
{moves?.last_direction ?? "N/A"}
{lastDirection}
</td>
<td className="border border-gray-700 p-2">
{moves?.remaining ?? "N/A"}
{moves?.remaining?.toString() ??
"N/A"}
</td>
</tr>
);
Expand All @@ -300,6 +288,9 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
</tbody>
</table>
</div>

{/* // Here sdk is passed as props but this can be done via contexts */}
<HistoricalEvents sdk={sdk} />
</div>
</div>
);
Expand Down
8 changes: 4 additions & 4 deletions examples/example-vite-react-sdk/src/DojoContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { Account } from "starknet";
import { dojoConfig } from "../dojoConfig";
import { DojoProvider } from "@dojoengine/core";
import { client } from "./contracts.gen";
import { setupWorld } from "./typescript/contracts.gen";

/**
* Interface defining the shape of the Dojo context.
Expand All @@ -16,7 +16,7 @@ interface DojoContextType {
/** The master account used for administrative operations */
masterAccount: Account;
/** The Dojo client instance */
client: ReturnType<typeof client>;
client: ReturnType<typeof setupWorld>;
/** The current burner account information */
account: BurnerAccount;
}
Expand Down Expand Up @@ -58,7 +58,7 @@ export const DojoContextProvider = ({
dojoConfig.masterPrivateKey,
"1"
),
[]
[dojoProvider.provider]
);

const burnerManagerData = useBurnerManager({ burnerManager });
Expand All @@ -67,7 +67,7 @@ export const DojoContextProvider = ({
<DojoContext.Provider
value={{
masterAccount,
client: client(dojoProvider),
client: setupWorld(dojoProvider),
account: {
...burnerManagerData,
account: burnerManagerData.account || masterAccount,
Expand Down
Loading

0 comments on commit d37e8c4

Please sign in to comment.