@latticexyz/react 2.0.0-next.2 → 2.0.0-next.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Change Log
2
2
 
3
+ ## 2.0.0-next.4
4
+
5
+ ### Major Changes
6
+
7
+ - [#1343](https://github.com/latticexyz/mud/pull/1343) [`e3de1a33`](https://github.com/latticexyz/mud/commit/e3de1a338fe110ac33ba9fb833366541e4cf4cf1) Thanks [@holic](https://github.com/holic)! - Removes `useRow` and `useRows` hooks, previously powered by `store-cache`, which is now deprecated. Please use `recs` and the corresponding `useEntityQuery` and `useComponentValue` hooks. We'll have more hooks soon for SQL.js sync backends.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`ce7125a1`](https://github.com/latticexyz/mud/commit/ce7125a1b97efd3db47c5ea1593d5a37ba143f64), [`c14f8bf1`](https://github.com/latticexyz/mud/commit/c14f8bf1ec8c199902c12899853ac144aa69bb9c)]:
12
+ - @latticexyz/recs@2.0.0-next.4
13
+ - @latticexyz/store@2.0.0-next.4
14
+
15
+ ## 2.0.0-next.3
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [[`952cd534`](https://github.com/latticexyz/mud/commit/952cd534447d08e6231ab147ed1cc24fb49bbb57), [`d5b73b12`](https://github.com/latticexyz/mud/commit/d5b73b12666699c442d182ee904fa8747b78fefd), [`433078c5`](https://github.com/latticexyz/mud/commit/433078c54c22fa1b4e32d7204fb41bd5f79ca1db), [`afaf2f5f`](https://github.com/latticexyz/mud/commit/afaf2f5ffb36fe389a3aba8da2f6d8c84bdb26ab), [`0d12db8c`](https://github.com/latticexyz/mud/commit/0d12db8c2170905f5116111e6bc417b6dca8eb61)]:
20
+ - @latticexyz/store@2.0.0-next.3
21
+ - @latticexyz/store-cache@2.0.0-next.3
22
+ - @latticexyz/recs@2.0.0-next.3
23
+
3
24
  ## 2.0.0-next.2
4
25
 
5
26
  ### Minor Changes
package/README.md CHANGED
@@ -2,34 +2,6 @@
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
5
  ## Hooks for `recs`
34
6
 
35
7
  ### useComponentValue
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{defineQuery as S,getComponentValue as m,Has as C,isComponentUpdate as T}from"@latticexyz/recs";import{useEffect as x,useState as g}from"react";function J(e,t,s){let[n,o]=g(t!=null?m(e,t):void 0);return x(()=>{if(o(t!=null?m(e,t):void 0),t==null)return;let a=S([C(e)],{runOnInit:!1}).update$.subscribe(u=>{if(T(u,e)&&u.entity===t){let[l]=u.value;o(l)}});return()=>a.unsubscribe()},[e,t]),n??s}import{useEffect as y,useState as R}from"react";var X=e=>{let[t,s]=R(e.get());return y(()=>{let n=e.observe_(()=>s(e.get()));return()=>n()},[e]),t};import{defineQuery as v}from"@latticexyz/recs";import{useEffect as h,useMemo as D,useState as k}from"react";import{useEffect as V,useState as E}from"react";import O from"fast-deep-equal";var i=e=>{let[t,s]=E(e);return V(()=>{O(e,t)||s(e)},[e]),t};import w from"fast-deep-equal";import{distinctUntilChanged as F,map as M}from"rxjs";function ie(e,t){let s=t?.updateOnValueChange??!0,n=i(e),o=D(()=>v(n,{runOnInit:!0}),[n]),[r,a]=k([...o.matching]);return h(()=>{a([...o.matching]);let u=o.update$.pipe(M(()=>[...o.matching]));s||(u=u.pipe(F((f,d)=>w(f,d))));let l=u.subscribe(f=>a(f));return()=>l.unsubscribe()},[o,s]),r}import{useEffect as P,useState as q}from"react";function me(e,t){let[s,n]=q(t);return P(()=>{let o=e.subscribe(n);return()=>o.unsubscribe()},[e]),s}import{useEffect as p,useRef as U,useState as I}from"react";function be(e){let t=U(e),[s,n]=I(e==null?{status:"idle"}:{status:"pending"});return p(()=>{e!==t.current&&(t.current=e,n(e==null?{status:"idle"}:{status:"pending"}))},[e]),p(()=>{e!=null&&Promise.allSettled([e]).then(([o])=>{e===t.current&&n(o)})},[e]),s}import{useEffect as $}from"react";import{useCallback as Q,useEffect as A,useRef as K,useState as _}from"react";function c(e){let[t,s]=_(e),n=K(!1);A(()=>(n.current=!0,()=>{n.current=!1}));let o=Q((...r)=>{n.current?s(...r):console.warn("Ignoring `setState` call because component unmounted",...r)},[]);return[t,o]}function b(e,t){let[s,n]=c([]),o=i(t);return $(()=>{e.scan(o).then(n);let r=e.subscribe(()=>{e.scan(o).then(n)},o);return()=>{r.then(a=>a())}},[o,n,e]),s}function ve(e,t){let{namespace:s,table:n,key:o}=t;return b(e,{namespace:s,table:n,key:{eq:o}})[0]}export{J as useComponentValue,X as useDeprecatedComputedValue,ie as useEntityQuery,me as useObservableValue,be as usePromise,ve as useRow,b as useRows};
1
+ import{defineQuery as b,getComponentValue as f,Has as d,isComponentUpdate as S}from"@latticexyz/recs";import{useEffect as C,useState as T}from"react";function H(e,t,n){let[s,u]=T(t!=null?f(e,t):void 0);return C(()=>{if(u(t!=null?f(e,t):void 0),t==null)return;let r=b([d(e)],{runOnInit:!1}).update$.subscribe(o=>{if(S(o,e)&&o.entity===t){let[a]=o.value;u(a)}});return()=>r.unsubscribe()},[e,t]),s??n}import{useEffect as x,useState as V}from"react";var z=e=>{let[t,n]=V(e.get());return x(()=>{let s=e.observe_(()=>n(e.get()));return()=>s()},[e]),t};import{defineQuery as h}from"@latticexyz/recs";import{useEffect as y,useMemo as O,useState as R}from"react";import{useEffect as g,useState as v}from"react";import E from"fast-deep-equal";var m=e=>{let[t,n]=v(e);return g(()=>{E(e,t)||n(e)},[e]),t};import P from"fast-deep-equal";import{distinctUntilChanged as q,map as Q}from"rxjs";function te(e,t){let n=t?.updateOnValueChange??!0,s=m(e),u=O(()=>h(s,{runOnInit:!0}),[s]),[l,r]=R([...u.matching]);return y(()=>{r([...u.matching]);let o=u.update$.pipe(Q(()=>[...u.matching]));n||(o=o.pipe(q((i,c)=>P(i,c))));let a=o.subscribe(i=>r(i));return()=>a.unsubscribe()},[u,n]),l}import{useEffect as I,useState as U}from"react";function se(e,t){let[n,s]=U(t);return I(()=>{let u=e.subscribe(s);return()=>u.unsubscribe()},[e]),n}import{useEffect as p,useRef as D,useState as F}from"react";function ae(e){let t=D(e),[n,s]=F(e==null?{status:"idle"}:{status:"pending"});return p(()=>{e!==t.current&&(t.current=e,s(e==null?{status:"idle"}:{status:"pending"}))},[e]),p(()=>{e!=null&&Promise.allSettled([e]).then(([u])=>{e===t.current&&s(u)})},[e]),n}export{H as useComponentValue,z as useDeprecatedComputedValue,te as useEntityQuery,se as useObservableValue,ae as usePromise};
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/usePromise.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 Schema,\n} from \"@latticexyz/recs\";\nimport { useEffect, useState } from \"react\";\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S>,\n entity: Entity | undefined,\n defaultValue: ComponentValue<S>\n): ComponentValue<S>;\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S>,\n entity: Entity | undefined\n): ComponentValue<S> | undefined;\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S>,\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 { useEffect, useRef, useState } from \"react\";\n\n// TODO: narrow type so `null`/`undefined` always return `{status: \"idle\"}`?\n\nexport type UsePromiseResult<T> = PromiseSettledResult<Awaited<T>> | { status: \"pending\" } | { status: \"idle\" };\n\nexport function usePromise<T>(promise: PromiseLike<T> | null | undefined) {\n const promiseRef = useRef(promise);\n const [result, setResult] = useState<UsePromiseResult<T>>(\n promise == null ? { status: \"idle\" } : { status: \"pending\" }\n );\n\n useEffect(() => {\n if (promise !== promiseRef.current) {\n promiseRef.current = promise;\n setResult(promise == null ? { status: \"idle\" } : { status: \"pending\" });\n }\n }, [promise]);\n\n useEffect(() => {\n if (promise == null) return;\n // TODO: do we need to check if result is already populated?\n Promise.allSettled([promise]).then(([settled]) => {\n if (promise === promiseRef.current) {\n setResult(settled);\n }\n });\n }, [promise]);\n\n return result;\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","b","subscription","useEffect","useState","useObservableValue","observable","defaultValue","value","setValue","subscription","useEffect","useRef","useState","usePromise","promise","promiseRef","result","setResult","settled","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"]}
1
+ {"version":3,"sources":["../src/useComponentValue.ts","../src/useDeprecatedComputedValue.ts","../src/useEntityQuery.ts","../src/utils/useDeepMemo.ts","../src/useObservableValue.ts","../src/usePromise.ts"],"sourcesContent":["import {\n Component,\n ComponentValue,\n defineQuery,\n Entity,\n getComponentValue,\n Has,\n isComponentUpdate,\n Schema,\n} from \"@latticexyz/recs\";\nimport { useEffect, useState } from \"react\";\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S>,\n entity: Entity | undefined,\n defaultValue: ComponentValue<S>\n): ComponentValue<S>;\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S>,\n entity: Entity | undefined\n): ComponentValue<S> | undefined;\n\nexport function useComponentValue<S extends Schema>(\n component: Component<S>,\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 { useEffect, useRef, useState } from \"react\";\n\n// TODO: narrow type so `null`/`undefined` always return `{status: \"idle\"}`?\n\nexport type UsePromiseResult<T> = PromiseSettledResult<Awaited<T>> | { status: \"pending\" } | { status: \"idle\" };\n\nexport function usePromise<T>(promise: PromiseLike<T> | null | undefined) {\n const promiseRef = useRef(promise);\n const [result, setResult] = useState<UsePromiseResult<T>>(\n promise == null ? { status: \"idle\" } : { status: \"pending\" }\n );\n\n useEffect(() => {\n if (promise !== promiseRef.current) {\n promiseRef.current = promise;\n setResult(promise == null ? { status: \"idle\" } : { status: \"pending\" });\n }\n }, [promise]);\n\n useEffect(() => {\n if (promise == null) return;\n // TODO: do we need to check if result is already populated?\n Promise.allSettled([promise]).then(([settled]) => {\n if (promise === promiseRef.current) {\n setResult(settled);\n }\n });\n }, [promise]);\n\n return result;\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","b","subscription","useEffect","useState","useObservableValue","observable","defaultValue","value","setValue","subscription","useEffect","useRef","useState","usePromise","promise","promiseRef","result","setResult","settled"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latticexyz/react",
3
- "version": "2.0.0-next.2",
3
+ "version": "2.0.0-next.4",
4
4
  "description": "React tools for MUD client.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,9 +18,8 @@
18
18
  "mobx": "^6.7.0",
19
19
  "react": "^18.2.0",
20
20
  "rxjs": "7.5.5",
21
- "@latticexyz/store": "2.0.0-next.2",
22
- "@latticexyz/recs": "2.0.0-next.2",
23
- "@latticexyz/store-cache": "2.0.0-next.2"
21
+ "@latticexyz/recs": "2.0.0-next.4",
22
+ "@latticexyz/store": "2.0.0-next.4"
24
23
  },
25
24
  "devDependencies": {
26
25
  "@testing-library/react-hooks": "^8.0.1",
@@ -28,6 +27,7 @@
28
27
  "@vitejs/plugin-react": "^4.0.0",
29
28
  "eslint-plugin-react": "7.31.11",
30
29
  "eslint-plugin-react-hooks": "4.6.0",
30
+ "jsdom": "^22.1.0",
31
31
  "react-test-renderer": "^18.2.0",
32
32
  "tsup": "^6.7.0",
33
33
  "vite": "^4.3.6",
package/src/index.ts CHANGED
@@ -3,4 +3,3 @@ export * from "./useDeprecatedComputedValue";
3
3
  export * from "./useEntityQuery";
4
4
  export * from "./useObservableValue";
5
5
  export * from "./usePromise";
6
- export * from "./store-cache";
@@ -1,2 +0,0 @@
1
- export * from "./useRows";
2
- export * from "./useRow";
@@ -1,197 +0,0 @@
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", async () => {
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
- await act(async () => {
42
- // Set values in the tables
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
- }
49
- });
50
-
51
- expect(result.current).toEqual({
52
- key: { key: "0x01" },
53
- value: { x: 2, y: 3 },
54
- namespace: config["namespace"],
55
- table: "Position",
56
- });
57
-
58
- await act(async () => {
59
- for (const update of positionUpdates.slice(0, 3)) {
60
- await client.tables.Position.remove(update.key);
61
- }
62
- });
63
-
64
- expect(result.current).toBe(undefined);
65
- });
66
-
67
- it("should re-render only when the position value of the specified key changes", async () => {
68
- const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x00" } }));
69
- expect(result.all.length).toBe(1);
70
-
71
- // Update the position table
72
- await act(async () => {
73
- await client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
74
- });
75
- expect(result.all.length).toBe(3);
76
- expect(result.current).toEqual({
77
- key: { key: "0x00" },
78
- value: { x: 1, y: 2 },
79
- namespace: config["namespace"],
80
- table: "Position",
81
- });
82
-
83
- // Update an unrelated table
84
- await act(async () => {
85
- await client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
86
- });
87
- expect(result.all.length).toBe(3);
88
- expect(result.current).toEqual({
89
- key: { key: "0x00" },
90
- value: { x: 1, y: 2 },
91
- namespace: config["namespace"],
92
- table: "Position",
93
- });
94
-
95
- // Update the position table
96
- await act(async () => {
97
- await client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
98
- });
99
- expect(result.all.length).toBe(4);
100
- expect(result.current).toEqual({
101
- key: { key: "0x00" },
102
- value: { x: 2, y: 2 },
103
- namespace: config["namespace"],
104
- table: "Position",
105
- });
106
-
107
- // Update an unrelated key
108
- await act(async () => {
109
- await client.tables.Position.set({ key: "0x01" }, { x: 2, y: 2 });
110
- });
111
- expect(result.all.length).toBe(4);
112
- expect(result.current).toEqual({
113
- key: { key: "0x00" },
114
- value: { x: 2, y: 2 },
115
- namespace: config["namespace"],
116
- table: "Position",
117
- });
118
-
119
- // Update the position table
120
- await act(async () => {
121
- await client.tables.Position.remove({ key: "0x00" });
122
- });
123
- expect(result.all.length).toBe(5);
124
- expect(result.current).toEqual(undefined);
125
- });
126
-
127
- it("should re-render when the filter changes", async () => {
128
- const { result, rerender, waitForNextUpdate } = renderHook(({ filter }) => useRow(client, filter), {
129
- initialProps: {
130
- filter: { table: "Position", key: { key: "0x01" } } as UseRowFilterOptions<typeof config>,
131
- },
132
- });
133
-
134
- expect(result.all.length).toBe(1);
135
- expect(result.current).toBe(undefined);
136
-
137
- const positionUpdates: KeyValue<typeof config, "Position">[] = [
138
- { key: { key: "0x00" }, value: { x: 1, y: 2 } },
139
- { key: { key: "0x01" }, value: { x: 2, y: 3 } },
140
- { key: { key: "0x02" }, value: { x: 3, y: 4 } },
141
- { key: { key: "0x03" }, value: { x: 4, y: 5 } },
142
- ];
143
-
144
- const multiKeyUpdates: KeyValue<typeof config, "MultiKey">[] = [
145
- { key: { first: "0x00", second: 4 }, value: { value: 1 } },
146
- { key: { first: "0x01", second: 3 }, value: { value: 2 } },
147
- { key: { first: "0x02", second: 2 }, value: { value: 3 } },
148
- { key: { first: "0x03", second: 1 }, value: { value: 4 } },
149
- ];
150
-
151
- await act(async () => {
152
- // Set values in the tables
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
- }
159
- });
160
-
161
- expect(result.all.length).toBe(3);
162
- expect(result.current).toEqual({
163
- key: { key: "0x01" },
164
- value: { x: 2, y: 3 },
165
- namespace: config["namespace"],
166
- table: "Position",
167
- });
168
-
169
- // Change the filter
170
- rerender({ filter: { table: "Position", key: { key: "0x02" } } });
171
- await waitForNextUpdate();
172
-
173
- // Expect hook to rerender three times:
174
- // 1. New prop, everything else changes the same
175
- // 2. `filterMemo` is updated by `useDeepMemo` because of the new prop
176
- // 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned
177
- expect(result.all.length).toBe(6);
178
- expect(result.current).toEqual({
179
- key: { key: "0x02" },
180
- value: { x: 3, y: 4 },
181
- namespace: config["namespace"],
182
- table: "Position",
183
- });
184
-
185
- // Change the filter
186
- rerender({ filter: { table: "MultiKey", key: { first: "0x00", second: 4 } } });
187
- await waitForNextUpdate();
188
-
189
- expect(result.all.length).toBe(9);
190
- expect(result.current).toEqual({
191
- key: { first: "0x00", second: 4 },
192
- value: { value: 1 },
193
- namespace: config["namespace"],
194
- table: "MultiKey",
195
- });
196
- });
197
- });
@@ -1,23 +0,0 @@
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
- }
@@ -1,168 +0,0 @@
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", async () => {
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
- await act(async () => {
42
- // Set values in the tables
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
- }
49
- });
50
-
51
- expect(result.current.length).toBe(positionUpdates.length);
52
- expect(result.current).toEqual([
53
- ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })),
54
- ]);
55
-
56
- await act(async () => {
57
- for (const update of positionUpdates.slice(0, 3)) {
58
- await client.tables.Position.remove(update.key);
59
- }
60
- });
61
-
62
- expect(result.current.length).toBe(1);
63
- expect(result.current).toEqual([
64
- { key: { key: "0x03" }, value: { x: 4, y: 5 }, namespace: config["namespace"], table: "Position" },
65
- ]);
66
- });
67
-
68
- it("should re-render only when the position table changes", async () => {
69
- const { result } = renderHook(() => useRows(client, { namespace: config["namespace"], table: "Position" }));
70
- expect(result.all.length).toBe(1);
71
-
72
- // Update the position table
73
- await act(async () => {
74
- await client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 });
75
- });
76
- expect(result.all.length).toBe(3);
77
- expect(result.current).toEqual([
78
- { key: { key: "0x00" }, value: { x: 1, y: 2 }, namespace: config["namespace"], table: "Position" },
79
- ]);
80
-
81
- // Update an unrelated table
82
- await act(async () => {
83
- await client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 });
84
- });
85
- expect(result.all.length).toBe(3);
86
- expect(result.current).toEqual([
87
- { key: { key: "0x00" }, value: { x: 1, y: 2 }, namespace: config["namespace"], table: "Position" },
88
- ]);
89
-
90
- // Update the position table
91
- await act(async () => {
92
- await client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 });
93
- });
94
- expect(result.all.length).toBe(4);
95
- expect(result.current).toEqual([
96
- { key: { key: "0x00" }, value: { x: 2, y: 2 }, namespace: config["namespace"], table: "Position" },
97
- ]);
98
-
99
- // Update an unrelated table
100
- await act(async () => {
101
- await client.tables.MultiKey.remove({ first: "0x03", second: 1 });
102
- });
103
- expect(result.all.length).toBe(4);
104
- expect(result.current).toEqual([
105
- { key: { key: "0x00" }, value: { x: 2, y: 2 }, namespace: config["namespace"], table: "Position" },
106
- ]);
107
-
108
- // Update the position table
109
- await act(async () => {
110
- await client.tables.Position.remove({ key: "0x00" });
111
- });
112
- expect(result.all.length).toBe(5);
113
- expect(result.current).toEqual([]);
114
- });
115
-
116
- it("should re-render when the filter changes", async () => {
117
- const { result, rerender, waitForNextUpdate } = renderHook(({ filter }) => useRows(client, filter), {
118
- initialProps: { filter: { table: "Position" as keyof (typeof config)["tables"] } },
119
- });
120
-
121
- expect(result.all.length).toBe(1);
122
- expect(result.current.length).toBe(0);
123
-
124
- const positionUpdates: KeyValue<typeof config, "Position">[] = [
125
- { key: { key: "0x00" }, value: { x: 1, y: 2 } },
126
- { key: { key: "0x01" }, value: { x: 2, y: 3 } },
127
- { key: { key: "0x02" }, value: { x: 3, y: 4 } },
128
- { key: { key: "0x03" }, value: { x: 4, y: 5 } },
129
- ];
130
-
131
- const multiKeyUpdates: KeyValue<typeof config, "MultiKey">[] = [
132
- { key: { first: "0x00", second: 4 }, value: { value: 1 } },
133
- { key: { first: "0x01", second: 3 }, value: { value: 2 } },
134
- { key: { first: "0x02", second: 2 }, value: { value: 3 } },
135
- { key: { first: "0x03", second: 1 }, value: { value: 4 } },
136
- ];
137
-
138
- await act(async () => {
139
- // Set values in the tables
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
- }
146
- });
147
-
148
- expect(result.all.length).toBe(6);
149
- expect(result.current.length).toBe(positionUpdates.length);
150
- expect(result.current).toEqual([
151
- ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })),
152
- ]);
153
-
154
- // Change the filter
155
- rerender({ filter: { table: "MultiKey" } });
156
- await waitForNextUpdate();
157
-
158
- // Expect hook to rerender three times:
159
- // 1. New prop, everything else changes the same
160
- // 2. `filterMemo` is updated by `useDeepMemo` because of the new prop
161
- // 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned
162
- expect(result.all.length).toBe(9);
163
- expect(result.current.length).toBe(multiKeyUpdates.length);
164
- expect(result.current).toEqual([
165
- ...multiKeyUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "MultiKey" })),
166
- ]);
167
- });
168
- });
@@ -1,32 +0,0 @@
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
- import { useMountedState } from "../utils/useMountedState";
6
-
7
- /**
8
- * Returns an array of all rows matching the provided filter
9
- */
10
- export function useRows<C extends StoreConfig, T extends keyof C["tables"] & string>(
11
- storeCache: DatabaseClient<C>,
12
- filter?: FilterOptions<C, T>
13
- ) {
14
- const [rows, setRows] = useMountedState<ScanResult<C, T>>([]);
15
- const filterMemo = useDeepMemo(filter);
16
-
17
- useEffect(() => {
18
- storeCache.scan(filterMemo).then(setRows);
19
-
20
- const unsubscribePromise = storeCache.subscribe(() => {
21
- // very naive implementation for now, but easier and probably more efficient than
22
- // manually looping through the `rows` array for every update event
23
- storeCache.scan(filterMemo).then(setRows);
24
- }, filterMemo);
25
-
26
- return () => {
27
- unsubscribePromise.then((unsubscribe) => unsubscribe());
28
- };
29
- }, [filterMemo, setRows, storeCache]);
30
-
31
- return rows;
32
- }