@latticexyz/react 2.0.0-alpha.1.21 → 2.0.0-alpha.1.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,11 +2,41 @@
2
2
 
3
3
  React hooks (and more) for building MUD clients.
4
4
 
5
+ ## Hooks for `store-cache`
6
+
7
+ ### useRows
8
+
9
+ Returns an array of all rows matching the provided filter. Re-renders if the filter changes or if table entries matching the provided filter change.
10
+
11
+ ```typescript
12
+ // get a list of all entries in the database
13
+ const allRows = useRows(storeCache);
14
+ // -> [ { namespace: "", table: "Position", key: { key: "0x01" }, value: { x: 1, y: 2 } }, ... ]
15
+
16
+ // get a list of all entries in the Position table
17
+ const allPositionRows = useRows(storeCache, { table: "Position" });
18
+
19
+ // get a list of all entries in the position table with key greater than `0x0A`
20
+ const filteredRows = useRows(storeCache, { table: "Position", key: { gt: { key: "0x0A" } } });
21
+ ```
22
+
23
+ ### useRow
24
+
25
+ Returns a single row with the provided key in the provided table. Re-renders if the filter changes or if the value of this row changes.
26
+
27
+ ```typescript
28
+ // get the Position value of key `0x01`
29
+ const position = useRow(storeCache, { table: "Position", key: { key: "0x01" } });
30
+ // -> { namespace: "", table: "Position", key: { key: "0x01" }, value: { x: 1, y: 2 } }
31
+ ```
32
+
33
+ ## Hooks for `recs`
34
+
5
35
  ### useComponentValue
6
36
 
7
37
  Returns the value of the component for the entity, and triggers a re-render as the component is added/removed/updated.
8
38
 
9
- ```
39
+ ```typescript
10
40
  const position = useComponentValue(Position, entity);
11
41
  ```
12
42
 
@@ -14,7 +44,7 @@ const position = useComponentValue(Position, entity);
14
44
 
15
45
  Returns all matching `EntityIndex`es for a given entity query, and triggers a re-render as new query results come in.
16
46
 
17
- ```
47
+ ```typescript
18
48
  const entities = useEntityQuery([Has(Position)]);
19
49
  const playersAtPosition = useEntityQuery([Has(Player), HasValue(Position, { x: 0, y: 0 })]);
20
50
  ```
package/dist/index.js CHANGED
@@ -1,128 +1,2 @@
1
- import { getComponentValue, defineQuery, Has, isComponentUpdate } from '@latticexyz/recs';
2
- import { useState, useEffect, useMemo } from 'react';
3
- import { map, distinctUntilChanged } from 'rxjs';
4
-
5
- function useComponentValue(component, entityIndex, defaultValue) {
6
- const [value, setValue] = useState(entityIndex != null ? getComponentValue(component, entityIndex) : undefined);
7
- useEffect(() => {
8
- // component or entityIndex changed, update state to latest value
9
- setValue(entityIndex != null ? getComponentValue(component, entityIndex) : undefined);
10
- if (entityIndex == null)
11
- return;
12
- const queryResult = defineQuery([Has(component)], { runOnInit: false });
13
- const subscription = queryResult.update$.subscribe((update) => {
14
- if (isComponentUpdate(update, component) && update.entity === entityIndex) {
15
- const [nextValue] = update.value;
16
- setValue(nextValue);
17
- }
18
- });
19
- return () => subscription.unsubscribe();
20
- }, [component, entityIndex]);
21
- return value ?? defaultValue;
22
- }
23
-
24
- /** @deprecated See https://github.com/latticexyz/mud/issues/339 */
25
- const useDeprecatedComputedValue = (computedValue) => {
26
- const [value, setValue] = useState(computedValue.get());
27
- useEffect(() => {
28
- const unsubscribe = computedValue.observe_(() => setValue(computedValue.get()));
29
- return () => unsubscribe();
30
- }, [computedValue]);
31
- return value;
32
- };
33
-
34
- // do not edit .js files directly - edit src/index.jst
35
-
36
-
37
-
38
- var fastDeepEqual = function equal(a, b) {
39
- if (a === b) return true;
40
-
41
- if (a && b && typeof a == 'object' && typeof b == 'object') {
42
- if (a.constructor !== b.constructor) return false;
43
-
44
- var length, i, keys;
45
- if (Array.isArray(a)) {
46
- length = a.length;
47
- if (length != b.length) return false;
48
- for (i = length; i-- !== 0;)
49
- if (!equal(a[i], b[i])) return false;
50
- return true;
51
- }
52
-
53
-
54
-
55
- if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
56
- if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
57
- if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
58
-
59
- keys = Object.keys(a);
60
- length = keys.length;
61
- if (length !== Object.keys(b).length) return false;
62
-
63
- for (i = length; i-- !== 0;)
64
- if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
65
-
66
- for (i = length; i-- !== 0;) {
67
- var key = keys[i];
68
-
69
- if (!equal(a[key], b[key])) return false;
70
- }
71
-
72
- return true;
73
- }
74
-
75
- // true if both NaN, false otherwise
76
- return a!==a && b!==b;
77
- };
78
-
79
- const useDeepMemo = (currentValue) => {
80
- const [stableValue, setStableValue] = useState(currentValue);
81
- useEffect(() => {
82
- if (!fastDeepEqual(currentValue, stableValue)) {
83
- setStableValue(currentValue);
84
- }
85
- // eslint-disable-next-line react-hooks/exhaustive-deps
86
- }, [currentValue]);
87
- return stableValue;
88
- };
89
-
90
- // This does a little more rendering than is necessary when arguments change,
91
- // but at least it's giving correct results now. Will optimize later!
92
- /**
93
- * Returns all matching `EntityIndex`es for a given entity query,
94
- * and triggers a re-render as new query results come in.
95
- *
96
- * @param fragments Query fragments to match against, executed from left to right.
97
- * @param options.updateOnValueChange False - re-renders only on entity array changes. True (default) - also on component value changes.
98
- * @returns Set of entities matching the query fragments.
99
- */
100
- function useEntityQuery(fragments, options) {
101
- const updateOnValueChange = options?.updateOnValueChange ?? true;
102
- const stableFragments = useDeepMemo(fragments);
103
- const query = useMemo(() => defineQuery(stableFragments, { runOnInit: true }), [stableFragments]);
104
- const [entities, setEntities] = useState([...query.matching]);
105
- useEffect(() => {
106
- setEntities([...query.matching]);
107
- let observable = query.update$.pipe(map(() => [...query.matching]));
108
- if (!updateOnValueChange) {
109
- // re-render only on entity array changes
110
- observable = observable.pipe(distinctUntilChanged((a, b) => fastDeepEqual(a, b)));
111
- }
112
- const subscription = observable.subscribe((entities) => setEntities(entities));
113
- return () => subscription.unsubscribe();
114
- }, [query, updateOnValueChange]);
115
- return entities;
116
- }
117
-
118
- function useObservableValue(observable, defaultValue) {
119
- const [value, setValue] = useState(defaultValue);
120
- useEffect(() => {
121
- const subscription = observable.subscribe(setValue);
122
- return () => subscription.unsubscribe();
123
- }, [observable]);
124
- return value;
125
- }
126
-
127
- export { useComponentValue, useDeprecatedComputedValue, useEntityQuery, useObservableValue };
128
- //# sourceMappingURL=index.js.map
1
+ import{defineQuery as c,getComponentValue as p,Has as d,isComponentUpdate as C}from"@latticexyz/recs";import{useEffect as S,useState as x}from"react";function z(e,t,n){let[s,o]=x(t!=null?p(e,t):void 0);return S(()=>{if(o(t!=null?p(e,t):void 0),t==null)return;let u=c([d(e)],{runOnInit:!1}).update$.subscribe(r=>{if(C(r,e)&&r.entity===t){let[m]=r.value;o(m)}});return()=>u.unsubscribe()},[e,t]),s??n}import{useEffect as T,useState as g}from"react";var G=e=>{let[t,n]=g(e.get());return T(()=>{let s=e.observe_(()=>n(e.get()));return()=>s()},[e]),t};import{defineQuery as v}from"@latticexyz/recs";import{useEffect as E,useMemo as R,useState as h}from"react";import{useEffect as y,useState as V}from"react";import O from"fast-deep-equal";var a=e=>{let[t,n]=V(e);return y(()=>{O(e,t)||n(e)},[e]),t};import M from"fast-deep-equal";import{distinctUntilChanged as D,map as k}from"rxjs";function ne(e,t){let n=t?.updateOnValueChange??!0,s=a(e),o=R(()=>v(s,{runOnInit:!0}),[s]),[i,u]=h([...o.matching]);return E(()=>{u([...o.matching]);let r=o.update$.pipe(k(()=>[...o.matching]));n||(r=r.pipe(D((f,b)=>M(f,b))));let m=r.subscribe(f=>u(f));return()=>m.unsubscribe()},[o,n]),i}import{useEffect as w,useState as F}from"react";function re(e,t){let[n,s]=F(t);return w(()=>{let o=e.subscribe(s);return()=>o.unsubscribe()},[e]),n}import{useEffect as q,useState as Q}from"react";function l(e,t){let[n,s]=Q([]),o=a(t);return q(()=>(s(e.scan(t)),e.subscribe(()=>{s(e.scan(t))},t)),[o]),n}function pe(e,t){let{namespace:n,table:s,key:o}=t;return l(e,{namespace:n,table:s,key:{eq:o}})[0]}export{z as useComponentValue,G as useDeprecatedComputedValue,ne as useEntityQuery,re as useObservableValue,pe as useRow,l as useRows};
2
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/useComponentValue.ts","../src/useDeprecatedComputedValue.ts","../../../node_modules/.pnpm/fast-deep-equal@3.1.3/node_modules/fast-deep-equal/index.js","../src/utils/useDeepMemo.ts","../src/useEntityQuery.ts","../src/useObservableValue.ts"],"sourcesContent":[null,null,"'use strict';\n\n// do not edit .js files directly - edit src/index.jst\n\n\n\nmodule.exports = function equal(a, b) {\n if (a === b) return true;\n\n if (a && b && typeof a == 'object' && typeof b == 'object') {\n if (a.constructor !== b.constructor) return false;\n\n var length, i, keys;\n if (Array.isArray(a)) {\n length = a.length;\n if (length != b.length) return false;\n for (i = length; i-- !== 0;)\n if (!equal(a[i], b[i])) return false;\n return true;\n }\n\n\n\n if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;\n if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();\n if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();\n\n keys = Object.keys(a);\n length = keys.length;\n if (length !== Object.keys(b).length) return false;\n\n for (i = length; i-- !== 0;)\n if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;\n\n for (i = length; i-- !== 0;) {\n var key = keys[i];\n\n if (!equal(a[key], b[key])) return false;\n }\n\n return true;\n }\n\n // true if both NaN, false otherwise\n return a!==a && b!==b;\n};\n",null,null,null],"names":["isEqual"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/useComponentValue.ts","../src/useDeprecatedComputedValue.ts","../src/useEntityQuery.ts","../src/utils/useDeepMemo.ts","../src/useObservableValue.ts","../src/store-cache/useRows.ts","../src/store-cache/useRow.ts"],"sourcesContent":["import {\n Component,\n ComponentValue,\n defineQuery,\n Entity,\n getComponentValue,\n Has,\n isComponentUpdate,\n Metadata,\n Schema,\n} from \"@latticexyz/recs\";\nimport { useEffect, useState } from \"react\";\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S, Metadata, undefined>,\n entity: Entity | undefined,\n defaultValue: ComponentValue<S>\n): ComponentValue<S>;\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S, Metadata, undefined>,\n entity: Entity | undefined\n): ComponentValue<S> | undefined;\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S, Metadata, undefined>,\n entity: Entity | undefined,\n defaultValue?: ComponentValue<S>\n) {\n const [value, setValue] = useState(entity != null ? getComponentValue(component, entity) : undefined);\n\n useEffect(() => {\n // component or entity changed, update state to latest value\n setValue(entity != null ? getComponentValue(component, entity) : undefined);\n if (entity == null) return;\n\n const queryResult = defineQuery([Has(component)], { runOnInit: false });\n const subscription = queryResult.update$.subscribe((update) => {\n if (isComponentUpdate(update, component) && update.entity === entity) {\n const [nextValue] = update.value;\n setValue(nextValue);\n }\n });\n return () => subscription.unsubscribe();\n }, [component, entity]);\n\n return value ?? defaultValue;\n}\n","import { IComputedValue } from \"mobx\";\nimport { useEffect, useState } from \"react\";\n\n/** @deprecated See https://github.com/latticexyz/mud/issues/339 */\nexport const useDeprecatedComputedValue = <T>(computedValue: IComputedValue<T> & { observe_: any }) => {\n const [value, setValue] = useState<T>(computedValue.get());\n\n useEffect(() => {\n const unsubscribe = computedValue.observe_(() => setValue(computedValue.get()));\n return () => unsubscribe();\n }, [computedValue]);\n\n return value;\n};\n","import { defineQuery, QueryFragment } from \"@latticexyz/recs\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useDeepMemo } from \"./utils/useDeepMemo\";\nimport isEqual from \"fast-deep-equal\";\nimport { distinctUntilChanged, map } from \"rxjs\";\n\n// This does a little more rendering than is necessary when arguments change,\n// but at least it's giving correct results now. Will optimize later!\n\n/**\n * Returns all matching entities for a given entity query,\n * and triggers a re-render as new query results come in.\n *\n * @param fragments Query fragments to match against, executed from left to right.\n * @param options.updateOnValueChange False - re-renders only on entity array changes. True (default) - also on component value changes.\n * @returns Set of entities matching the query fragments.\n */\nexport function useEntityQuery(fragments: QueryFragment[], options?: { updateOnValueChange?: boolean }) {\n const updateOnValueChange = options?.updateOnValueChange ?? true;\n\n const stableFragments = useDeepMemo(fragments);\n const query = useMemo(() => defineQuery(stableFragments, { runOnInit: true }), [stableFragments]);\n const [entities, setEntities] = useState([...query.matching]);\n\n useEffect(() => {\n setEntities([...query.matching]);\n let observable = query.update$.pipe(map(() => [...query.matching]));\n if (!updateOnValueChange) {\n // re-render only on entity array changes\n observable = observable.pipe(distinctUntilChanged((a, b) => isEqual(a, b)));\n }\n const subscription = observable.subscribe((entities) => setEntities(entities));\n return () => subscription.unsubscribe();\n }, [query, updateOnValueChange]);\n\n return entities;\n}\n","import { useEffect, useState } from \"react\";\nimport isEqual from \"fast-deep-equal\";\n\nexport const useDeepMemo = <T>(currentValue: T): T => {\n const [stableValue, setStableValue] = useState(currentValue);\n\n useEffect(() => {\n if (!isEqual(currentValue, stableValue)) {\n setStableValue(currentValue);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentValue]);\n\n return stableValue;\n};\n","import { useEffect, useState } from \"react\";\nimport { Observable } from \"rxjs\";\n\nexport function useObservableValue<T>(observable: Observable<T>, defaultValue: T): T;\n\nexport function useObservableValue<T>(observable: Observable<T>): T | undefined;\n\nexport function useObservableValue<T>(observable: Observable<T>, defaultValue?: T) {\n const [value, setValue] = useState(defaultValue);\n\n useEffect(() => {\n const subscription = observable.subscribe(setValue);\n return () => subscription.unsubscribe();\n }, [observable]);\n\n return value;\n}\n","import { DatabaseClient, FilterOptions, ScanResult } from \"@latticexyz/store-cache\";\nimport { StoreConfig } from \"@latticexyz/store\";\nimport { useEffect, useState } from \"react\";\nimport { useDeepMemo } from \"../utils/useDeepMemo\";\n\n/**\n * Returns an array of all rows matching the provided filter\n */\nexport function useRows<C extends StoreConfig, T extends keyof C[\"tables\"] & string>(\n storeCache: DatabaseClient<C>,\n filter?: FilterOptions<C, T>\n) {\n const [rows, setRows] = useState<ScanResult<C, T>>([]);\n const filterMemo = useDeepMemo(filter);\n\n useEffect(() => {\n setRows(storeCache.scan(filter));\n\n const unsubscribe = storeCache.subscribe(() => {\n // very naive implementation for now, but easier and probably more efficient than\n // manually looping through the `rows` array for every update event\n setRows(storeCache.scan(filter));\n }, filter);\n\n return unsubscribe;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [filterMemo]);\n\n return rows;\n}\n","import { StoreConfig } from \"@latticexyz/store\";\nimport { DatabaseClient, Key, ScanResult } from \"@latticexyz/store-cache\";\nimport { useRows } from \"./useRows\";\n\nexport type UseRowFilterOptions<\n C extends StoreConfig = StoreConfig,\n T extends keyof C[\"tables\"] & string = keyof C[\"tables\"] & string\n> = {\n namespace?: C[\"namespace\"];\n table: T;\n key: Key<C, T>;\n};\n\n/**\n * Returns a single row of a given table at the given key, updates when the key changes\n */\nexport function useRow<C extends StoreConfig, T extends keyof C[\"tables\"] & string>(\n storeCache: DatabaseClient<C>,\n filter: UseRowFilterOptions<C, T>\n): ScanResult<C, T>[0] | undefined {\n const { namespace, table, key } = filter;\n return useRows(storeCache, { namespace, table, key: { eq: key } })[0];\n}\n"],"mappings":"AAAA,OAGE,eAAAA,EAEA,qBAAAC,EACA,OAAAC,EACA,qBAAAC,MAGK,mBACP,OAAS,aAAAC,EAAW,YAAAC,MAAgB,QAa7B,SAASC,EACdC,EACAC,EACAC,EACA,CACA,GAAM,CAACC,EAAOC,CAAQ,EAAIN,EAASG,GAAU,KAAOP,EAAkBM,EAAWC,CAAM,EAAI,MAAS,EAEpG,OAAAJ,EAAU,IAAM,CAGd,GADAO,EAASH,GAAU,KAAOP,EAAkBM,EAAWC,CAAM,EAAI,MAAS,EACtEA,GAAU,KAAM,OAGpB,IAAMI,EADcZ,EAAY,CAACE,EAAIK,CAAS,CAAC,EAAG,CAAE,UAAW,EAAM,CAAC,EACrC,QAAQ,UAAWM,GAAW,CAC7D,GAAIV,EAAkBU,EAAQN,CAAS,GAAKM,EAAO,SAAWL,EAAQ,CACpE,GAAM,CAACM,CAAS,EAAID,EAAO,MAC3BF,EAASG,CAAS,EAEtB,CAAC,EACD,MAAO,IAAMF,EAAa,YAAY,CACxC,EAAG,CAACL,EAAWC,CAAM,CAAC,EAEfE,GAASD,CAClB,CC9CA,OAAS,aAAAM,EAAW,YAAAC,MAAgB,QAG7B,IAAMC,EAAiCC,GAAyD,CACrG,GAAM,CAACC,EAAOC,CAAQ,EAAIJ,EAAYE,EAAc,IAAI,CAAC,EAEzD,OAAAH,EAAU,IAAM,CACd,IAAMM,EAAcH,EAAc,SAAS,IAAME,EAASF,EAAc,IAAI,CAAC,CAAC,EAC9E,MAAO,IAAMG,EAAY,CAC3B,EAAG,CAACH,CAAa,CAAC,EAEXC,CACT,ECbA,OAAS,eAAAG,MAAkC,mBAC3C,OAAS,aAAAC,EAAW,WAAAC,EAAS,YAAAC,MAAgB,QCD7C,OAAS,aAAAC,EAAW,YAAAC,MAAgB,QACpC,OAAOC,MAAa,kBAEb,IAAMC,EAAkBC,GAAuB,CACpD,GAAM,CAACC,EAAaC,CAAc,EAAIL,EAASG,CAAY,EAE3D,OAAAJ,EAAU,IAAM,CACTE,EAAQE,EAAcC,CAAW,GACpCC,EAAeF,CAAY,CAG/B,EAAG,CAACA,CAAY,CAAC,EAEVC,CACT,EDXA,OAAOE,MAAa,kBACpB,OAAS,wBAAAC,EAAsB,OAAAC,MAAW,OAanC,SAASC,GAAeC,EAA4BC,EAA6C,CACtG,IAAMC,EAAsBD,GAAS,qBAAuB,GAEtDE,EAAkBC,EAAYJ,CAAS,EACvCK,EAAQC,EAAQ,IAAMC,EAAYJ,EAAiB,CAAE,UAAW,EAAK,CAAC,EAAG,CAACA,CAAe,CAAC,EAC1F,CAACK,EAAUC,CAAW,EAAIC,EAAS,CAAC,GAAGL,EAAM,QAAQ,CAAC,EAE5D,OAAAM,EAAU,IAAM,CACdF,EAAY,CAAC,GAAGJ,EAAM,QAAQ,CAAC,EAC/B,IAAIO,EAAaP,EAAM,QAAQ,KAAKP,EAAI,IAAM,CAAC,GAAGO,EAAM,QAAQ,CAAC,CAAC,EAC7DH,IAEHU,EAAaA,EAAW,KAAKf,EAAqB,CAACgB,EAAG,IAAMjB,EAAQiB,EAAG,CAAC,CAAC,CAAC,GAE5E,IAAMC,EAAeF,EAAW,UAAWJ,GAAaC,EAAYD,CAAQ,CAAC,EAC7E,MAAO,IAAMM,EAAa,YAAY,CACxC,EAAG,CAACT,EAAOH,CAAmB,CAAC,EAExBM,CACT,CEpCA,OAAS,aAAAO,EAAW,YAAAC,MAAgB,QAO7B,SAASC,GAAsBC,EAA2BC,EAAkB,CACjF,GAAM,CAACC,EAAOC,CAAQ,EAAIL,EAASG,CAAY,EAE/C,OAAAJ,EAAU,IAAM,CACd,IAAMO,EAAeJ,EAAW,UAAUG,CAAQ,EAClD,MAAO,IAAMC,EAAa,YAAY,CACxC,EAAG,CAACJ,CAAU,CAAC,EAERE,CACT,CCdA,OAAS,aAAAG,EAAW,YAAAC,MAAgB,QAM7B,SAASC,EACdC,EACAC,EACA,CACA,GAAM,CAACC,EAAMC,CAAO,EAAIC,EAA2B,CAAC,CAAC,EAC/CC,EAAaC,EAAYL,CAAM,EAErC,OAAAM,EAAU,KACRJ,EAAQH,EAAW,KAAKC,CAAM,CAAC,EAEXD,EAAW,UAAU,IAAM,CAG7CG,EAAQH,EAAW,KAAKC,CAAM,CAAC,CACjC,EAAGA,CAAM,GAIR,CAACI,CAAU,CAAC,EAERH,CACT,CCbO,SAASM,GACdC,EACAC,EACiC,CACjC,GAAM,CAAE,UAAAC,EAAW,MAAAC,EAAO,IAAAC,CAAI,EAAIH,EAClC,OAAOI,EAAQL,EAAY,CAAE,UAAAE,EAAW,MAAAC,EAAO,IAAK,CAAE,GAAIC,CAAI,CAAE,CAAC,EAAE,CAAC,CACtE","names":["defineQuery","getComponentValue","Has","isComponentUpdate","useEffect","useState","useComponentValue","component","entity","defaultValue","value","setValue","subscription","update","nextValue","useEffect","useState","useDeprecatedComputedValue","computedValue","value","setValue","unsubscribe","defineQuery","useEffect","useMemo","useState","useEffect","useState","isEqual","useDeepMemo","currentValue","stableValue","setStableValue","isEqual","distinctUntilChanged","map","useEntityQuery","fragments","options","updateOnValueChange","stableFragments","useDeepMemo","query","useMemo","defineQuery","entities","setEntities","useState","useEffect","observable","a","subscription","useEffect","useState","useObservableValue","observable","defaultValue","value","setValue","subscription","useEffect","useState","useRows","storeCache","filter","rows","setRows","useState","filterMemo","useDeepMemo","useEffect","useRow","storeCache","filter","namespace","table","key","useRows"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latticexyz/react",
3
- "version": "2.0.0-alpha.1.21+4ad66187",
3
+ "version": "2.0.0-alpha.1.211+80a9a33b",
4
4
  "description": "React tools for MUD client.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,49 +8,41 @@
