-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
Describe the problem
When a derived store has multiple parent stores, its callback might sometimes want to know which parent store changed and triggered the callback. This would mostly be useful in advanced callbacks that use set
(and, if #6750 is merged, update
) to calculate their value.
Describe the proposed solution
The derived store's callback could take an optional parameter after set
(and after update
, if #6750 is merged), which would be an array of Booleans describing which parent store(s) were updated. The Booleans would be in the same order as the parent stores, i.e. if stores[i]
was updated, then updated[i]
(or changed[i]
, see naming discussion below) would be true.
This would actually be quite simple to implement, looking something like this:
diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts
index 51a13b85e..a67f11fe2 100644
--- a/src/runtime/store/index.ts
+++ b/src/runtime/store/index.ts
@@ -182,6 +182,7 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
const values = [];
let pending = 0;
+ let changed = [];
let cleanup = noop;
const sync = () => {
@@ -189,7 +190,8 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
return;
}
cleanup();
- const result = fn(single ? values[0] : values, set, update);
+ const result = fn(single ? values[0] : values, set, update, single ? changed[0] : changed);
+ changed.fill(false);
if (auto) {
set(result as T);
} else {
@@ -202,6 +204,7 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
(value) => {
values[i] = value;
pending &= ~(1 << i);
+ changed[i] = true;
if (inited) {
sync();
}
Of course, type definitions would also need to change, so the actual PR would have more to it than that. But the core of this feature can be implemented in just four lines of code.
Alternatives considered
Some use cases for this feature can be handled with the status quo:
// Can't do this yet:
const discountedPrice = derived(
[price, discount],
([$price, $discount], set, [priceChanged, discountChanged]) => {
if (priceChanged) {
console.log('we promise to keep discounted price the same even when regular price changes');
}
if (discountChanged) {
console.log('new discount is calculated from current price');
set($price - $discount);
}
});
// Have to do this instead:
const manualDiscounts = (() => {
let currentPrice;
price.subscribe(p => currentPrice = p);
return derived(discount, $discount => currentPrice - $discount);
})();
Perhaps not the best example, as the manualDiscounts store is simpler to read than the version with a changed
array. But imagine a complex state being stored in the derived store: the current user, the shopping cart contents, and data about the product being viewed right now (product description, review scores, top five reviews...).
Importance
nice to have
Miscellaneous information
The first version of this proposal asked for a bitmap instead of a Boolean array. But once I realized that array destructuring would allow giving names to the items in the array, so that you could write [priceChanged, discountChanged]
and then reference priceChanged
instead of changed[0]
, the Boolean array version of the idea became much, much better, so I abandoned the bitmap idea.