For users of Svelte v3, this is a read-write variant of Svelte's derived stores that accepts an extra callback to send values back to the source. It builds on the derived & writable stores provided by Svelte, and emulates their behavior as closely as possible.
This project has a Code of Conduct. By participating in the Git repo or issues tracker, you agree to be as courteous, welcoming, and generally a lovely person as its terms require. ๐
- Default export:
writableDerived()
- Regarding Subscription-less
writableDerived
Stores - Examples
- Browser compatibility
Parameters: origins
(store or array of stores), derive
(function), reflect
(function), optional initial
(any)
Returns a store with writable
methods
Create a store that behaves similarly to Svelte's derived
, with origins
, derive
, and initial
working like its 1st, 2nd, and 3rd parameters respectively. Values introduced to the store via its set
and update
methods are passed to the new 3rd parameter, reflect
, which can in turn set values for the origin stores.
It is not possible for derived
and reflect
to trigger calls to each other, provided they only use the set
callbacks provided to them and do not reach out to any outer set
or update
.
Called with: object with reflecting
, old
, and set
properties
Return value varies (see below)
Called when the derived store is given a new value via its set
and update
methods (not via the derive
callback), and can set new values on the origin stores without causing a call to derive
.
reflect
will be called before any of the derived store's subscriptions are called. If this results in any origin stores being set synchronously, their subscriptions will also be called before the derived store's subscriptions.
reflect
is called with one parameter, an object that has these properties:
Name | Description |
---|---|
reflecting |
The new value of the derived store. |
old |
The initial value of the origin stores. It's an array if origins was an array. (This is an accessor property, and has special behavior for derived stores with no subscriptions.) |
set() |
If origins is a single store, this takes its new value. If origins is an array of stores, this takes an array of values to set in each store. If the array you pass in is sparse or shorter than origins , this sets only the stores it has elements for, and other stores don't necessarily need to be writable. (This is an accessor property, and affects the treatment of the return value as per below.) |
If the set
property was not read, reflect
is considered synchronous, and its return value will be used to set origin stores just as if it were passed to set()
. If the set
property was read, reflect
is considered asynchronous, and its return value, if it's a function, is a cleanup function that will be called before the next reflect
call. (Unlike its derive
counterpart, reflect
's cleanup function is never called in response to unsubscriptions.)
It is recommended that your reflect
function use a destructuring parameter, like so:
var coolStore = writableDerived(origins, derive, reflectExample, initial);
function reflectExample({reflecting, old, set}) {
// The set property was read in the process of destructuring.
// Therefore, this function is guaranteed to be treated as asynchronous.
return cleanup;
}
One of the ways writableDerived
emulates the behavior of Svelte's derived
is that it does not subscribe to any origin store until the derived store itself has a subscription. However, writableDerived
makes an exception in certain situations to guarantee that values of interest are up-to-date.
When the derived store has no subscriptions, performing these operations will subscribe to & then unsubscribe from all its origins:
- Calling the derived store's
update
method - Getting the
old
property of the object passed toreflect
import { writable, get } from "svelte/store";
import writableDerived from "svelte-writable-derived";
var jsonStore = writable(`{"I'm a property": true}`);
var objectStore = writableDerived(
jsonStore,
(json) => JSON.parse(json),
({reflecting}) => JSON.stringify(reflecting)
);
console.log( Object.keys( get(objectStore) ) ); // ["I'm a property"]
objectStore.set({"I'm not a property": false});
console.log( get(jsonStore) ); // "{\"I'm not a property\": false}"
import { writable, get } from "svelte/store";
import writableDerived from "svelte-writable-derived";
var objectStore = writable({"a horse": "a horse", "of course": "of course"});
var valueStore = writableDerived(
objectStore,
(object) => object["a horse"],
({reflecting, old}) => {
old["a horse"] = reflecting;
return old; // needed to ensure objectStore.set is called with the correct value
}
);
console.log( get(valueStore) ); // "a horse"
valueStore.set("*whinny*");
console.log( get(objectStore) ); // {"a horse": "*whinny*", "of course": "of course"}
import { writable, get } from "svelte/store";
import writableDerived from "svelte-writable-derived";
var valueStore1 = "sparta", valueStore2 = "monty python's flying circus";
var objectStore = writableDerived(
[valueStore1, valueStore2],
([value1, value2]) => ( {"this is": value1, "it's": value2} ),
({reflecting}) => [ reflecting["this is"], reflecting["it's"] ]
);
console.log( get(objectStore) ); // {"this is": "sparta", "it's": "monty python's flying circus"}
objectStore.set( {"this is": "rocket league", "it's": "over 9000"} );
console.log( get(valueStore1), get(valueStore2) ); // "rocket league" "over 9000"
// What if Rube Goldberg were a JavaScript developer?
import { writable, get } from "svelte/store";
import writableDerived from "svelte-writable-derived";
var jsonStore = writable(`{"owner": "dragon", "possessions": ["crown", "gold"]}`);
var hoardStore = writableDerived(
jsonStore,
(json) => JSON.parse(json),
({reflecting}) => JSON.stringify(reflecting)
);
var hoarderStore = writableDerived(
objectStore,
(hoard) => hoard["owner"],
({reflecting, old}) => {
old["owner"] = reflecting;
return old;
}
);
var hoardContentsStore = writableDerived(
objectStore,
(hoard) => hoard["possessions"],
({reflecting, old}) => {
old["possessions"] = reflecting;
return old;
}
);
var itemListStore = writableDerived(
[hoarderStore, hoardContentsStore],
([hoarder, hoardContents]) => {
return hoardContents.map( (item) => {
return {item, owner: "hoarder"};
});
},
({reflecting}) => {
// This is only for demonstration purposes, so we won't handle differing owners
var hoarder = reflecting[0].owner;
var hoardContents = reflecting.map( (itemListEntry) => {
return itemListEntry["item"];
} );
return [hoarder, hoardContents];
}
);
jsonStore.subscribe(console.log);
hoardStore.subscribe(console.log);
hoarderStore.subscribe(console.log);
// skipping hoardContentsStore
itemListStore.subscribe(console.log);
itemListStore.update( (itemList) => {
return itemList.map( (itemListEntry) => {
return {item: itemListEntry.item, owner: "protagonist"};
} );
} );
/*
reflect runs before subscribers. Since all our reflects are synchronous,
before any subscribers can run, the next layer's set is called, which
calls *its* reflect before *its* subscribers, and so on. This means
stores will call their subscribers starting with the lowest/innermost
layer and going up/out.
Therefore, upon the update, the console logs:
"{\"owner\": \"protagonist\", \"possessions\": [\"crown\", \"gold\"]}"
{owner: "protagonist", possessions: ["crown", "gold"]}
"protagonist"
[{item: "crown", owner: "protagonist"}, {item: "gold", owner: "protagonist"}]
*/
This package should run anywhere Svelte can run. Use transpilers/polyfills as needed.
I muchly appreciate any way you'd like to show your thanks - knowing people are helped gives me warm fuzzies and makes it all worthwhile!
I'm on Ko-Fi! If you'd like to make a recurring donation, first please help me afford Ko-Fi Gold!
Current contact info is on this page - or you can create an "issue" on this repo just to say thanks! Thank-you "issues" will be closed right away, but are treasured regardless~