8
8
  "directory": "packages/react"
9
9
  },
10
10
  "license": "MIT",
11
- "main": "dist/index.js",
12
- "source": "src/index.ts",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": "./dist/index.js"
14
+ },
15
+ "types": "src/index.ts",
13
16
  "scripts": {
14
- "build": "rimraf dist && rollup -c rollup.config.js",
17
+ "build": "pnpm run build:js",
18
+ "build:js": "tsup",
19
+ "clean": "pnpm run clean:js",
20
+ "clean:js": "rimraf dist",
21
+ "dev": "tsup --watch",
15
22
  "docs": "rimraf API && typedoc src && find API -type f -name '*.md' -exec sed -E -i \"\" \"s/(#.*)(<.*>)/\\1/\" {} \\;",
16
- "prepack": "mv package.json package.json.bak && jq \".main = \\\"dist/index.js\\\"\" package.json.bak > package.json ",
17
- "postpack": "mv package.json.bak package.json || echo 'no package.json.bak'",
18
- "release": "npm publish --access=public",
19
- "test": "tsc --noEmit && jest"
23
+ "test": "tsc --noEmit && vitest --run"
20
24
  },
21
25
  "dependencies": {
22
- "fast-deep-equal": "^3.1.3"
26
+ "@latticexyz/recs": "2.0.0-alpha.1.211+80a9a33b",
27
+ "@latticexyz/store": "2.0.0-alpha.1.211+80a9a33b",
28
+ "@latticexyz/store-cache": "2.0.0-alpha.1.211+80a9a33b",
29
+ "fast-deep-equal": "^3.1.3",
30
+ "mobx": "^6.7.0",
31
+ "react": "^18.2.0",
32
+ "rxjs": "^7.5.5"
23
33
  },
