Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

svelte-writable-derived

PixievoltNo198.1kMIT3.1.1TypeScript support: included

Two-way data-transforming stores for Svelte

svelte, svelte3, svelte4, svelte5, derived, store

readme

svelte-writable-derived

npm Bundle size GitHub CI Status License GitHub Repo stars

For users of Svelte v3 and v4, 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 & named export: writableDerived()

Parameters: origins (store or array of stores), derive (function), reflect (see documentation), 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.

As long as derived and reflect set stores only by the means provided to them and not via any store's methods, they won't trigger calls to each other.

New parameter: reflect

Function with parameters: reflecting (any), optional old (any)

This function is called when the derived store gets a new value via its set and update methods (not via the derive callback). Its reflecting parameter is this new value, and old is the origin store's current value, or an array of values if origins is an array. It must return a value to set in the origin store, or an array of values to set if origins was an array. If the returned array is sparse or shorter than origins, it will only set the stores it has elements for, and other stores don't necessarily need to be writable.

reflect is called after the derived store's subscriptions are called. If the derived store has its set and/or update methods called again in the process of calling its subscriptions, reflect will be called only once, with the most-recently-set value.

arguments, default parameters, and rest parameters will not receive old unless the function's length is at least 2.

Named export: propertyStore()

Parameters: origin (store), propName (string, number, symbol, or array of strings/numbers/symbols)
Returns a store with writable methods

A utility wrapper for writableDerived. Given a store containing an object, this function returns a store containing the value of the object's property propName. If propName is an array, it's used as a path to navigate nested objects.

Regarding Subscription-less svelte-writable-derived Stores

One of the ways this package 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, there's an exception: Calling the set and update methods when the derived store has no subscriptions will subscribe to & then unsubscribe from all its origins.

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),
    (object) => JSON.stringify(object)
);
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 { propertyStore } from "svelte-writable-derived";

var objectStore = writable({"a horse": "a horse", "of course": "of course"});
var valueStore = propertyStore(objectStore, "a horse");
console.log( get(valueStore) ); // "a horse"
valueStore.set("*whinny*");
console.log( get(objectStore) ); // {"a horse": "*whinny*", "of course": "of course"}

// propertyStore is just a wrapper. You could also use writableDerived directly:

import writableDerived from "svelte-writable-derived";

var valueStore = writableDerived(
    objectStore,
    (object) => object["a horse"],
    (reflecting, object) => {
        object["a horse"] = reflecting;
        return object; // needed to call objectStore.set with the proper value
    }
);

... when the object is an array

// An array is an object with numerically-named properties.
// Access them using a number for the propName parameter.

import { writable, get } from "svelte/store";
import { propertyStore } from "svelte-writable-derived";

var treasureCoordinates = writable([7, -2, 31]);
var treasureElevation = propertyStore(treasureCoordinates, 1);
console.log( get(treasureElevation) ); // -2
treasureElevation.set(1); // dig up the treasure
console.log( get(treasureCoordinates) ); // [7, 1, 31]

... when the value is deeply nested in the object

import { writable, get } from "svelte/store";
import { propertyStore } from "svelte-writable-derived";

var objectStore = writable({ deeply: { buried: { item: "trash" } } });
var valueStore = propertyStore(objectStore, ["deeply", "buried", "item"]);
console.log( get(valueStore) ); // "trash"
valueStore.set("treasure");
console.log( get(objectStore) ); // { deeply: { buried: { item: "treasure" } } }

// Using writableDerived directly:

import writableDerived from "svelte-writable-derived";

var valueStore = writableDerived(
    objectStore,
    (object) => object.deeply.buried.item,
    (reflecting, object) => {
        object.deeply.buried.item = reflecting;
        return object; // needed to call objectStore.set with the proper value
    }
);

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} ),
    (object) => [ object["this is"], object["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, propertyStore } from "svelte-writable-derived";

var jsonStore = writable(`{"owner": "dragon", "possessions": ["crown", "gold"]}`);
var hoardStore = writableDerived(
    jsonStore,
    (json) => JSON.parse(json),
    (object) => JSON.stringify(object)
);

var hoarderStore = propertyStore(hoardStore, "owner");
var hoardContentsStore = propertyStore(hoardStore, "possessions");

var itemListStore = writableDerived(
    [hoarderStore, hoardContentsStore],
    ([hoarder, hoardContents]) => {
        return hoardContents.map( (item) => {
            return {item, owner: hoarder};
        });
    },
    (itemList) => {
        // This is only for demonstration purposes, so we won't handle differing owners
        var hoarder = itemList[0].owner;
        var hoardContents = itemList.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"};
    } );
} );
/*
    Upon the update, the console logs:
    [{item: "crown", owner: "protagonist"}, {item: "gold", owner: "protagonist"}]
    "protagonist"
    {owner: "protagonist", possessions: ["crown", "gold"]}
    "{\"owner\": \"protagonist\", \"possessions\": [\"crown\", \"gold\"]}"
*/

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

