feat: pass update function to store setup callbacks (#6750)

Fixes #4880.
Fixes #6737.

This will be a breaking change for anyone who uses the StartStopNotifier
type in their / implements custom stores.

---------

Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/8566/head
Robin Munn 2 years ago committed by GitHub
parent ea73930132
commit 19e163f59d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,7 @@
* **breaking** Overhaul and drastically improve creating custom elements with Svelte (see PR for list of changes and migration instructions) ([#8457](https://github.com/sveltejs/svelte/pull/8457))
* **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512))
* **breaking** Error on falsy values instead of stores passed to `derived` ([#7947](https://github.com/sveltejs/svelte/pull/7947))
* **breaking** Custom store implementers now need to pass an `update` function additionally to the `set` function ([#6750](https://github.com/sveltejs/svelte/pull/6750))
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))

@ -289,7 +289,7 @@ This makes it possible to wrap almost any other reactive state handling library
store = writable(value?: any)
```
```js
store = writable(value?: any, start?: (set: (value: any) => void) => () => void)
store = writable(value?: any, start?: (set: (value: any) => void, update: (fn: any => any) => void) => () => void)
```
---
@ -316,7 +316,7 @@ count.update(n => n + 1); // logs '2'
---
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a `set` function which changes the value of the store. It must return a `stop` function that is called when the subscriber count goes from one to zero.
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a `set` function which changes the value of the store, and an `update` function which works like the `update` method on the store, taking a callback to calculate the store's new value from its old value. It must return a `stop` function that is called when the subscriber count goes from one to zero.
```js
import { writable } from 'svelte/store';
@ -340,7 +340,7 @@ Note that the value of a `writable` is lost when it is destroyed, for example wh
#### `readable`
```js
store = readable(value?: any, start?: (set: (value: any) => void) => () => void)
store = readable(value?: any, start?: (set: (value: any) => void, update: (fn: any => any) => void) => () => void)
```
---
@ -359,6 +359,16 @@ const time = readable(null, set => {
return () => clearInterval(interval);
});
const ticktock = readable(null, (set, update) => {
set('tick');
const interval = setInterval(() => {
update(sound => sound === 'tick' ? 'tock' : 'tick');
}, 1000);
return () => clearInterval(interval);
});
```
#### `derived`
@ -367,13 +377,13 @@ const time = readable(null, set => {
store = derived(a, callback: (a: any) => any)
```
```js
store = derived(a, callback: (a: any, set: (value: any) => void) => void | () => void, initial_value: any)
store = derived(a, callback: (a: any, set: (value: any) => void, update: (fn: any => any) => void) => void | () => void, initial_value: any)
```
```js
store = derived([a, ...b], callback: ([a: any, ...b: any[]]) => any)
```
```js
store = derived([a, ...b], callback: ([a: any, ...b: any[]], set: (value: any) => void) => void | () => void, initial_value: any)
store = derived([a, ...b], callback: ([a: any, ...b: any[]], set: (value: any) => void, update: (fn: any => any) => void) => void | () => void, initial_value: any)
```
---
@ -390,9 +400,9 @@ const doubled = derived(a, $a => $a * 2);
---
The callback can set a value asynchronously by accepting a second argument, `set`, and calling it when appropriate.
The callback can set a value asynchronously by accepting a second argument, `set`, and an optional third argument, `update`, calling either or both of them when appropriate.
In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` is first called.
In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` or `update` is first called. If no initial value is specified, the store's initial value will be `undefined`.
```js
import { derived } from 'svelte/store';
@ -400,6 +410,13 @@ import { derived } from 'svelte/store';
const delayed = derived(a, ($a, set) => {
setTimeout(() => set($a), 1000);
}, 'one moment...');
const delayedIncrement = derived(a, ($a, set, update) => {
set($a);
setTimeout(() => update(x => x + 1), 1000);
// every time $a produces a value, this produces two
// values, $a immediately and then $a + 1 a second later
});
```
---

@ -17,10 +17,11 @@ type Invalidator<T> = (value?: T) => void;
* This function is called when the first subscriber subscribes.
*
* @param {(value: T) => void} set Function that sets the value of the store.
* @param {(value: Updater<T>) => void} set Function that sets the value of the store after passing the current value to the update function.
* @returns {void | (() => void)} Optionally, a cleanup function that is called when the last remaining
* subscriber unsubscribes.
*/
export type StartStopNotifier<T> = (set: (value: T) => void) => void | (() => void);
export type StartStopNotifier<T> = (set: (value: T) => void, update: (fn: Updater<T>) => void) => void | (() => void);
/** Readable interface for subscribing. */
export interface Readable<T> {
@ -99,7 +100,7 @@ export function writable<T>(value?: T, start: StartStopNotifier<T> = noop): Writ
const subscriber: SubscribeInvalidateTuple<T> = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set) || noop;
stop = start(set, update) || noop;
}
run(value);
@ -130,9 +131,9 @@ type StoresValues<T> = T extends Readable<infer U> ? U :
* @param fn - function callback that aggregates the values
* @param initial_value - when used asynchronously
*/
export function derived<S extends Stores, T>(
export function derived<S extends Stores, T>(
stores: S,
fn: (values: StoresValues<S>, set: (value: T) => void) => Unsubscriber | void,
fn: (values: StoresValues<S>, set: Subscriber<T>, update: (fn: Updater<T>) => void) => Unsubscriber | void,
initial_value?: T
): Readable<T>;
@ -171,7 +172,7 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
const auto = fn.length < 2;
return readable(initial_value, (set) => {
return readable(initial_value, (set, update) => {
let started = false;
const values = [];
@ -183,7 +184,7 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
return;
}
cleanup();
const result = fn(single ? values[0] : values, set);
const result = fn(single ? values[0] : values, set, update);
if (auto) {
set(result as T);
} else {

@ -137,6 +137,50 @@ describe('store', () => {
assert.deepEqual(values, [0, 1, 2]);
});
it('passes an optional update function', () => {
let running;
let tick;
let add;
const store = readable(undefined, (set, update) => {
tick = set;
running = true;
add = n => update(value => value + n);
set(0);
return () => {
tick = () => { };
add = _ => { };
running = false;
};
});
assert.ok(!running);
const values = [];
const unsubscribe = store.subscribe(value => {
values.push(value);
});
assert.ok(running);
tick(1);
tick(2);
add(3);
add(4);
tick(5);
add(6);
unsubscribe();
assert.ok(!running);
tick(7);
add(8);
assert.deepEqual(values, [0, 1, 2, 5, 9, 5, 11]);
});
it('creates an undefined readable store', () => {
const store = readable();
const values = [];
@ -241,6 +285,39 @@ describe('store', () => {
assert.deepEqual(values, [0, 2, 4]);
});
it('passes optional set and update functions', () => {
const number = writable(1);
const evensAndSquaresOf4 = derived(number, (n, set, update) => {
if (n % 2 === 0) set(n);
if (n % 4 === 0) update(n => n * n);
}, 0);
const values = [];
const unsubscribe = evensAndSquaresOf4.subscribe(value => {
values.push(value);
});
number.set(2);
number.set(3);
number.set(4);
number.set(5);
number.set(6);
assert.deepEqual(values, [0, 2, 4, 16, 6]);
number.set(7);
number.set(8);
number.set(9);
number.set(10);
assert.deepEqual(values, [0, 2, 4, 16, 6, 8, 64, 10]);
unsubscribe();
number.set(11);
number.set(12);
assert.deepEqual(values, [0, 2, 4, 16, 6, 8, 64, 10]);
});
it('prevents glitches', () => {
const lastname = writable('Jekyll');
const firstname = derived(lastname, n => n === 'Jekyll' ? 'Henry' : 'Edward');

Loading…
Cancel
Save