Skip to content

Commit

Permalink
feat(stash): add useStash and improve other helpers (#3320)
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Oct 24, 2024
1 parent 89697ff commit 93d0e76
Show file tree
Hide file tree
Showing 37 changed files with 669 additions and 519 deletions.
32 changes: 32 additions & 0 deletions .changeset/twenty-boats-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"@latticexyz/stash": patch
---

Added `useStash` React hook. It's heavily inspired by Zustand's `useStore` and accepts a stash, a state selector, an an optional equality function to avoid unnecessary re-render cycles when returning unstable values.

Also updated `getRecord` and `getRecords` to each take either a `stash` or `state` object for more ergonomic use with `useStash`.

```ts
import { useStash } from "@latticexyz/stash/react";
import { getRecord } from "@latticexyz/stash";
import config from "../mud.config";

const tables = config.namespaces.app.tables;

export function PlayerName({ playerId }) {
const record = useStash(stash, (state) => getRecord({ state, table: tables.Player, key: { playerId } }));
...
}
```

```ts
import isEqual from "fast-deep-equal";
import { useStash } from "@latticexyz/stash/react";
import { getRecords } from "@latticexyz/stash";
import config from "../mud.config";

export function PlayerNames() {
const record = useStash(stash, (state) => getRecords({ state, table: tables.Player }), { isEqual });
...
}
```
2 changes: 2 additions & 0 deletions packages/common/tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"noErrorTruncation": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
// TODO: enable this
// "noUncheckedIndexedAccess": true,
"sourceMap": true
}
}
7 changes: 7 additions & 0 deletions packages/stash/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": ["../../.eslintrc", "plugin:react/recommended", "plugin:react-hooks/recommended"],
"plugins": ["react", "react-hooks"],
"rules": {
"react/react-in-jsx-scope": "off"
}
}
18 changes: 14 additions & 4 deletions packages/stash/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,36 @@
"build": "tsup",
"clean": "shx rm -rf dist",
"dev": "tsup --watch",
"test": "vitest typecheck --run --passWithNoTests && vitest --run --passWithNoTests",
"test": "tsc -p tsconfig.test.json --noEmit && vitest --run",
"test:ci": "pnpm run test"
},
"dependencies": {
"@ark/util": "catalog:",
"@latticexyz/common": "workspace:*",
"@latticexyz/config": "workspace:*",
"@latticexyz/protocol-parser": "workspace:*",
"@latticexyz/schema-type": "workspace:*",
"@latticexyz/store": "workspace:*",
"react": "^18.2.0",
"viem": "catalog:"
},
"devDependencies": {
"@testing-library/react": "^16.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@types/react": "18.2.22",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"fast-deep-equal": "^3.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsup": "^6.7.0",
"vitest": "0.34.6"
"tsup": "^6.7.0"
},
"peerDependencies": {
"react": "18.x"
},
"publishConfig": {
"access": "public"
},
"optionalPeerDependencies": {
"react": "18.x"
}
}
10 changes: 5 additions & 5 deletions packages/stash/src/actions/decodeKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Table } from "@latticexyz/config";
import { Key, Stash } from "../common";
import { getKey } from "@latticexyz/protocol-parser/internal";

export type DecodeKeyArgs<table extends Table = Table> = {
stash: Stash;
Expand All @@ -14,9 +15,8 @@ export function decodeKey<table extends Table>({
table,
encodedKey,
}: DecodeKeyArgs<table>): DecodeKeyResult<table> {
const { namespaceLabel, label, key } = table;
const record = stash.get().records[namespaceLabel][label][encodedKey];

// Typecast needed because record values could be arrays, but we know they are not if they are key fields
return Object.fromEntries(Object.entries(record).filter(([field]) => key.includes(field))) as never;
const { namespaceLabel, label } = table;
const record = stash.get().records[namespaceLabel]?.[label]?.[encodedKey];
if (!record) throw new Error(`No record found for key "${encodedKey}".`);
return getKey(table, record);
}
6 changes: 3 additions & 3 deletions packages/stash/src/actions/deleteRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ export function deleteRecord<table extends Table>({ stash, table, key }: DeleteR
}

const encodedKey = encodeKey({ table, key });
const prevRecord = stash.get().records[namespaceLabel][label][encodedKey];
const prevRecord = stash.get().records[namespaceLabel]?.[label]?.[encodedKey];

// Early return if this record doesn't exist
if (prevRecord == null) return;

// Delete record
delete stash._.state.records[namespaceLabel][label][encodedKey];
delete stash._.state.records[namespaceLabel]?.[label]?.[encodedKey];

// Notify table subscribers
const updates = { [encodedKey]: { prev: prevRecord && { ...prevRecord }, current: undefined } };
stash._.tableSubscribers[namespaceLabel][label].forEach((subscriber) => subscriber(updates));
stash._.tableSubscribers[namespaceLabel]?.[label]?.forEach((subscriber) => subscriber(updates));

// Notify stash subscribers
const storeUpdate = { config: {}, records: { [namespaceLabel]: { [label]: updates } } };
Expand Down
7 changes: 3 additions & 4 deletions packages/stash/src/actions/getKeys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Table } from "@latticexyz/config";
import { getKey } from "@latticexyz/protocol-parser/internal";
import { Stash, Keys } from "../common";
import { decodeKey } from "./decodeKey";

export type GetKeysArgs<table extends Table = Table> = {
stash: Stash;
Expand All @@ -11,11 +11,10 @@ export type GetKeysResult<table extends Table = Table> = Keys<table>;

export function getKeys<table extends Table>({ stash, table }: GetKeysArgs<table>): GetKeysResult<table> {
const { namespaceLabel, label } = table;

return Object.fromEntries(
Object.keys(stash.get().records[namespaceLabel][label]).map((encodedKey) => [
Object.entries(stash.get().records[namespaceLabel]?.[label] ?? {}).map(([encodedKey, record]) => [
encodedKey,
decodeKey({ stash, table, encodedKey }),
getKey(table, record),
]),
);
}
Loading

0 comments on commit 93d0e76

Please sign in to comment.