@latticexyz/react 2.0.0-alpha.1.254 → 2.0.0-alpha.1.256
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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/store-cache/useRow.test.ts +36 -24
- package/src/store-cache/useRows.test.ts +37 -26
- package/src/store-cache/useRows.ts +10 -8
- package/src/utils/useMountedState.ts +23 -0
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{defineQuery as
|
|
1
|
+
import{defineQuery as d,getComponentValue as p,Has as C,isComponentUpdate as S}from"@latticexyz/recs";import{useEffect as T,useState as x}from"react";function G(e,t,s){let[o,n]=x(t!=null?p(e,t):void 0);return T(()=>{if(n(t!=null?p(e,t):void 0),t==null)return;let a=d([C(e)],{runOnInit:!1}).update$.subscribe(u=>{if(S(u,e)&&u.entity===t){let[f]=u.value;n(f)}});return()=>a.unsubscribe()},[e,t]),o??s}import{useEffect as g,useState as y}from"react";var N=e=>{let[t,s]=y(e.get());return g(()=>{let o=e.observe_(()=>s(e.get()));return()=>o()},[e]),t};import{defineQuery as E}from"@latticexyz/recs";import{useEffect as R,useMemo as h,useState as M}from"react";import{useEffect as V,useState as O}from"react";import v from"fast-deep-equal";var i=e=>{let[t,s]=O(e);return V(()=>{v(e,t)||s(e)},[e]),t};import D from"fast-deep-equal";import{distinctUntilChanged as k,map as w}from"rxjs";function ue(e,t){let s=t?.updateOnValueChange??!0,o=i(e),n=h(()=>E(o,{runOnInit:!0}),[o]),[r,a]=M([...n.matching]);return R(()=>{a([...n.matching]);let u=n.update$.pipe(w(()=>[...n.matching]));s||(u=u.pipe(k((m,b)=>D(m,b))));let f=u.subscribe(m=>a(m));return()=>f.unsubscribe()},[n,s]),r}import{useEffect as F,useState as q}from"react";function fe(e,t){let[s,o]=q(t);return F(()=>{let n=e.subscribe(o);return()=>n.unsubscribe()},[e]),s}import{useEffect as K}from"react";import{useCallback as I,useEffect as Q,useRef as U,useState as A}from"react";function l(e){let[t,s]=A(e),o=U(!1);Q(()=>(o.current=!0,()=>{o.current=!1}));let n=I((...r)=>{o.current?s(...r):console.warn("Ignoring `setState` call because component unmounted",...r)},[]);return[t,n]}function c(e,t){let[s,o]=l([]),n=i(t);return K(()=>{e.scan(n).then(o);let r=e.subscribe(()=>{e.scan(n).then(o)},n);return()=>{r.then(a=>a())}},[n,o,e]),s}function ye(e,t){let{namespace:s,table:o,key:n}=t;return c(e,{namespace:s,table:o,key:{eq:n}})[0]}export{G as useComponentValue,N as useDeprecatedComputedValue,ue as useEntityQuery,fe as useObservableValue,ye as useRow,c as useRows};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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","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"]}
|
|
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/utils/useMountedState.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\";\nimport { useMountedState } from \"../utils/useMountedState\";\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] = useMountedState<ScanResult<C, T>>([]);\n const filterMemo = useDeepMemo(filter);\n\n useEffect(() => {\n storeCache.scan(filterMemo).then(setRows);\n\n const unsubscribePromise = 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 storeCache.scan(filterMemo).then(setRows);\n }, filterMemo);\n\n return () => {\n unsubscribePromise.then((unsubscribe) => unsubscribe());\n };\n }, [filterMemo, setRows, storeCache]);\n\n return rows;\n}\n","import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Use in place of useState when the component may be unmounted before the state is updated.\n */\nexport function useMountedState<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {\n const [state, setState] = useState<T>(initialState);\n const mountedRef = useRef(false);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n });\n const mountedSetState: typeof setState = useCallback((...args) => {\n if (mountedRef.current) {\n setState(...args);\n } else {\n console.warn(\"Ignoring `setState` call because component unmounted\", ...args);\n }\n }, []);\n return [state, mountedSetState];\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":"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","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","useCallback","useEffect","useRef","useState","useMountedState","initialState","state","setState","mountedRef","mountedSetState","args","useRows","storeCache","filter","rows","setRows","useMountedState","filterMemo","useDeepMemo","useEffect","unsubscribePromise","unsubscribe","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.
|
|
3
|
+
"version": "2.0.0-alpha.1.256+e3260c8a",
|
|
4
4
|
"description": "React tools for MUD client.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"test": "tsc --noEmit && vitest --run"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@latticexyz/recs": "2.0.0-alpha.1.
|
|
27
|
-
"@latticexyz/store": "2.0.0-alpha.1.
|
|
28
|
-
"@latticexyz/store-cache": "2.0.0-alpha.1.
|
|
26
|
+
"@latticexyz/recs": "2.0.0-alpha.1.256+e3260c8a",
|
|
27
|
+
"@latticexyz/store": "2.0.0-alpha.1.256+e3260c8a",
|
|
28
|
+
"@latticexyz/store-cache": "2.0.0-alpha.1.256+e3260c8a",
|
|
29
29
|
"fast-deep-equal": "^3.1.3",
|
|
30
30
|
"mobx": "^6.7.0",
|
|
31
31
|
"react": "^18.2.0",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"vite": "^4.3.6",
|
|
45
45
|
"vitest": "0.31.4"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "e3260c8a237b42b6f2b875314203756a47bdce2e"
|
|
48
48
|
}
|
|
@@ -20,7 +20,7 @@ describe("useRow", () => {
|
|
|
20
20
|
client = createDatabaseClient(db, config);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it("should return the row of the position table with the specified key", () => {
|
|
23
|
+
it("should return the row of the position table with the specified key", async () => {
|
|
24
24
|
const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x01" } }));
|
|
25
25
|
expect(result.current).toBe(undefined);
|
|
26
26
|
|
|
@@ -38,10 +38,14 @@ describe("useRow", () => {
|
|
|
38
38
|
{ key: { first: "0x03", second: 1 }, value: { value: 4 } },
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
-
act(() => {
|
|
41
|
+
await act(async () => {
|
|
42
42
|
// Set values in the tables
|
|
43
|
-
for (const update of positionUpdates)
|
|
44
|
-
|
|
43
|
+
for (const update of positionUpdates) {
|
|
44
|
+
await client.tables.Position.set(update.key, update.value);
|
|
45
|
+
}
|
|
46
|
+
for (const update of multiKeyUpdates) {
|
|
47
|
+
await client.tables.MultiKey.set(update.key, update.value);
|
|
48
|
+
}
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
expect(result.current).toEqual({
|
|
@@ -51,20 +55,22 @@ describe("useRow", () => {
|
|
|
51
55
|
table: "Position",
|
|
52
56
|
});
|
|
53
57
|
|
|
54
|
-
act(() => {
|
|
55
|
-
for (const update of positionUpdates.slice(0, 3))
|
|
58
|
+
await act(async () => {
|
|
59
|
+
for (const update of positionUpdates.slice(0, 3)) {
|
|
60
|
+
await client.tables.Position.remove(update.key);
|
|
61
|
+
}
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
expect(result.current).toBe(undefined);
|
|
59
65
|
});
|
|
60
66
|
|
|
61
|
-
it("should re-render only when the position value of the specified key changes", () => {
|
|
67
|
+
it("should re-render only when the position value of the specified key changes", async () => {
|
|
62
68
|
const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x00" } }));
|
|
63
|
-
expect(result.all.length).toBe(
|
|
69
|
+
expect(result.all.length).toBe(1);
|
|
64
70
|
|
|
65
71
|
// Update the position table
|
|
66
|
-
act(() => {
|
|
67
|
-
client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
|
|
72
|
+
await act(async () => {
|
|
73
|
+
await client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
|
|
68
74
|
});
|
|
69
75
|
expect(result.all.length).toBe(3);
|
|
70
76
|
expect(result.current).toEqual({
|
|
@@ -75,8 +81,8 @@ describe("useRow", () => {
|
|
|
75
81
|
});
|
|
76
82
|
|
|
77
83
|
// Update an unrelated table
|
|
78
|
-
act(() => {
|
|
79
|
-
client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
|
|
84
|
+
await act(async () => {
|
|
85
|
+
await client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
|
|
80
86
|
});
|
|
81
87
|
expect(result.all.length).toBe(3);
|
|
82
88
|
expect(result.current).toEqual({
|
|
@@ -87,8 +93,8 @@ describe("useRow", () => {
|
|
|
87
93
|
});
|
|
88
94
|
|
|
89
95
|
// Update the position table
|
|
90
|
-
act(() => {
|
|
91
|
-
client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
|
|
96
|
+
await act(async () => {
|
|
97
|
+
await client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
|
|
92
98
|
});
|
|
93
99
|
expect(result.all.length).toBe(4);
|
|
94
100
|
expect(result.current).toEqual({
|
|
@@ -99,8 +105,8 @@ describe("useRow", () => {
|
|
|
99
105
|
});
|
|
100
106
|
|
|
101
107
|
// Update an unrelated key
|
|
102
|
-
act(() => {
|
|
103
|
-
client.tables.Position.set({ key: "0x01" }, { x: 2, y: 2 });
|
|
108
|
+
await act(async () => {
|
|
109
|
+
await client.tables.Position.set({ key: "0x01" }, { x: 2, y: 2 });
|
|
104
110
|
});
|
|
105
111
|
expect(result.all.length).toBe(4);
|
|
106
112
|
expect(result.current).toEqual({
|
|
@@ -111,21 +117,21 @@ describe("useRow", () => {
|
|
|
111
117
|
});
|
|
112
118
|
|
|
113
119
|
// Update the position table
|
|
114
|
-
act(() => {
|
|
115
|
-
client.tables.Position.remove({ key: "0x00" });
|
|
120
|
+
await act(async () => {
|
|
121
|
+
await client.tables.Position.remove({ key: "0x00" });
|
|
116
122
|
});
|
|
117
123
|
expect(result.all.length).toBe(5);
|
|
118
124
|
expect(result.current).toEqual(undefined);
|
|
119
125
|
});
|
|
120
126
|
|
|
121
|
-
it("should re-render when the filter changes", () => {
|
|
122
|
-
const { result, rerender } = renderHook(({ filter }) => useRow(client, filter), {
|
|
127
|
+
it("should re-render when the filter changes", async () => {
|
|
128
|
+
const { result, rerender, waitForNextUpdate } = renderHook(({ filter }) => useRow(client, filter), {
|
|
123
129
|
initialProps: {
|
|
124
130
|
filter: { table: "Position", key: { key: "0x01" } } as UseRowFilterOptions<typeof config>,
|
|
125
131
|
},
|
|
126
132
|
});
|
|
127
133
|
|
|
128
|
-
expect(result.all.length).toBe(
|
|
134
|
+
expect(result.all.length).toBe(1);
|
|
129
135
|
expect(result.current).toBe(undefined);
|
|
130
136
|
|
|
131
137
|
const positionUpdates: KeyValue<typeof config, "Position">[] = [
|
|
@@ -142,10 +148,14 @@ describe("useRow", () => {
|
|
|
142
148
|
{ key: { first: "0x03", second: 1 }, value: { value: 4 } },
|
|
143
149
|
];
|
|
144
150
|
|
|
145
|
-
act(() => {
|
|
151
|
+
await act(async () => {
|
|
146
152
|
// Set values in the tables
|
|
147
|
-
for (const update of positionUpdates)
|
|
148
|
-
|
|
153
|
+
for (const update of positionUpdates) {
|
|
154
|
+
await client.tables.Position.set(update.key, update.value);
|
|
155
|
+
}
|
|
156
|
+
for (const update of multiKeyUpdates) {
|
|
157
|
+
await client.tables.MultiKey.set(update.key, update.value);
|
|
158
|
+
}
|
|
149
159
|
});
|
|
150
160
|
|
|
151
161
|
expect(result.all.length).toBe(3);
|
|
@@ -158,6 +168,7 @@ describe("useRow", () => {
|
|
|
158
168
|
|
|
159
169
|
// Change the filter
|
|
160
170
|
rerender({ filter: { table: "Position", key: { key: "0x02" } } });
|
|
171
|
+
await waitForNextUpdate();
|
|
161
172
|
|
|
162
173
|
// Expect hook to rerender three times:
|
|
163
174
|
// 1. New prop, everything else changes the same
|
|
@@ -173,6 +184,7 @@ describe("useRow", () => {
|
|
|
173
184
|
|
|
174
185
|
// Change the filter
|
|
175
186
|
rerender({ filter: { table: "MultiKey", key: { first: "0x00", second: 4 } } });
|
|
187
|
+
await waitForNextUpdate();
|
|
176
188
|
|
|
177
189
|
expect(result.all.length).toBe(9);
|
|
178
190
|
expect(result.current).toEqual({
|
|
@@ -20,7 +20,7 @@ describe("useRows", () => {
|
|
|
20
20
|
client = createDatabaseClient(db, config);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it("should return all rows of the position table", () => {
|
|
23
|
+
it("should return all rows of the position table", async () => {
|
|
24
24
|
const { result } = renderHook(() => useRows(client, { table: "Position" }));
|
|
25
25
|
expect(result.current.length).toBe(0);
|
|
26
26
|
|
|
@@ -38,10 +38,14 @@ describe("useRows", () => {
|
|
|
38
38
|
{ key: { first: "0x03", second: 1 }, value: { value: 4 } },
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
-
act(() => {
|
|
41
|
+
await act(async () => {
|
|
42
42
|
// Set values in the tables
|
|
43
|
-
for (const update of positionUpdates)
|
|
44
|
-
|
|
43
|
+
for (const update of positionUpdates) {
|
|
44
|
+
await client.tables.Position.set(update.key, update.value);
|
|
45
|
+
}
|
|
46
|
+
for (const update of multiKeyUpdates) {
|
|
47
|
+
await client.tables.MultiKey.set(update.key, update.value);
|
|
48
|
+
}
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
expect(result.current.length).toBe(positionUpdates.length);
|
|
@@ -49,8 +53,10 @@ describe("useRows", () => {
|
|
|
49
53
|
...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })),
|
|
50
54
|
]);
|
|
51
55
|
|
|
52
|
-
act(() => {
|
|
53
|
-
for (const update of positionUpdates.slice(0, 3))
|
|
56
|
+
await act(async () => {
|
|
57
|
+
for (const update of positionUpdates.slice(0, 3)) {
|
|
58
|
+
await client.tables.Position.remove(update.key);
|
|
59
|
+
}
|
|
54
60
|
});
|
|
55
61
|
|
|
56
62
|
expect(result.current.length).toBe(1);
|
|
@@ -59,13 +65,13 @@ describe("useRows", () => {
|
|
|
59
65
|
]);
|
|
60
66
|
});
|
|
61
67
|
|
|
62
|
-
it("should re-render only when the position table changes", () => {
|
|
68
|
+
it("should re-render only when the position table changes", async () => {
|
|
63
69
|
const { result } = renderHook(() => useRows(client, { namespace: config["namespace"], table: "Position" }));
|
|
64
|
-
expect(result.all.length).toBe(
|
|
70
|
+
expect(result.all.length).toBe(1);
|
|
65
71
|
|
|
66
72
|
// Update the position table
|
|
67
|
-
act(() => {
|
|
68
|
-
client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
|
|
73
|
+
await act(async () => {
|
|
74
|
+
await client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
|
|
69
75
|
});
|
|
70
76
|
expect(result.all.length).toBe(3);
|
|
71
77
|
expect(result.current).toEqual([
|
|
@@ -73,8 +79,8 @@ describe("useRows", () => {
|
|
|
73
79
|
]);
|
|
74
80
|
|
|
75
81
|
// Update an unrelated table
|
|
76
|
-
act(() => {
|
|
77
|
-
client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
|
|
82
|
+
await act(async () => {
|
|
83
|
+
await client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
|
|
78
84
|
});
|
|
79
85
|
expect(result.all.length).toBe(3);
|
|
80
86
|
expect(result.current).toEqual([
|
|
@@ -82,8 +88,8 @@ describe("useRows", () => {
|
|
|
82
88
|
]);
|
|
83
89
|
|
|
84
90
|
// Update the position table
|
|
85
|
-
act(() => {
|
|
86
|
-
client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
|
|
91
|
+
await act(async () => {
|
|
92
|
+
await client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
|
|
87
93
|
});
|
|
88
94
|
expect(result.all.length).toBe(4);
|
|
89
95
|
expect(result.current).toEqual([
|
|
@@ -91,8 +97,8 @@ describe("useRows", () => {
|
|
|
91
97
|
]);
|
|
92
98
|
|
|
93
99
|
// Update an unrelated table
|
|
94
|
-
act(() => {
|
|
95
|
-
client.tables.MultiKey.remove({ first: "0x03", second: 1 });
|
|
100
|
+
await act(async () => {
|
|
101
|
+
await client.tables.MultiKey.remove({ first: "0x03", second: 1 });
|
|
96
102
|
});
|
|
97
103
|
expect(result.all.length).toBe(4);
|
|
98
104
|
expect(result.current).toEqual([
|
|
@@ -100,19 +106,19 @@ describe("useRows", () => {
|
|
|
100
106
|
]);
|
|
101
107
|
|
|
102
108
|
// Update the position table
|
|
103
|
-
act(() => {
|
|
104
|
-
client.tables.Position.remove({ key: "0x00" });
|
|
109
|
+
await act(async () => {
|
|
110
|
+
await client.tables.Position.remove({ key: "0x00" });
|
|
105
111
|
});
|
|
106
112
|
expect(result.all.length).toBe(5);
|
|
107
113
|
expect(result.current).toEqual([]);
|
|
108
114
|
});
|
|
109
115
|
|
|
110
|
-
it("should re-render when the filter changes", () => {
|
|
111
|
-
const { result, rerender } = renderHook(({ filter }) => useRows(client, filter), {
|
|
116
|
+
it("should re-render when the filter changes", async () => {
|
|
117
|
+
const { result, rerender, waitForNextUpdate } = renderHook(({ filter }) => useRows(client, filter), {
|
|
112
118
|
initialProps: { filter: { table: "Position" as keyof (typeof config)["tables"] } },
|
|
113
119
|
});
|
|
114
120
|
|
|
115
|
-
expect(result.all.length).toBe(
|
|
121
|
+
expect(result.all.length).toBe(1);
|
|
116
122
|
expect(result.current.length).toBe(0);
|
|
117
123
|
|
|
118
124
|
const positionUpdates: KeyValue<typeof config, "Position">[] = [
|
|
@@ -129,13 +135,17 @@ describe("useRows", () => {
|
|
|
129
135
|
{ key: { first: "0x03", second: 1 }, value: { value: 4 } },
|
|
130
136
|
];
|
|
131
137
|
|
|
132
|
-
act(() => {
|
|
138
|
+
await act(async () => {
|
|
133
139
|
// Set values in the tables
|
|
134
|
-
for (const update of positionUpdates)
|
|
135
|
-
|
|
140
|
+
for (const update of positionUpdates) {
|
|
141
|
+
await client.tables.Position.set(update.key, update.value);
|
|
142
|
+
}
|
|
143
|
+
for (const update of multiKeyUpdates) {
|
|
144
|
+
await client.tables.MultiKey.set(update.key, update.value);
|
|
145
|
+
}
|
|
136
146
|
});
|
|
137
147
|
|
|
138
|
-
expect(result.all.length).toBe(
|
|
148
|
+
expect(result.all.length).toBe(6);
|
|
139
149
|
expect(result.current.length).toBe(positionUpdates.length);
|
|
140
150
|
expect(result.current).toEqual([
|
|
141
151
|
...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })),
|
|
@@ -143,12 +153,13 @@ describe("useRows", () => {
|
|
|
143
153
|
|
|
144
154
|
// Change the filter
|
|
145
155
|
rerender({ filter: { table: "MultiKey" } });
|
|
156
|
+
await waitForNextUpdate();
|
|
146
157
|
|
|
147
158
|
// Expect hook to rerender three times:
|
|
148
159
|
// 1. New prop, everything else changes the same
|
|
149
160
|
// 2. `filterMemo` is updated by `useDeepMemo` because of the new prop
|
|
150
161
|
// 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned
|
|
151
|
-
expect(result.all.length).toBe(
|
|
162
|
+
expect(result.all.length).toBe(9);
|
|
152
163
|
expect(result.current.length).toBe(multiKeyUpdates.length);
|
|
153
164
|
expect(result.current).toEqual([
|
|
154
165
|
...multiKeyUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "MultiKey" })),
|
|
@@ -2,6 +2,7 @@ import { DatabaseClient, FilterOptions, ScanResult } from "@latticexyz/store-cac
|
|
|
2
2
|
import { StoreConfig } from "@latticexyz/store";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useDeepMemo } from "../utils/useDeepMemo";
|
|
5
|
+
import { useMountedState } from "../utils/useMountedState";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Returns an array of all rows matching the provided filter
|
|
@@ -10,21 +11,22 @@ export function useRows<C extends StoreConfig, T extends keyof C["tables"] & str
|
|
|
10
11
|
storeCache: DatabaseClient<C>,
|
|
11
12
|
filter?: FilterOptions<C, T>
|
|
12
13
|
) {
|
|
13
|
-
const [rows, setRows] =
|
|
14
|
+
const [rows, setRows] = useMountedState<ScanResult<C, T>>([]);
|
|
14
15
|
const filterMemo = useDeepMemo(filter);
|
|
15
16
|
|
|
16
17
|
useEffect(() => {
|
|
17
|
-
|
|
18
|
+
storeCache.scan(filterMemo).then(setRows);
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
+
const unsubscribePromise = storeCache.subscribe(() => {
|
|
20
21
|
// very naive implementation for now, but easier and probably more efficient than
|
|
21
22
|
// manually looping through the `rows` array for every update event
|
|
22
|
-
|
|
23
|
-
},
|
|
23
|
+
storeCache.scan(filterMemo).then(setRows);
|
|
24
|
+
}, filterMemo);
|
|
24
25
|
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
return () => {
|
|
27
|
+
unsubscribePromise.then((unsubscribe) => unsubscribe());
|
|
28
|
+
};
|
|
29
|
+
}, [filterMemo, setRows, storeCache]);
|
|
28
30
|
|
|
29
31
|
return rows;
|
|
30
32
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Use in place of useState when the component may be unmounted before the state is updated.
|
|
5
|
+
*/
|
|
6
|
+
export function useMountedState<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
|
|
7
|
+
const [state, setState] = useState<T>(initialState);
|
|
8
|
+
const mountedRef = useRef(false);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
mountedRef.current = true;
|
|
11
|
+
return () => {
|
|
12
|
+
mountedRef.current = false;
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
const mountedSetState: typeof setState = useCallback((...args) => {
|
|
16
|
+
if (mountedRef.current) {
|
|
17
|
+
setState(...args);
|
|
18
|
+
} else {
|
|
19
|
+
console.warn("Ignoring `setState` call because component unmounted", ...args);
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
return [state, mountedSetState];
|
|
23
|
+
}
|