At my Ko-Fi page, you can make a one-time or monthly donation, or commission work on an issue.

You can also support all your dependencies at once using StackAid!

💌 ... 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

Want me to sling some JavaScript for you? Look over my other work and contact me!

changelog

3.1.1 (April 3, 2024)

  • Fixed incorrect TypeScript definition for async derived (#25)
  • Now supports Svelte 5 (-next.94 and later)

In Svelte 5, this package does not use runes and continues to use the original writable store interface, which is not deprecated. I may release a separate package that uses runes.

3.1.0 (June🏳️‍🌈 14, 2023)

With this release, I am committing to supporting Svelte v4 through its prereleases and beyond! If this is helpful for you, tip the maintainer!

3.0.1 (published as @next December 24🎄, 2022; @latest January 11, 2023)

  • Changed: reflect now has only one form with signature (reflecting, old). It can no longer set origins asynchronously. (#23)
  • Typescript improvements:
    • When origins is an array, values returned by reflect must be compatible with its stores, just as when origins is a single store (#19)
    • writableDerived is reduced from 8 to 2 signatures, making TypeScript error messages more useful

(Version 3.0.0 introduced the above changes but lacked some updates to readme example code and the JSDocs.)

2.1.6 (December 24🎄, 2022)

No behavior changes.

  • Announce v3 and deprecate asynchronous reflect

2.1.5 (July 3, 2022)

No behavior changes.

  • Specify package.json's default conditional export last, resolving errors with tools like Webpack that strictly require it (#21)

2.1.4 (June🏳️‍🌈 22, 2022)

  • Fixed derive not getting called when an origin was set by its own subscription in response to reflect (#18)
  • TypeScript changes (#19):
    • Using a single origin store now requires it implement the set method from Svelte's Writable type
    • Improved definitions of reflect to help TypeScript choose the correct writableDerived overload
    • When origins is an array, the type reflect can set with has been loosened to any[]. This is a temporary measure to reduce undecipherable TypeScript errors.
    • Stores passed to propertyStore are no longer required to have an update method
  • Fixed links meant to point to specific sections of the Svelte documentation in the readme and JSDocs

2.1.3 (March 20, 2022)

No behavioral changes.

  • TypeScript fully supported (thanks, qu1ncyk!)

2.1.2 (November 14, 2021)

  • Fixed reflect not getting the latest value after a subscription updates its own store in Svelte >=3.6.9 (#13)
  • Documented in the readme & JSDocs that propertyStore accepts numerical propNames (though this is newly-documented, it's in a SemVer-patch release to indicate it's worked since 2.1.0) (#12)
  • JSDocs now use Markdown [links] instead of JSDoc {@link}s, working around VSCode bugs with the latter

2.1.1 (November 22, 2020)

No behavioral changes.

  • Added repository, exports, & sideEffects fields to package.json

2.1.0 (July 24, 2020)

  • Added: New export propertyStore, a writableDerived wrapper for making a derived store from a stored object's property with less boilerplate (#5)
  • Added: Named export writableDerived is an alias for the default export, allowing you to get both exports with names if you so choose
  • JSDocs added. TypeScript can get type info from JSDocs, so TypeScript is now partially supported!

2.0.1 (June 14, 2019)

  • Fixed: Setting/updating a store to a new primitive value, then setting/updating the store to the same value from its subscriptions would prevent reflect from being called
  • Fixed: Setting/updating a subscriptionless store to the primitive value it already had could cause a spurious reflect call

2.0.0 (June 13, 2019)

  • Changed: reflect is now either a function with signature (reflecting, set) or an object with interface { withOld(reflecting, old, set) }. Async-ness is determined by whether the function accepts a set parameter, just as for the derive callback.
  • Changed: The reflect callback now runs after subscriptions. Additional set/update calls while running subscriptions result in reflect being called only once with the most recent value.
  • Changed: Changes to which operations cause a subscription-less writableDerived to subscribe-then-unsubscribe to all origins:
    • set method calls now do so
    • Getting old origin values in reflect no longer does so
  • Optimization: Origin values are no longer kept if reflect will not need them
  • Optimization: Participates in Svelte v3.5.0's diamond dependencies solution (downstream only; there is no diamond dependency handling for reflecting values back to their sources)
  • Optimization: No more internal writable store; the derived is the only internal store remaining

If this update was useful for you, show your appreciation with money, kind words, or a job!

1.0.1 (June 7, 2019)

  • Fixed: First subscription was getting its first call before derive ran
  • Optimization: Internal derived store is no longer set; all sets go to the internal writable store (replaced with a different optimization in 2.0.0)

1.0.0 (June 2, 2019)

  • Initial release