@talismn/balances-react 0.1.7 → 0.1.8

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,19 @@
1
1
  # @talismn/balances-react
2
2
 
3
+ ## 0.1.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 3db774a: fix: useBalances creating too many subscriptions when called from multiple components
8
+ - Updated dependencies [cbd4770]
9
+ - Updated dependencies [3db774a]
10
+ - @talismn/balances-substrate-native@0.1.8
11
+ - @talismn/balances-substrate-orml@0.1.8
12
+ - @talismn/balances@0.1.8
13
+ - @talismn/balances-evm-erc20@0.1.8
14
+ - @talismn/balances-evm-native@0.1.8
15
+ - @talismn/balances-example@0.1.8
16
+
3
17
  ## 0.1.7
4
18
 
5
19
  ### Patch Changes
@@ -18,6 +18,89 @@ import { useChains, useEvmNetworks, useTokens } from "./useChaindata";
18
18
  export function useBalances(
19
19
  // TODO: Make this array of BalanceModules more type-safe
20
20
  balanceModules, chaindataProvider, addressesByToken) {
21
+ useBalancesSubscriptions(balanceModules, chaindataProvider, addressesByToken);
22
+ const chains = useChains(chaindataProvider);
23
+ const evmNetworks = useEvmNetworks(chaindataProvider);
24
+ const tokens = useTokens(chaindataProvider);
25
+ const balances = useLiveQuery(() => __awaiter(this, void 0, void 0, function* () {
26
+ return new Balances(yield db.balances
27
+ .filter((balance) => {
28
+ if (!balanceModules.map(({ type }) => type).includes(balance.source))
29
+ return false;
30
+ if (!addressesByToken)
31
+ return false;
32
+ if (!Object.keys(addressesByToken).includes(balance.tokenId))
33
+ return false;
34
+ if (!addressesByToken[balance.tokenId].includes(balance.address))
35
+ return false;
36
+ return true;
37
+ })
38
+ .toArray(), { chains, evmNetworks, tokens });
39
+ }), [balanceModules, addressesByToken, chains, evmNetworks, tokens]);
40
+ // debounce every 100ms to prevent hammering UI with updates
41
+ const [debouncedBalances, setDebouncedBalances] = useState(balances);
42
+ useDebounce(() => balances && setDebouncedBalances(balances), 100, [balances]);
43
+ return debouncedBalances;
44
+ }
45
+ // TODO: Turn into react context
46
+ const subscriptions = {};
47
+ // This hook is responsible for allowing us to call useBalances
48
+ // from multiple components, without setting up unnecessary
49
+ // balance subscriptions
50
+ function useBalancesSubscriptions(
51
+ // TODO: Make this array of BalanceModules more type-safe
52
+ balanceModules, chaindataProvider, addressesByToken) {
53
+ // const subscriptions = useRef<
54
+ // Record<string, { unsub: Promise<() => void>; refcount: number; generation: number }>
55
+ // >({})
56
+ const addSubscription = (key, balanceModule, chainConnector, chaindataProvider, addressesByToken) => {
57
+ var _a;
58
+ // create subscription if it doesn't already exist
59
+ if (!subscriptions[key] || subscriptions[key].refcount === 0) {
60
+ const generation = ((((_a = subscriptions[key]) === null || _a === void 0 ? void 0 : _a.generation) || 0) + 1) % Number.MAX_SAFE_INTEGER;
61
+ const unsub = balancesFn(balanceModule, chainConnector, chaindataProvider, addressesByToken, (error, balances) => {
62
+ if (error)
63
+ return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
64
+ if (!balances)
65
+ return;
66
+ // ignore balances from old subscriptions which are still in the process of unsubscribing
67
+ if (subscriptions[key].generation !== generation)
68
+ return;
69
+ const putBalances = Object.entries(balances.toJSON()).map(([id, balance]) => (Object.assign({ id }, balance)));
70
+ db.transaction("rw", db.balances, () => __awaiter(this, void 0, void 0, function* () { return yield db.balances.bulkPut(putBalances); }));
71
+ });
72
+ subscriptions[key] = { unsub, refcount: 0, generation };
73
+ }
74
+ // bump up the refcount by 1
75
+ subscriptions[key].refcount += 1;
76
+ };
77
+ const removeSubscription = (key, balanceModule, addressesByToken) => {
78
+ // ignore dead subscriptions
79
+ if (!subscriptions[key] || subscriptions[key].refcount === 0)
80
+ return;
81
+ // drop the refcount by one
82
+ subscriptions[key].refcount -= 1;
83
+ // unsubscribe if refcount is now 0 (nobody wants this subcription anymore)
84
+ if (subscriptions[key].refcount < 1) {
85
+ // remove subscription
86
+ subscriptions[key].unsub.then((unsub) => unsub());
87
+ delete subscriptions[key];
88
+ // set this subscription's balances in the store to status: cache
89
+ db.transaction("rw", db.balances, () => __awaiter(this, void 0, void 0, function* () {
90
+ return yield db.balances
91
+ .filter((balance) => {
92
+ if (balance.source !== balanceModule.type)
93
+ return false;
94
+ if (!Object.keys(addressesByToken).includes(balance.tokenId))
95
+ return false;
96
+ if (!addressesByToken[balance.tokenId].includes(balance.address))
97
+ return false;
98
+ return true;
99
+ })
100
+ .modify({ status: "cache" });
101
+ }));
102
+ }
103
+ };
21
104
  const chainConnector = useChainConnector(chaindataProvider);
22
105
  useEffect(() => {
23
106
  if (chainConnector === null)
@@ -26,28 +109,16 @@ balanceModules, chaindataProvider, addressesByToken) {
26
109
  return;
27
110
  if (addressesByToken === null)
28
111
  return;
29
- const unsubscribePromises = balanceModules.map((balanceModule) => balancesFn(balanceModule, chainConnector, chaindataProvider, addressesByToken, (error, balances) => {
30
- if (error)
31
- return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
32
- if (!balances)
33
- return;
34
- db.transaction("rw", db.balances, () => __awaiter(this, void 0, void 0, function* () {
35
- yield db.balances.bulkPut(Object.entries(balances.toJSON()).map(([id, balance]) => (Object.assign({ id }, balance))));
36
- }));
37
- }));
38
- // TODO: Set balances status to cache on unmount
39
- return () => {
40
- unsubscribePromises.forEach((unsubscribePromise) => unsubscribePromise.then((unsub) => unsub()));
41
- };
112
+ const unsubs = balanceModules.map((balanceModule) => {
113
+ const subscriptionKey = `${balanceModule.type}-${JSON.stringify(addressesByToken)}`;
114
+ // add balance subscription for this module
115
+ addSubscription(subscriptionKey, balanceModule, chainConnector, chaindataProvider, addressesByToken);
116
+ // return an unsub method, to be called when this effect unmounts
117
+ return () => removeSubscription(subscriptionKey, balanceModule, addressesByToken);
118
+ });
119
+ const unsubAll = () => unsubs.forEach((unsub) => unsub());
120
+ return unsubAll;
42
121
  }, [addressesByToken, chainConnector]);
43
- const chains = useChains(chaindataProvider);
44
- const evmNetworks = useEvmNetworks(chaindataProvider);
45
- const tokens = useTokens(chaindataProvider);
46
- const balances = useLiveQuery(() => __awaiter(this, void 0, void 0, function* () { return new Balances(yield db.balances.toArray(), { chains, evmNetworks, tokens }); }), [chains, evmNetworks, tokens]);
47
- // debounce every 100ms to prevent hammering UI with updates
48
- const [debouncedBalances, setDebouncedBalances] = useState(balances);
49
- useDebounce(() => balances && setDebouncedBalances(balances), 100, [balances]);
50
- return debouncedBalances;
51
122
  }