24
34
  "devDependencies": {
25
- "@latticexyz/recs": "2.0.0-alpha.1.21+4ad66187",
26
- "@rollup/plugin-json": "^4.1.0",
27
- "@rollup/plugin-node-resolve": "^13.1.3",
28
- "@rollup/plugin-typescript": "^11.0.0",
29
35
  "@testing-library/react-hooks": "^8.0.1",
30
- "@types/jest": "^27.4.1",
31
- "@types/react": "^18.0.12",
32
- "eslint-plugin-react": "^7.31.11",
33
- "eslint-plugin-react-hooks": "^4.6.0",
34
- "jest": "^29.3.1",
35
- "mobx": "^6.4.2",
36
- "react": "^18.2.0",
36
+ "@types/react": "^18.2.6",
37
+ "@vitejs/plugin-react": "^4.0.0",
38
+ "eslint-plugin-react": "7.31.11",
39
+ "eslint-plugin-react-hooks": "4.6.0",
37
40
  "react-test-renderer": "^18.2.0",
38
- "rimraf": "^3.0.2",
39
- "rollup": "^2.69.0",
40
- "rollup-plugin-commonjs": "^10.1.0",
41
- "rollup-plugin-peer-deps-external": "^2.2.4",
42
- "rxjs": "^7.5.5",
43
- "ts-jest": "^29.0.5",
44
- "tslib": "^2.5.0",
41
+ "tsup": "^6.7.0",
45
42
  "typedoc": "0.23.21",
46
43
  "typedoc-plugin-markdown": "^3.13.6",
47
- "typescript": "^4.9.5"
48
- },
49
- "peerDependencies": {
50
- "@latticexyz/recs": "latest",
51
- "mobx": "^6.4.2",
52
- "react": "^18.2.0",
53
- "rxjs": "^7.5.5"
44
+ "vite": "^4.3.6",
45
+ "vitest": "^0.31.0"
54
46
  },
55
- "gitHead": "4ad661878bcf916a9ab7fd4d72d051b372af74c5"
47
+ "gitHead": "80a9a33bc79f22d347f72c7ba9d99a11f40e7fcd"
56
48
  }
@@ -2,3 +2,4 @@ export * from "./useComponentValue";
2
2
  export * from "./useDeprecatedComputedValue";
3
3
  export * from "./useEntityQuery";
4
4
  export * from "./useObservableValue";
