ptzagk / svelte-writable-derived

Two-way data-transforming stores for Svelte

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

svelte-writable-derived

npm Bundle size License

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()

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.

New parameter: reflect()

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;
}

Regarding Subscription-less writableDerived Stores

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 to reflect

Examples

Making an object store from a JSON string store

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}"

Making a single-value store from an object store

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"}

Making an object store from several single-value stores

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"

Chaining all of the above together

// 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"}]
*/

Browser compatibility

This package should run anywhere Svelte can run. Use transpilers/polyfills as needed.

πŸ’– Support the developer

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!

πŸ’Έ ... with money

I'm on Ko-Fi! If you'd like to make a recurring donation, first please help me afford Ko-Fi Gold!

πŸ’Œ ... with kind words

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~

🀝 ... with a job

I have a Developer Story on Stack Overflow!

About

Two-way data-transforming stores for Svelte

License:MIT License


Languages

Language:JavaScript 100.0%