Zustand
Zustand (opens in a new tab) is a state management library that supports JavaScript and TypeScript.
You can see it in use in the react template (opens in a new tab).
With React
Zustand is native to React.
The way you use it is very similar to the way you'd use useState, but with a bigger state tree that is synchronised with the contract state by MUD. To change state you send a transaction to a System.
Initialization
To initialize the synchronization you use syncToZustand (opens in a new tab).
import { syncToZustand } from "@latticexyz/store-sync/zustand";
 
const { tables, useStore, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToZustand({
  config: mudConfig,
  address: networkConfig.worldAddress as Hex,
  publicClient,
  startBlock: BigInt(networkConfig.initialBlockNumber),
});The most important fields returned by syncToZustand are:
- The tablesvariable, which contains the metadata (key schema, value schema, etc.) of the tables.
- The useStoreReact hook (opens in a new tab) you can use to read information from tables (opens in a new tab). It can also be used without React (opens in a new tab).
Get all records
To get all the records in a specific table you can use this syntax:
const records = useStore(state => Object.values(state.getRecords(tables.<table name>)))The result is a list of records.
For every field that is part of the key each record contains a key.<field name> field.
For every field that is part of the value each record contains a value.<field name> field.
For example, if you look at the Systems table (opens in a new tab), you might get these records:
[
  {
    "id": "0x7462776f726c6400000000000000000053797374656d73000000000000000000:0x73790000000000000000000000000000636f7265000000000000000000000000",
    "table": {
      "keySchema": {
        "systemId": {
          "type": "bytes32",
          "internalType": "ResourceId"
        }
      },
      "valueSchema": {
        "system": {
          "type": "address",
          "internalType": "address"
        },
        "publicAccess": {
          "type": "bool",
          "internalType": "bool"
        }
      },
      "namespace": "world",
      "name": "Systems",
      "tableId": "0x7462776f726c6400000000000000000053797374656d73000000000000000000"
    },
    "keyTuple": ["0x73790000000000000000000000000000636f7265000000000000000000000000"],
    "key": {
      "systemId": "0x73790000000000000000000000000000636f7265000000000000000000000000"
    },
    "value": {
      "system": "0xD041DF1408B365897dA363b3b2100057514CC725",
      "publicAccess": true
    }
  },
  {
    "id": "0x7462776f726c6400000000000000000053797374656d73000000000000000000:0x737900000000000000000000000000005461736b7353797374656d0000000000",
    "table": {
      "keySchema": {
        "systemId": {
          "type": "bytes32",
          "internalType": "ResourceId"
        }
      },
      "valueSchema": {
        "system": {
          "type": "address",
          "internalType": "address"
        },
        "publicAccess": {
          "type": "bool",
          "internalType": "bool"
        }
      },
      "namespace": "world",
      "name": "Systems",
      "tableId": "0x7462776f726c6400000000000000000053797374656d73000000000000000000"
    },
    "keyTuple": ["0x737900000000000000000000000000005461736b7353797374656d0000000000"],
    "key": {
      "systemId": "0x737900000000000000000000000000005461736b7353797374656d0000000000"
    },
    "value": {
      "system": "0x29E2e167C27caab62666E2E88B4e3BEB579E351F",
      "publicAccess": true
    }
  }
]Get a specific record
To get a specific record you can use getRecord.
For example, this code finds the record for the systemId that corresponds to :TasksSystem.
const key = {
  systemId: "0x737900000000000000000000000000005461736b7353797374656d0000000000",
};
const rec = useStore((state) => state.getRecord(tables.Systems, key));For the result:
{
  "id": "0x7462776f726c6400000000000000000053797374656d73000000000000000000:0x737900000000000000000000000000005461736b7353797374656d0000000000",
  "table": {
    "keySchema": {
      "systemId": {
        "type": "bytes32",
        "internalType": "ResourceId"
      }
    },
    "valueSchema": {
      "system": {
        "type": "address",
        "internalType": "address"
      },
      "publicAccess": {
        "type": "bool",
        "internalType": "bool"
      }
    },
    "namespace": "world",
    "name": "Systems",
    "tableId": "0x7462776f726c6400000000000000000053797374656d73000000000000000000"
  },
  "keyTuple": ["0x737900000000000000000000000000005461736b7353797374656d0000000000"],
  "key": {
    "systemId": "0x737900000000000000000000000000005461736b7353797374656d0000000000"
  },
  "value": {
    "system": "0x29E2e167C27caab62666E2E88B4e3BEB579E351F",
    "publicAccess": true
  }
}If you just want the value, you can use getValue.
const key = {
  systemId: "0x737900000000000000000000000000005461736b7353797374656d0000000000",
};
const val = useStore((state) => state.getValue(tables.Systems, key));For the result:
{
  "system": "0x29E2e167C27caab62666E2E88B4e3BEB579E351F",
  "publicAccess": true
}Without React
If your application does not use React, you can still use Zustand to read MUD data.
Initialization
import { syncToZustand } from "@latticexyz/store-sync/zustand";
 
const { tables, useStore, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToZustand({
  config: mudConfig,
  address: networkConfig.worldAddress as Hex,
  publicClient,
  startBlock: BigInt(networkConfig.initialBlockNumber),
});Reading data
To read data you use useStore.getState(). It supports the same functions as those supported in useStore(state => ...) in React.
- useStore.getState().getRecords(tables.<table name>)gives you all the records in a table.
- useStore.getState().getRecord(tables.<table name>, key)gives you a single record corresponding to that key.
- useStore.getState().getValue(tables.<table name>, key)gives you only the value corresponding to that key in the table without the extra information.
Seeing this in action
- 
Install the template and select the vanillatemplate.
- 
Change to the packages/client/srcdirectory inside the template.cd packages/client/src
- 
Edit mud/setupNetwork.tsto add the Zustand definitions.setupNetwork.ts/* * The MUD client code is built on top of viem * (https://viem.sh/docs/getting-started.html). * This line imports the functions we need from it. */ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig, } from "viem"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; import IWorldAbi from "contracts/out/IWorld.sol/IWorld.abi.json"; import { createBurnerAccount, getContract, transportObserver, ContractWrite } from "@latticexyz/common"; import { syncToZustand } from "@latticexyz/store-sync/zustand"; import { Subject, share } from "rxjs"; /* * Import our MUD config, which includes strong types for * our tables and other config options. We use this to generate * things like RECS components and get back strong types for them. * * See https://mud.dev/templates/typescript/contracts#mudconfigts * for the source of this information. */ import mudConfig from "contracts/mud.config"; export type SetupNetworkResult = Awaited<ReturnType<typeof setupNetwork>>; export async function setupNetwork() { const networkConfig = await getNetworkConfig(); /* * Create a viem public (read only) client * (https://viem.sh/docs/clients/public.html) */ const clientOptions = { chain: networkConfig.chain, transport: transportObserver(fallback([webSocket(), http()])), pollingInterval: 1000, } as const satisfies ClientConfig; const publicClient = createPublicClient(clientOptions); /* * Create a temporary wallet and a viem client for it * (see https://viem.sh/docs/clients/wallet.html). */ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex); const burnerWalletClient = createWalletClient({ ...clientOptions, account: burnerAccount, }); /* * Create an observable for contract writes that we can * pass into MUD dev tools for transaction observability. */ const write$ = new Subject<ContractWrite>(); /* * Create an object for communicating with the deployed World. */ const worldContract = getContract({ address: networkConfig.worldAddress as Hex, abi: IWorldAbi, publicClient, walletClient: burnerWalletClient, onWrite: (write) => write$.next(write), }); /* * Sync on-chain state into RECS and keeps our client in sync. * Uses the MUD indexer if available, otherwise falls back * to the viem publicClient to make RPC calls to fetch MUD * events from the chain. */ const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, address: networkConfig.worldAddress as Hex, publicClient, startBlock: BigInt(networkConfig.initialBlockNumber), }); const { tables, useStore, /* latestBlock$, storedBlockLogs$, waitForTransaction */ } = await syncToZustand({ config: mudConfig, address: networkConfig.worldAddress as Hex, publicClient, startBlock: BigInt(networkConfig.initialBlockNumber), }); return { tables, useStore, world, components, playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }), publicClient, walletClient: burnerWalletClient, latestBlock$, storedBlockLogs$, waitForTransaction, worldContract, write$: write$.asObservable().pipe(share()), }; }
- 
Edit index.tsto create a function that reads from Zustand.index.tsimport { setup } from "./mud/setup"; import mudConfig from "contracts/mud.config"; const { components, systemCalls: { increment }, network, } = await setup(); // Components expose a stream that triggers when the component is updated. components.Counter.update$.subscribe((update) => { const [nextValue, prevValue] = update.value; console.log("Counter updated", update, { nextValue, prevValue }); document.getElementById("counter")!.innerHTML = String(nextValue?.value ?? "unset"); }); // Just for demonstration purposes: we create a global function that can be // called to invoke the Increment system contract via the world. (See IncrementSystem.sol.) (window as any).increment = async () => { console.log("new counter value:", await increment()); }; (window as any).zustand = () => { console.log("Records:"); const records = Object.values(network.useStore.getState().getRecords(network.tables.Systems)); console.log(records); const key = { systemId: "0x73790000000000000000000000000000636f7265000000000000000000000000" }; const rec = network.useStore.getState().getRecord(network.tables.Systems, key); console.log("\nSingle record:"); console.log(rec); const val = network.useStore.getState().getValue(network.tables.Systems, key); console.log("\nJust the value from the record:"); console.log(val); }; // https://vitejs.dev/guide/env-and-mode.html if (import.meta.env.DEV) { const { mount: mountDevTools } = await import("@latticexyz/dev-tools"); mountDevTools({ config: mudConfig, publicClient: network.publicClient, walletClient: network.walletClient, latestBlock$: network.latestBlock$, storedBlockLogs$: network.storedBlockLogs$, worldAddress: network.worldContract.address, worldAbi: network.worldContract.abi, write$: network.write$, recsWorld: network.world, }); }
- 
Run the application. cd ../../.. pnpm dev
- 
Browse to the application. 
- 
Open the console (see Chrome instructions) (opens in a new tab). 
- 
Run this command to call zustandwhich we defined inindex.ts:window.zustand();
- 
See (in the console) the results: - List of all the records (in :Systems).
- The record for a specific System.
- Just the value for that System.
 
- List of all the records (in