52
123
  // TODO: Allow advanced users of this library to provide their own chain connector
53
124
  function useChainConnector(chaindataProvider) {
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { ChaindataProviderExtension } from "@talismn/chaindata-provider-extension";
11
11
  import { useEffect, useState } from "react";
12
12
  import log from "../log";
13
+ // TODO: Allow user to call useChaindata from multiple places
13
14
  export function useChaindata() {
14
15
  const [chaindataProvider, setChaindataProvider] = useState(null);
15
16
  // this number is incremented each time the chaindataProvider has fetched new data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances-react",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "UNLICENSED",
@@ -30,12 +30,12 @@
30
30
  "clean": "rm -rf dist && rm -rf .turbo rm -rf node_modules"
31
31
  },
32
32
  "dependencies": {
33
- "@talismn/balances": "^0.1.7",
34
- "@talismn/balances-evm-erc20": "^0.1.7",
35
- "@talismn/balances-evm-native": "^0.1.7",
36
- "@talismn/balances-example": "^0.1.7",
37
- "@talismn/balances-substrate-native": "^0.1.7",
38
- "@talismn/balances-substrate-orml": "^0.1.7",
33
+ "@talismn/balances": "^0.1.8",
34
+ "@talismn/balances-evm-erc20": "^0.1.8",
35
+ "@talismn/balances-evm-native": "^0.1.8",
36
+ "@talismn/balances-example": "^0.1.8",
37
+ "@talismn/balances-substrate-native": "^0.1.8",
38
+ "@talismn/balances-substrate-orml": "^0.1.8",
39
39
  "@talismn/chain-connector": "^0.1.4",
40
40
  "@talismn/chaindata-provider": "^0.1.4",
41
41
  "@talismn/chaindata-provider-extension": "^0.1.4",