TL;DR
Svelte store does not notify subscribers on set/update events if the value of the primitive type is the same.
The story
Once upon a time, the young raccoon was trying to build a "running line" or an element on the page that would print out messages to a user based on some user interactions. Therefore, the application was heavily dependent on the store's event firing system.
And as any young raccoon, our raccoon assumed that if the value is set to a store, the store will notify all subscribers even if the value is the same, right? As it occurred, the raccoon was wrong.
Let's try if different types will behave differently:
Here is a testNotifyType
function to:
- set up a store
- add subscriber to log updates
- set the value to the store
- update the value
- put object for logs
Demo REPL
const testNotifyType = (A, B, comment) => {
const store = writable();
let out = {
value: A,
type: typeof A,
logs: [],
notifiedCounter: 0,
comment: comment
};
store.subscribe((data) => {
out.notifiedCounter += 1;
out.logs.push(`notified with: ${String(data)}`);
})
out.logs.push(`Trying to set: ${String(A)}`);
store.set(A);
store.set(A);
out.logs.push(`Let's try with update with: ${String(B)}`);
store.update(() => B);
store.update(() => B);
output.push(out);
}
Feel free to skip the code
Undefined:
testNotifyType(undefined, undefined);
Output:
Hello Store!
Can I please have a notifications for every set of "undefined" with type of undefined?
notified with: undefined
Trying to set: undefined
Let's try with update with: undefined
Notified: 1 time, so: No, sorry bro
Boolean
testNotifyType(true, false);
Can I please have a notifications for every set of "true" with type of boolean?
notified with: undefined
Trying to set: true
notified with: true
Let's try with update with: false
notified with: false
Notified: 3 times, so: No, sorry bro
Number
testNotifyType(0, 1);
Can I please have a notifications for every set of "0" with type of number?
notified with: undefined
Trying to set: 0
notified with: 0
Let's try with update with: 1
notified with: 1
Notified: 3 times, so: No, sorry bro
BigInt
testNotifyType(BigInt(2), BigInt(3));
Can I please have a notifications for every set of "2" with type of bigint?
notified with: undefined
Trying to set: 2
notified with: 2
Let's try with update with: 3
notified with: 3
Notified: 3 times, so: No, sorry bro
String
testNotifyType("a", "b");
Can I please have a notifications for every set of "a" with type of string?
notified with: undefined
Trying to set: a
notified with: a
Let's try with update with: b
notified with: b
Notified: 3 times, so: No, sorry bro
Symbol
testNotifyType(Symbol("a"), Symbol("b"));
Can I please have a notifications for every set of "Symbol(c)" with type of symbol?
notified with: undefined
Trying to set: Symbol(c)
notified with: Symbol(c)
Let's try with update with: Symbol(d)
notified with: Symbol(d)
Notified: 3 times, so: No, sorry bro
As you can see undefined, boolean, number, bigint, string, symbol type didn't notify subscribers.
Should we try an object?
Object
testNotifyType({}, {}, "What about object?");
What about object?
Can I please have a notifications for every set of "[object Object]" with type of object?
notified with: undefined
Trying to set: [object Object]
notified with: [object Object]
notified with: [object Object]
Let's try with update with: [object Object]
notified with: [object Object]
notified with: [object Object]
Notified: 5 times, so: Yes!
Array?
testNotifyType([4, 3], [1, 2], "What about array?");
What about array?
Can I please have a notifications for every set of "4,3" with type of object?
notified with: undefined
Trying to set: 4,3
notified with: 4,3
notified with: 4,3
Let's try with update with: 1,2
notified with: 1,2
notified with: 1,2
Notified: 5 times, so: Yes!
And it worked!
As we can see above the store will notify subscribers for the same value only in case of the object
type, but not primitive types.
Why?
In case you are as curious as a young raccoon to dig deeper into the sveltejs/svelte repo, then:
We can see writable
store does safe_not_equal check before any actions.
export function writable(value, start = noop) {
/** @type {import('./public.js').Unsubscriber} */
let stop;
/** @type {Set<import('./private.js').SubscribeInvalidateTuple<T>>} */
const subscribers = new Set();
/** @param {T} new_value
* @returns {void}
*/
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
And the code for the safe_not_equal
function:
/** @returns {boolean} */
export function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function';
}
Function will return true for the same values only if an object
or function
were passed.
I think it does make sense now.
Workaround
For my "running line" I've wrapped the value of the stores into the object:
queueWritable: Writable<string[]>;
mutexWritable: Writable<{ mutex: boolean }>;
waitTimeWritable: Writable<{ time: number }>;
message: Writable<{ message: string }>;
And setting the value by:
this.mutexWritable.set({ mutex: true });
I hope you've learned something from this "article", but if you didn't, have a good day)
Of course, a mature raccoon already knew this feature of the svelte store because raccoon reads the source code instead of documentation, but I felt like it was worth sharing, even if a single young raccoon will find it helpful.
So, be careful and be smarter than our young raccoon🦝.
Be safe.
Try the demo REPL
Top comments (0)