5
+ export * from "./store-cache";
@@ -0,0 +1,2 @@
1
+ export * from "./useRows";
2
+ export * from "./useRow";
@@ -0,0 +1,185 @@
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
+ import { UseRowFilterOptions, useRow } from "./useRow";
3
+ import { mudConfig } from "@latticexyz/store/register";
4
+ import { KeyValue, createDatabase, createDatabaseClient } from "@latticexyz/store-cache";
5
+ import { describe, it, beforeEach, expect } from "vitest";
6
+
7
+ const config = mudConfig({
8
+ tables: {
9
+ MultiKey: { keySchema: { first: "bytes32", second: "uint32" }, schema: "int32" },
10
+ Position: { schema: { x: "int32", y: "int32" } },
11
+ },
12
+ });
13
+
14
+ describe("useRow", () => {
15
+ let db: ReturnType<typeof createDatabase>;
16
+ let client: ReturnType<typeof createDatabaseClient<typeof config>>;
17
+
18
+ beforeEach(() => {
19
+ db = createDatabase();
20
+ client = createDatabaseClient(db, config);
21
+ });
22
+
23
+ it("should return the row of the position table with the specified key", () => {
24
+ const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x01" } }));
25
+ expect(result.current).toBe(undefined);
26
+
27
+ const positionUpdates: KeyValue<typeof config, "Position">[] = [
28
+ { key: { key: "0x00" }, value: { x: 1, y: 2 } },
29
+ { key: { key: "0x01" }, value: { x: 2, y: 3 } },
30
+ { key: { key: "0x02" }, value: { x: 3, y: 4 } },
31
+ { key: { key: "0x03" }, value: { x: 4, y: 5 } },
32
+ ];
33
+
34
+ const multiKeyUpdates: KeyValue<typeof config, "MultiKey">[] = [
35
+ { key: { first: "0x00", second: 4 }, value: { value: 1 } },
36
+ { key: { first: "0x01", second: 3 }, value: { value: 2 } },
37
+ { key: { first: "0x02", second: 2 }, value: { value: 3 } },
38
+ { key: { first: "0x03", second: 1 }, value: { value: 4 } },
39
+ ];
40
+
41
+ act(() => {
42
+ // Set values in the tables
43
+ for (const update of positionUpdates) client.tables.Position.set(update.key, update.value);
44
+ for (const update of multiKeyUpdates) client.tables.MultiKey.set(update.key, update.value);
45
+ });
46
+
47
+ expect(result.current).toEqual({
48
+ key: { key: "0x01" },
49
+ value: { x: 2, y: 3 },
50
+ namespace: config["namespace"],
51
+ table: "Position",
52
+ });
53
+
54
+ act(() => {
55
+ for (const update of positionUpdates.slice(0, 3)) client.tables.Position.remove(update.key);
56
+ });
57
+
58
+ expect(result.current).toBe(undefined);
59
+ });
60
+
61
+ it("should re-render only when the position value of the specified key changes", () => {
62
+ const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x00" } }));
63
+ expect(result.all.length).toBe(2);
64
+
65
+ // Update the position table
66
+ act(() => {
67
+ client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
68
+ });
69
+ expect(result.all.length).toBe(3);
70
+ expect(result.current).toEqual({
71
+ key: { key: "0x00" },
72
+ value: { x: 1, y: 2 },
73
+ namespace: config["namespace"],
74
+ table: "Position",
75
+ });
76
+
77
+ // Update an unrelated table
78
+ act(() => {
79
+ client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
80
+ });
81
+ expect(result.all.length).toBe(3);
82
+ expect(result.current).toEqual({
83
+ key: { key: "0x00" },
84
+ value: { x: 1, y: 2 },
85
+ namespace: config["namespace"],
86
+ table: "Position",
87
+ });
88
+
89
+ // Update the position table
90
+ act(() => {
91
+ client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
92
+ });
93
+ expect(result.all.length).toBe(4);
94
+ expect(result.current).toEqual({
95
+ key: { key: "0x00" },
96
+ value: { x: 2, y: 2 },
97
+ namespace: config["namespace"],
98
+ table: "Position",
99
+ });
100
+
101
+ // Update an unrelated key
102
+ act(() => {
103
+ client.tables.Position.set({ key: "0x01" }, { x: 2, y: 2 });
104
+ });
105
+ expect(result.all.length).toBe(4);
106
+ expect(result.current).toEqual({
107
+ key: { key: "0x00" },
108
+ value: { x: 2, y: 2 },
109
+ namespace: config["namespace"],
110
+ table: "Position",
111
+ });
112
+
113
+ // Update the position table
114
+ act(() => {
115
+ client.tables.Position.remove({ key: "0x00" });
116
+ });
117
+ expect(result.all.length).toBe(5);
118
+ expect(result.current).toEqual(undefined);
119
+ });
120
+
121
+ it("should re-render when the filter changes", () => {
122
+ const { result, rerender } = renderHook(({ filter }) => useRow(client, filter), {
123
+ initialProps: {
124
+ filter: { table: "Position", key: { key: "0x01" } } as UseRowFilterOptions<typeof config>,
125
+ },
126
+ });
127
+
128
+ expect(result.all.length).toBe(2);
129
+ expect(result.current).toBe(undefined);
130
+
131
+ const positionUpdates: KeyValue<typeof config, "Position">[] = [
132
+ { key: { key: "0x00" }, value: { x: 1, y: 2 } },
133
+ { key: { key: "0x01" }, value: { x: 2, y: 3 } },
134
+ { key: { key: "0x02" }, value: { x: 3, y: 4 } },
135
+ { key: { key: "0x03" }, value: { x: 4, y: 5 } },
136
+ ];
137
+
138
+ const multiKeyUpdates: KeyValue<typeof config, "MultiKey">[] = [
139
+ { key: { first: "0x00", second: 4 }, value: { value: 1 } },
140
+ { key: { first: "0x01", second: 3 }, value: { value: 2 } },
141
+ { key: { first: "0x02", second: 2 }, value: { value: 3 } },
142
+ { key: { first: "0x03", second: 1 }, value: { value: 4 } },
143
+ ];
144
+
145
+ act(() => {
146
+ // Set values in the tables
147
+ for (const update of positionUpdates) client.tables.Position.set(update.key, update.value);
148
+ for (const update of multiKeyUpdates) client.tables.MultiKey.set(update.key, update.value);
149
+ });
150
+
151
+ expect(result.all.length).toBe(3);
152
+ expect(result.current).toEqual({
153
+ key: { key: "0x01" },
154
+ value: { x: 2, y: 3 },
155
+ namespace: config["namespace"],
156
+ table: "Position",
157
+ });
158
+
159
+ // Change the filter
160
+ rerender({ filter: { table: "Position", key: { key: "0x02" } } });
161
+
162
+ // Expect hook to rerender three times:
163
+ // 1. New prop, everything else changes the same
164
+ // 2. `filterMemo` is updated by `useDeepMemo` because of the new prop
165
+ // 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned
166
+ expect(result.all.length).toBe(6);
167
+ expect(result.current).toEqual({
168
+ key: { key: "0x02" },
169
+ value: { x: 3, y: 4 },
170
+ namespace: config["namespace"],
171
+ table: "Position",
172
+ });
173
+
174
+ // Change the filter
175
+ rerender({ filter: { table: "MultiKey", key: { first: "0x00", second: 4 } } });
176
+
177
+ expect(result.all.length).toBe(9);
178
+ expect(result.current).toEqual({
179
+ key: { first: "0x00", second: 4 },
180
+ value: { value: 1 },
181
+ namespace: config["namespace"],
182
+ table: "MultiKey",
183
+ });
184
+ });
185
+ });
@@ -0,0 +1,23 @@
1
+ import { StoreConfig } from "@latticexyz/store";
2
+ import { DatabaseClient, Key, ScanResult } from "@latticexyz/store-cache";
3
+ import { useRows } from "./useRows";
4
+
5
+ export type UseRowFilterOptions<
6
+ C extends StoreConfig = StoreConfig,
7
+ T extends keyof C["tables"] & string = keyof C["tables"] & string
8
+ > = {
9
+ namespace?: C["namespace"];
10
+ table: T;
11
+ key: Key<C, T>;
12
+ };
13
+
14
+ /**
15
+ * Returns a single row of a given table at the given key, updates when the key changes
16
+ */
17
+ export function useRow<C extends StoreConfig, T extends keyof C["tables"] & string>(
18
+ storeCache: DatabaseClient<C>,
19
+ filter: UseRowFilterOptions<C, T>
20
+ ): ScanResult<C, T>[0] | undefined {
21
+ const { namespace, table, key } = filter;
22
+ return useRows(storeCache, { namespace, table, key: { eq: key } })[0];
23
+ }
@@ -0,0 +1,157 @@
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
+ import { useRows } from "./useRows";
3
+ import { mudConfig } from "@latticexyz/store/register";
4
+ import { KeyValue, createDatabase, createDatabaseClient } from "@latticexyz/store-cache";
5
+ import { describe, it, beforeEach, expect } from "vitest";
6
+
7
+ const config = mudConfig({
8
+ tables: {
9
+ MultiKey: { keySchema: { first: "bytes32", second: "uint32" }, schema: "int32" },
10
+ Position: { schema: { x: "int32", y: "int32" } },
11
+ },
12
+ });
13
+
14
+ describe("useRows", () => {
15
+ let db: ReturnType<typeof createDatabase>;
16
+ let client: ReturnType<typeof createDatabaseClient<typeof config>>;
17
+
18
+ beforeEach(() => {
19
+ db = createDatabase();
20
+ client = createDatabaseClient(db, config);
21
+ });
22
+
23
+ it("should return all rows of the position table", () => {
24
+ const { result } = renderHook(() => useRows(client, { table: "Position" }));
25
+ expect(result.current.length).toBe(0);
26
+
27
+ const positionUpdates: KeyValue<typeof config, "Position">[] = [
28
+ { key: { key: "0x00" }, value: { x: 1, y: 2 } },
29
+ { key: { key: "0x01" }, value: { x: 2, y: 3 } },
30
+ { key: { key: "0x02" }, value: { x: 3, y: 4 } },
31
+ { key: { key: "0x03" }, value: { x: 4, y: 5 } },
32
+ ];
33
+
34
+ const multiKeyUpdates: KeyValue<typeof config, "MultiKey">[] = [
35
+ { key: { first: "0x00", second: 4 }, value: { value: 1 } },
36
+ { key: { first: "0x01", second: 3 }, value: { value: 2 } },
37
+ { key: { first: "0x02", second: 2 }, value: { value: 3 } },
38
+ { key: { first: "0x03", second: 1 }, value: { value: 4 } },
39
+ ];
40
+
41
+ act(() => {
42
+ // Set values in the tables
43
+ for (const update of positionUpdates) client.tables.Position.set(update.key, update.value);
44
+ for (const update of multiKeyUpdates) client.tables.MultiKey.set(update.key, update.value);
45
+ });
46
+
47
+ expect(result.current.length).toBe(positionUpdates.length);
48
+ expect(result.current).toEqual([
49
+ ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })),
50
+ ]);
51
+
52
+ act(() => {
53
+ for (const update of positionUpdates.slice(0, 3)) client.tables.Position.remove(update.key);
54
+ });
55
+
56
+ expect(result.current.length).toBe(1);
57
+ expect(result.current).toEqual([
58
+ { key: { key: "0x03" }, value: { x: 4, y: 5 }, namespace: config["namespace"], table: "Position" },
59
+ ]);
60
+ });
61
+
62
+ it("should re-render only when the position table changes", () => {
63
+ const { result } = renderHook(() => useRows(client, { namespace: config["namespace"], table: "Position" }));
64
+ expect(result.all.length).toBe(2);
65
+
66
+ // Update the position table
67
+ act(() => {
68
+ client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
69
+ });
70
+ expect(result.all.length).toBe(3);
71
+ expect(result.current).toEqual([
72
+ { key: { key: "0x00" }, value: { x: 1, y: 2 }, namespace: config["namespace"], table: "Position" },
73
+ ]);
74
+
75
+ // Update an unrelated table
76
+ act(() => {
77
+ client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
78
+ });
79
+ expect(result.all.length).toBe(3);
80
+ expect(result.current).toEqual([
81
+ { key: { key: "0x00" }, value: { x: 1, y: 2 }, namespace: config["namespace"], table: "Position" },
82
+ ]);
83
+
84
+ // Update the position table
85
+ act(() => {
86
+ client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
87
+ });
88
+ expect(result.all.length).toBe(4);
89
+ expect(result.current).toEqual([
90
+ { key: { key: "0x00" }, value: { x: 2, y: 2 }, namespace: config["namespace"], table: "Position" },
91
+ ]);
92
+
93
+ // Update an unrelated table
94
+ act(() => {
95
+ client.tables.MultiKey.remove({ first: "0x03", second: 1 });
96
+ });
97
+ expect(result.all.length).toBe(4);
98
+ expect(result.current).toEqual([
99
+ { key: { key: "0x00" }, value: { x: 2, y: 2 }, namespace: config["namespace"], table: "Position" },
100
+ ]);
101
+
102
+ // Update the position table
103
+ act(() => {
104
+ client.tables.Position.remove({ key: "0x00" });
105
+ });
106
+ expect(result.all.length).toBe(5);
107
+ expect(result.current).toEqual([]);
108
+ });
109
+
110
+ it("should re-render when the filter changes", () => {
111
+ const { result, rerender } = renderHook(({ filter }) => useRows(client, filter), {
112
+ initialProps: { filter: { table: "Position" as keyof (typeof config)["tables"] } },
113
+ });
114
+
115
+ expect(result.all.length).toBe(2);
116
+ expect(result.current.length).toBe(0);
117
+
118
+ const positionUpdates: KeyValue<typeof config, "Position">[] = [
119
+ { key: { key: "0x00" }, value: { x: 1, y: 2 } },
120
+ { key: { key: "0x01" }, value: { x: 2, y: 3 } },
121
+ { key: { key: "0x02" }, value: { x: 3, y: 4 } },
122
+ { key: { key: "0x03" }, value: { x: 4, y: 5 } },
123
+ ];
124
+
125
+ const multiKeyUpdates: KeyValue<typeof config, "MultiKey">[] = [
126
+ { key: { first: "0x00", second: 4 }, value: { value: 1 } },
127
+ { key: { first: "0x01", second: 3 }, value: { value: 2 } },
128
+ { key: { first: "0x02", second: 2 }, value: { value: 3 } },
129
+ { key: { first: "0x03", second: 1 }, value: { value: 4 } },
130
+ ];
131
+
132
+ act(() => {
133
+ // Set values in the tables
134
+ for (const update of positionUpdates) client.tables.Position.set(update.key, update.value);
135
+ for (const update of multiKeyUpdates) client.tables.MultiKey.set(update.key, update.value);
136
+ });
137
+
138
+ expect(result.all.length).toBe(3);
139
+ expect(result.current.length).toBe(positionUpdates.length);
140
+ expect(result.current).toEqual([
141
+ ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })),
142
+ ]);
143
+
144
+ // Change the filter
145
+ rerender({ filter: { table: "MultiKey" } });
146
+
147
+ // Expect hook to rerender three times:
148
+ // 1. New prop, everything else changes the same
149
+ // 2. `filterMemo` is updated by `useDeepMemo` because of the new prop
150
+ // 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned
151
+ expect(result.all.length).toBe(6);
152
+ expect(result.current.length).toBe(multiKeyUpdates.length);
153
+ expect(result.current).toEqual([
154
+ ...multiKeyUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "MultiKey" })),
155
+ ]);
156
+ });
157
+ });
@@ -0,0 +1,30 @@
1
+ import { DatabaseClient, FilterOptions, ScanResult } from "@latticexyz/store-cache";
2
+ import { StoreConfig } from "@latticexyz/store";
3
+ import { useEffect, useState } from "react";
4
+ import { useDeepMemo } from "../utils/useDeepMemo";
5
+
6
+ /**
7
+ * Returns an array of all rows matching the provided filter
8
+ */
9
+ export function useRows<C extends StoreConfig, T extends keyof C["tables"] & string>(
10
+ storeCache: DatabaseClient<C>,
11
+ filter?: FilterOptions<C, T>
12
+ ) {
13
+ const [rows, setRows] = useState<ScanResult<C, T>>([]);
14
+ const filterMemo = useDeepMemo(filter);
15
+
16
+ useEffect(() => {
17
+ setRows(storeCache.scan(filter));
18
+
19
+ const unsubscribe = storeCache.subscribe(() => {
20
+ // very naive implementation for now, but easier and probably more efficient than
21
+ // manually looping through the `rows` array for every update event
22
+ setRows(storeCache.scan(filter));
23
+ }, filter);
24
+
25
+ return unsubscribe;
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ }, [filterMemo]);
28
+
29
+ return rows;
30
+ }
@@ -0,0 +1,89 @@
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
+ import {
3
+ World,
4
+ Type,
5
+ createWorld,
6
+ defineComponent,
7
+ Component,
8
+ createEntity,
9
+ withValue,
10
+ setComponent,
11
+ removeComponent,
12
+ } from "@latticexyz/recs";
13
+ import { useComponentValue } from "./useComponentValue";
14
+ import { describe, it, expect, beforeEach } from "vitest";
15
+
16
+ describe("useComponentValue", () => {
17
+ let world: World;
18
+ let Position: Component<{
19
+ x: Type.Number;
20
+ y: Type.Number;
21
+ }>;
22
+
23
+ beforeEach(() => {
24
+ world = createWorld();
25
+ Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { id: "Position" });
26
+ });
27
+
28
+ it("should return Position value for entity", () => {
29
+ const entity = createEntity(world, [withValue(Position, { x: 1, y: 1 })]);
30
+
31
+ const { result } = renderHook(() => useComponentValue(Position, entity));
32
+ expect(result.current).toEqual({ x: 1, y: 1 });
33
+
34
+ act(() => {
35
+ setComponent(Position, entity, { x: 0, y: 0 });
36
+ });
37
+ expect(result.current).toEqual({ x: 0, y: 0 });
38
+
39
+ act(() => {
40
+ removeComponent(Position, entity);
41
+ });
42
+ expect(result.current).toBe(undefined);
43
+ });
44
+
45
+ it("should re-render only when Position changes for entity", () => {
46
+ const entity = createEntity(world, [withValue(Position, { x: 1, y: 1 })]);
47
+ const otherEntity = createEntity(world, [withValue(Position, { x: 2, y: 2 })]);
48
+
49
+ const { result } = renderHook(() => useComponentValue(Position, entity));
50
+ expect(result.all.length).toBe(2);
51
+ expect(result.current).toEqual({ x: 1, y: 1 });
52
+
53
+ act(() => {
54
+ setComponent(Position, entity, { x: 0, y: 0 });
55
+ });
56
+ expect(result.all.length).toBe(3);
57
+ expect(result.current).toEqual({ x: 0, y: 0 });
58
+
59
+ act(() => {
60
+ setComponent(Position, otherEntity, { x: 0, y: 0 });
61
+ removeComponent(Position, otherEntity);
62
+ });
63
+ expect(result.all.length).toBe(3);
64
+ expect(result.current).toEqual({ x: 0, y: 0 });
65
+
66
+ act(() => {
67
+ removeComponent(Position, entity);
68
+ });
69
+ expect(result.all.length).toBe(4);
70
+ expect(result.current).toBe(undefined);
71
+ });
72
+
73
+ it("should return default value when Position is not set", () => {
74
+ const entity = createEntity(world);
75
+
76
+ const { result } = renderHook(() => useComponentValue(Position, entity, { x: -1, y: -1 }));
77
+ expect(result.current).toEqual({ x: -1, y: -1 });
78
+
79
+ act(() => {
80
+ setComponent(Position, entity, { x: 0, y: 0 });
81
+ });
82
+ expect(result.current).toEqual({ x: 0, y: 0 });
83
+
84
+ act(() => {
85
+ removeComponent(Position, entity);
86
+ });
87
+ expect(result.current).toEqual({ x: -1, y: -1 });
88
+ });
89
+ });
@@ -0,0 +1,48 @@
1
+ import {
2
+ Component,
3
+ ComponentValue,
4
+ defineQuery,
5
+ Entity,
6
+ getComponentValue,
7
+ Has,
8
+ isComponentUpdate,
9
+ Metadata,
10
+ Schema,
11
+ } from "@latticexyz/recs";
12
+ import { useEffect, useState } from "react";
13
+
14
+ export function useComponentValue<S extends Schema>(
15
+ component: Component<S, Metadata, undefined>,
16
+ entity: Entity | undefined,
17
+ defaultValue: ComponentValue<S>
18
+ ): ComponentValue<S>;
19
+
20
+ export function useComponentValue<S extends Schema>(
21
+ component: Component<S, Metadata, undefined>,
22
+ entity: Entity | undefined
23
+ ): ComponentValue<S> | undefined;
24
+
25
+ export function useComponentValue<S extends Schema>(
26
+ component: Component<S, Metadata, undefined>,
27
+ entity: Entity | undefined,
28
+ defaultValue?: ComponentValue<S>
29
+ ) {
30
+ const [value, setValue] = useState(entity != null ? getComponentValue(component, entity) : undefined);
31
+
32
+ useEffect(() => {
33
+ // component or entity changed, update state to latest value
34
+ setValue(entity != null ? getComponentValue(component, entity) : undefined);
35
+ if (entity == null) return;
36
+
37
+ const queryResult = defineQuery([Has(component)], { runOnInit: false });
38
+ const subscription = queryResult.update$.subscribe((update) => {
39
+ if (isComponentUpdate(update, component) && update.entity === entity) {
40
+ const [nextValue] = update.value;
41
+ setValue(nextValue);
42
+ }
43
+ });
44
+ return () => subscription.unsubscribe();
45
+ }, [component, entity]);
46
+
47
+ return value ?? defaultValue;
48
+ }
@@ -0,0 +1,14 @@
1
+ import { IComputedValue } from "mobx";
2
+ import { useEffect, useState } from "react";
3
+
4
+ /** @deprecated See https://github.com/latticexyz/mud/issues/339 */
5
+ export const useDeprecatedComputedValue = <T>(computedValue: IComputedValue<T> & { observe_: any }) => {
6
+ const [value, setValue] = useState<T>(computedValue.get());
7
+
8
+ useEffect(() => {
9
+ const unsubscribe = computedValue.observe_(() => setValue(computedValue.get()));
10
+ return () => unsubscribe();
11
+ }, [computedValue]);
12
+
13
+ return value;
14
+ };
@@ -0,0 +1,164 @@
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
+ import {
3
+ World,
4
+ Type,
5
+ createWorld,
6
+ defineComponent,
7
+ Component,
8
+ createEntity,
9
+ withValue,
10
+ Has,
11
+ setComponent,
12
+ HasValue,
13
+ removeComponent,
14
+ } from "@latticexyz/recs";
15
+ import { useEntityQuery } from "./useEntityQuery";
16
+ import { describe, beforeEach, it, expect } from "vitest";
17
+
18
+ describe("useEntityQuery", () => {
19
+ let world: World;
20
+ let Position: Component<{
21
+ x: Type.Number;
22
+ y: Type.Number;
23
+ }>;
24
+ let OwnedBy: Component<{ value: Type.Entity }>;
25
+
26
+ beforeEach(() => {
27
+ world = createWorld();
28
+ Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { id: "Position" });
29
+ OwnedBy = defineComponent(world, { value: Type.Entity }, { id: "OwnedBy" });
30
+ });
31
+
32
+ it("should find entities with Position component", () => {
33
+ const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 1 })]);
34
+ const entity2 = createEntity(world, [withValue(Position, { x: 2, y: 2 })]);
35
+ const entity3 = createEntity(world, []);
36
+
37
+ const { result } = renderHook(() => useEntityQuery([Has(Position)], { updateOnValueChange: false }));
38
+ const { result: resultOnValueChange } = renderHook(() =>
39
+ useEntityQuery([Has(Position)], { updateOnValueChange: true })
40
+ );
41
+
42
+ expect(result.current.length).toBe(2);
43
+ expect(result.current).toContain(entity1);
44
+ expect(result.current).toContain(entity2);
45
+ expect(result.current).not.toContain(entity3);
46
+ expect(resultOnValueChange.current).toEqual(result.current);
47
+
48
+ act(() => {
49
+ setComponent(Position, entity3, { x: 0, y: 0 });
50
+ });
51
+
52
+ expect(result.current.length).toBe(3);
53
+ expect(result.current).toContain(entity1);
54
+ expect(result.current).toContain(entity2);
55
+ expect(result.current).toContain(entity3);
56
+ expect(resultOnValueChange.current).toEqual(result.current);
57
+
58
+ act(() => {
59
+ removeComponent(Position, entity1);
60
+ removeComponent(Position, entity3);
61
+ });
62
+
63
+ expect(result.current.length).toBe(1);
64
+ expect(result.current).not.toContain(entity1);
65
+ expect(result.current).toContain(entity2);
66
+ expect(result.current).not.toContain(entity3);
67
+ expect(resultOnValueChange.current).toEqual(result.current);
68
+
69
+ act(() => {
70
+ removeComponent(Position, entity2);
71
+ });
72
+
73
+ expect(result.current.length).toBe(0);
74
+ });
75
+
76
+ it("should re-render only when Position changes", () => {
77
+ const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 1 })]);
78
+ const entity2 = createEntity(world, [withValue(Position, { x: 2, y: 2 })]);
79
+ const entity3 = createEntity(world, []);
80
+
81
+ const { result } = renderHook(() => useEntityQuery([Has(Position)], { updateOnValueChange: false }));
82
+ const { result: resultOnValueChange } = renderHook(() =>
83
+ useEntityQuery([Has(Position)], { updateOnValueChange: true })
84
+ );
85
+
86
+ expect(result.all).toHaveLength(2);
87
+ expect(result.current).toHaveLength(2);
88
+ expect(result.current).toContain(entity1);
89
+ expect(result.current).toContain(entity2);
90
+ expect(result.current).not.toContain(entity3);
91
+
92
+ // Changing an entity's component value should NOT re-render,
93
+ // unless updateOnValueChange === true
94
+ act(() => {
95
+ setComponent(Position, entity2, { x: 0, y: 0 });
96
+ });
97
+
98
+ expect(result.all).toHaveLength(2);
99
+ expect(resultOnValueChange.all).toHaveLength(3);
100
+
101
+ // Changing a different component value should NOT re-render
102
+ act(() => {
103
+ setComponent(OwnedBy, entity2, { value: entity1 });
104
+ setComponent(OwnedBy, entity3, { value: entity1 });
105
+ });
106
+
107
+ expect(result.all).toHaveLength(2);
108
+ expect(resultOnValueChange.all).toHaveLength(3);
109
+
110
+ // Changing which entities have the component should re-render
111
+ act(() => {
112
+ setComponent(Position, entity3, { x: 0, y: 0 });
113
+ });
114
+
115
+ expect(result.all).toHaveLength(3);
116
+ expect(resultOnValueChange.all).toHaveLength(4);
117
+ expect(result.current).toHaveLength(3);
118
+ expect(result.current).toContain(entity1);
119
+ expect(result.current).toContain(entity2);
120
+ expect(result.current).toContain(entity3);
121
+
122
+ // Changing which entities have the component should re-render
123
+ act(() => {
124
+ removeComponent(Position, entity1);
125
+ });
126
+
127
+ expect(result.all).toHaveLength(4);
128
+ expect(resultOnValueChange.all).toHaveLength(5);
129
+ expect(result.current).toHaveLength(2);
130
+ expect(result.current).toContain(entity2);
131
+ expect(result.current).toContain(entity3);
132
+ });
133
+
134
+ it("should re-render as hook arguments change", () => {
135
+ // TODO: reduce re-renders during argument changes?
136
+
137
+ const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 1 })]);
138
+ const entity2 = createEntity(world, [withValue(Position, { x: 2, y: 2 })]);
139
+ const entity3 = createEntity(world, [withValue(Position, { x: 2, y: 2 })]);
140
+
141
+ const { result, rerender } = renderHook(({ x, y }) => useEntityQuery([HasValue(Position, { x, y })]), {
142
+ initialProps: { x: 1, y: 1 },
143
+ });
144
+
145
+ expect(result.all).toHaveLength(2);
146
+ expect(result.current).toHaveLength(1);
147
+ expect(result.current).toContain(entity1);
148
+
149
+ rerender({ x: 1, y: 1 });
150
+ expect(result.all).toHaveLength(3);
151
+ expect(result.current).toHaveLength(1);
152
+ expect(result.current).toContain(entity1);
153
+
154
+ rerender({ x: 2, y: 2 });
155
+ expect(result.all).toHaveLength(6);
156
+ expect(result.current).toHaveLength(2);
157
+ expect(result.current).toContain(entity2);
158
+ expect(result.current).toContain(entity3);
159
+
160
+ rerender({ x: -1, y: -1 });
161
+ expect(result.all).toHaveLength(9);
162
+ expect(result.current).toHaveLength(0);
163
+ });
164
+ });
@@ -0,0 +1,37 @@
1
+ import { defineQuery, QueryFragment } from "@latticexyz/recs";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { useDeepMemo } from "./utils/useDeepMemo";
4
+ import isEqual from "fast-deep-equal";
5
+ import { distinctUntilChanged, map } from "rxjs";
6
+
7
+ // This does a little more rendering than is necessary when arguments change,
8
+ // but at least it's giving correct results now. Will optimize later!
9
+
10
+ /**
11
+ * Returns all matching entities for a given entity query,
12
+ * and triggers a re-render as new query results come in.
13
+ *
14
+ * @param fragments Query fragments to match against, executed from left to right.
15
+ * @param options.updateOnValueChange False - re-renders only on entity array changes. True (default) - also on component value changes.
16
+ * @returns Set of entities matching the query fragments.
17
+ */
18
+ export function useEntityQuery(fragments: QueryFragment[], options?: { updateOnValueChange?: boolean }) {
19
+ const updateOnValueChange = options?.updateOnValueChange ?? true;
20
+
21
+ const stableFragments = useDeepMemo(fragments);
22
+ const query = useMemo(() => defineQuery(stableFragments, { runOnInit: true }), [stableFragments]);
23
+ const [entities, setEntities] = useState([...query.matching]);
24
+
25
+ useEffect(() => {
26
+ setEntities([...query.matching]);
27
+ let observable = query.update$.pipe(map(() => [...query.matching]));
28
+ if (!updateOnValueChange) {
29
+ // re-render only on entity array changes
30
+ observable = observable.pipe(distinctUntilChanged((a, b) => isEqual(a, b)));
31
+ }
32
+ const subscription = observable.subscribe((entities) => setEntities(entities));
33
+ return () => subscription.unsubscribe();
34
+ }, [query, updateOnValueChange]);
35
+
36
+ return entities;
37
+ }
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Observable } from "rxjs";
3
+
4
+ export function useObservableValue<T>(observable: Observable<T>, defaultValue: T): T;
5
+
6
+ export function useObservableValue<T>(observable: Observable<T>): T | undefined;
7
+
8
+ export function useObservableValue<T>(observable: Observable<T>, defaultValue?: T) {
9
+ const [value, setValue] = useState(defaultValue);
10
+
11
+ useEffect(() => {
12
+ const subscription = observable.subscribe(setValue);
13
+ return () => subscription.unsubscribe();
14
+ }, [observable]);
15
+
16
+ return value;
17
+ }
@@ -0,0 +1,15 @@
1
+ import { useEffect, useState } from "react";
2
+ import isEqual from "fast-deep-equal";
3
+
4
+ export const useDeepMemo = <T>(currentValue: T): T => {
5
+ const [stableValue, setStableValue] = useState(currentValue);
6
+
7
+ useEffect(() => {
8
+ if (!isEqual(currentValue, stableValue)) {
9
+ setStableValue(currentValue);
10
+ }
11
+ // eslint-disable-next-line react-hooks/exhaustive-deps
12
+ }, [currentValue]);
13
+
14
+ return stableValue;
15
+ };
@@ -1,3 +0,0 @@
1
- import { Component, ComponentValue, EntityIndex, Metadata, Schema } from "@latticexyz/recs";
2
- export declare function useComponentValue<S extends Schema>(component: Component<S, Metadata, undefined>, entityIndex: EntityIndex | undefined, defaultValue: ComponentValue<S>): ComponentValue<S>;
3
- export declare function useComponentValue<S extends Schema>(component: Component<S, Metadata, undefined>, entityIndex: EntityIndex | undefined): ComponentValue<S> | undefined;
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- import { IComputedValue } from "mobx";
2
- /** @deprecated See https://github.com/latticexyz/mud/issues/339 */
3
- export declare const useDeprecatedComputedValue: <T>(computedValue: IComputedValue<T> & {
4
- observe_: any;
5
- }) => T;
@@ -1,12 +0,0 @@
1
- import { QueryFragment } from "@latticexyz/recs";
2
- /**
3
- * Returns all matching `EntityIndex`es for a given entity query,
4
- * and triggers a re-render as new query results come in.
5
- *
6
- * @param fragments Query fragments to match against, executed from left to right.
7
- * @param options.updateOnValueChange False - re-renders only on entity array changes. True (default) - also on component value changes.
8
- * @returns Set of entities matching the query fragments.
9
- */
10
- export declare function useEntityQuery(fragments: QueryFragment[], options?: {
11
- updateOnValueChange?: boolean;
12
- }): import("@latticexyz/recs").EntityIndex[];
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- import { Observable } from "rxjs";
2
- export declare function useObservableValue<T>(observable: Observable<T>, defaultValue: T): T;
3
- export declare function useObservableValue<T>(observable: Observable<T>): T | undefined;
@@ -1 +0,0 @@
1
- export declare const useDeepMemo: <T>(currentValue: T) => T;