From 2fe9c64b91e3ca7acf151cd6e4893a661f31fc29 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Sat, 5 Oct 2024 17:47:05 +1000 Subject: [PATCH] feat: optimistic example --- examples/example-vite-react-sdk/src/App.tsx | 9 +-- .../src/useSystemCalls.ts | 62 ++++++++++++++++ packages/sdk/readme.md | 74 ++++++++++++++++++- readme.md | 18 ++--- 4 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 examples/example-vite-react-sdk/src/useSystemCalls.ts diff --git a/examples/example-vite-react-sdk/src/App.tsx b/examples/example-vite-react-sdk/src/App.tsx index b33fd68b..2afab183 100644 --- a/examples/example-vite-react-sdk/src/App.tsx +++ b/examples/example-vite-react-sdk/src/App.tsx @@ -6,6 +6,7 @@ import { addAddressPadding } from "starknet"; import { Models, Schema } from "./bindings.ts"; import { useDojo } from "./useDojo.tsx"; import useModel from "./useModel.tsx"; +import { useSystemCalls } from "./useSystemCalls.ts"; export const useDojoStore = createDojoStore(); @@ -17,6 +18,8 @@ function App({ db }: { db: SDK }) { 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] @@ -161,11 +164,7 @@ function App({ db }: { db: SDK }) {
diff --git a/examples/example-vite-react-sdk/src/useSystemCalls.ts b/examples/example-vite-react-sdk/src/useSystemCalls.ts new file mode 100644 index 00000000..f03f12e1 --- /dev/null +++ b/examples/example-vite-react-sdk/src/useSystemCalls.ts @@ -0,0 +1,62 @@ +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { useDojoStore } from "./App"; +import { useDojo } from "./useDojo"; +import { v4 as uuidv4 } from "uuid"; + +export const useSystemCalls = () => { + const state = useDojoStore((state) => state); + + const { + setup: { client }, + account: { account }, + } = useDojo(); + + const generateEntityId = () => { + return getEntityIdFromKeys([BigInt(account?.address)]); + }; + + const spawn = async () => { + // Generate a unique entity ID + const entityId = generateEntityId(); + + // Generate a unique transaction ID + const transactionId = uuidv4(); + + // The value to update the Moves model with + const remainingMoves = 100; + + // Apply an optimistic update to the state + // this uses immer drafts to update the state + state.applyOptimisticUpdate( + transactionId, + (draft) => + (draft.entities[entityId].models.dojo_starter.Moves!.remaining = + remainingMoves) + ); + + try { + // Execute the spawn action from the client + await client.actions.spawn({ account }); + + // Wait for the entity to be updated with the new state + await state.waitForEntityChange(entityId, (entity) => { + return ( + entity?.models?.dojo_starter?.Moves?.remaining === + remainingMoves + ); + }); + } catch (error) { + // Revert the optimistic update if an error occurs + state.revertOptimisticUpdate(transactionId); + console.error("Error executing spawn:", error); + throw error; + } finally { + // Confirm the transaction if successful + state.confirmTransaction(transactionId); + } + }; + + return { + spawn, + }; +}; diff --git a/packages/sdk/readme.md b/packages/sdk/readme.md index b2a143a1..b55cbd8a 100644 --- a/packages/sdk/readme.md +++ b/packages/sdk/readme.md @@ -344,7 +344,79 @@ const subscription = await sdk.subscribeEntityQuery( ## Optimistic Client Rendering - +We use [immer](https://immerjs.github.io/immer/) for efficient optimistic rendering. This allows instant client-side entity state updates while awaiting blockchain confirmation. + +The process: + +1. Update entity state optimistically. +2. Wait for condition (e.g., a specific state change). +3. Resolve update, providing immediate user feedback. + +This ensures a responsive user experience while maintaining blockchain data integrity. + +See our [example project](../../examples/example-vite-react-sdk/src/useSystemCalls.ts) for a real-world implementation. + +Note: You will need to have a subscription running in order for the update to resolve. + +```typescript +export const useSystemCalls = () => { + const state = useDojoStore((state) => state); + + const { + setup: { client }, + account: { account }, + } = useDojo(); + + const generateEntityId = () => { + return getEntityIdFromKeys([BigInt(account?.address)]); + }; + + const spawn = async () => { + // Generate a unique entity ID + const entityId = generateEntityId(); + + // Generate a unique transaction ID + const transactionId = uuidv4(); + + // The value to update + const remainingMoves = 100; + + // Apply an optimistic update to the state + // this uses immer drafts to update the state + state.applyOptimisticUpdate( + transactionId, + (draft) => + (draft.entities[entityId].models.dojo_starter.Moves!.remaining = + remainingMoves) + ); + + try { + // Execute the spawn action + await client.actions.spawn({ account }); + + // Wait for the entity to be updated with the new state + await state.waitForEntityChange(entityId, (entity) => { + return ( + entity?.models?.dojo_starter?.Moves?.remaining === + remainingMoves + ); + }); + } catch (error) { + // Revert the optimistic update if an error occurs + state.revertOptimisticUpdate(transactionId); + console.error("Error executing spawn:", error); + throw error; + } finally { + // Confirm the transaction if successful + state.confirmTransaction(transactionId); + } + }; + + return { + spawn, + }; +}; +``` # Advanced Usage diff --git a/readme.md b/readme.md index 5d66c147..b08d7b76 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ If you are not familiar with Dojo, then you should read the [book](https://book. - [Quick start in 5 minutes](#quick-start-in-5-minutes) - [Dojo SDK](#dojo-sdk) -- [Examples](#examples) +- [Examples](#all-examples) - [All Packages](#all-packages) ## Quick start in 5 minutes @@ -70,7 +70,7 @@ pnpm i @dojoengine/sdk ``` > Basic example: [example-vite-react-sdk](./examples/example-vite-react-sdk/) -> More complex: [example-vite-react-sdk](./examples/example-vite-kitchen-sink/) +> More complex: [example-vite-kitchen-sink](./examples/example-vite-kitchen-sink/) ```js // Create client with your parameters @@ -153,13 +153,13 @@ Spin any of these examples locally npx @dojoengine/create-dojo start ``` -- [example-vite-react-sdk](./examples/example-vite-react-sdk/): A React application using Vite and the Dojo SDK -- [example-vite-react-phaser-recs](./examples/example-vite-react-phaser-recs/): A React application using Vite and the Dojo SDK -- [example-vite-react-pwa-recs](./examples/example-vite-react-pwa-recs/): A React application using Vite and the Dojo SDK -- [example-vite-react-threejs-recs](./examples/example-vite-react-threejs-recs/): A React application using Vite and the Dojo SDK -- [example-vue-app-recs](./examples/example-vue-app-recs/): A React application using Vite and the Dojo SDK -- [example-vite-kitchen-sink](./examples/example-vite-kitchen-sink/): A React application using Vite and the Dojo SDK -- [example-nodejs-bot](./examples/example-nodejs-bot/): A React application using Vite and the Dojo SDK +- [example-vite-react-sdk](./examples/example-vite-react-sdk/) +- [example-vite-react-phaser-recs](./examples/example-vite-react-phaser-recs/) +- [example-vite-react-pwa-recs](./examples/example-vite-react-pwa-recs/) +- [example-vite-react-threejs-recs](./examples/example-vite-react-threejs-recs/) +- [example-vue-app-recs](./examples/example-vue-app-recs/) +- [example-vite-kitchen-sink](./examples/example-vite-kitchen-sink/) +- [example-nodejs-bot](./examples/example-nodejs-bot/) ## Contributing to dojo.js