Skip to content

Provide "what changed" array (of Booleans) to derived stores #6777

@rmunn

Description

@rmunn

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions