-
Hi! I'm getting up to speed on how valtio works (coming from mobx), and was surprised that it("valtio watch is selective", () => {
const a1 = proxy({ first: "a", last: "b" });
let count = 0;
watch(
(get) => {
const snap = get(a1);
console.log(`first is ${snap.first}`);
count++;
},
{ sync: true },
);
a1.last = "c";
a1.last = "d";
expect(count).toBe(1);
}); I expected my callback to only be ran once (on initial subscription), and then not again b/c it's list of affected keys is only This may not matter if I end up using Could watch be selective? Is there a reason it's not? Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments
-
I call what you call "selective", "usage tracking". btw,
The mental model is, all vanilla functions are simple ones without snapshot nor usage tracking. Usage tracking is only implemented in Now, implementing usage tracking in Overall, while the question/suggestion is reasonable, it's by design, and I'm not confident if we should add a new util. |
Beta Was this translation helpful? Give feedback.
-
Gotcha! I like that term.
Cool, that makes sense. Fwiw I was playing with From what I've seen so far, I'm tempted to assert What do you think of these doc changes to clarify the non-usage-tracking aspect of https://github.com/pmndrs/valtio/pull/645/files I admittedly was a little presumptive in renaming Happy to put Thanks! |
Beta Was this translation helpful? Give feedback.
-
just for anyone interested. Made my own version of watch-like util with "usage tracking".
import { Snapshot, snapshot } from 'valtio';
import { subscribeKey } from 'valtio/utils';
export type WatcherSnapshot = <State extends object>(state: State) => Snapshot<State>;
export const createWatcher = (cb: (snapshot: WatcherSnapshot) => void) => {
const subs = new Map<object, Map<PropertyKey, { (): void }>>();
const subscribe = <State extends object>(state: State, field: keyof State) => {
let fieldSubs = subs.get(state);
if (!fieldSubs) {
fieldSubs = new Map();
subs.set(state, fieldSubs);
}
if (fieldSubs.has(field)) return;
fieldSubs.set(field, subscribeKey(state, field, handleUpdate));
};
const snapshotsCache = new WeakMap<object, object>();
const handleUpdate = () => {
cb(state => {
const snap = snapshot(state);
let proxy = snapshotsCache.get(snap as object);
if (proxy !== undefined) return proxy as never;
proxy = new Proxy(snap as object, {
get(_, field) {
if (typeof field === 'string') subscribe(state, field as never);
return snap[field as never];
},
});
return proxy as never;
});
};
handleUpdate();
return Object.assign(
() => {
for (const sub of subs.values().flatMap(subs => subs.values())) {
sub();
}
},
{ subscribe },
);
};
// with react hook
import { DependencyList, useEffect } from 'react';
// I have added useWatcher to `additionalHooks` in `react-hooks/exhaustive-deps`, that's my personal preference
export const useWatcher = (cb: (get: WatcherSnapshot) => void, deps?: DependencyList) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => createWatcher(cb), deps);
}; |
Beta Was this translation helpful? Give feedback.
-
You can use my valtio-observe 😄 it("but valtio-observe is selective :)", () => {
const a1 = proxy({ first: "a", last: "b" });
let count = 0;
observe(
() => {
console.log(`first is ${a1.first}`); // note: no get(...), just use the proxy directly
return a1.first; // produce result
},
(_newValueOfFirst) => count++, // consume the produced result
true // sync
);
expect(count).toBe(1);
a1.first = "aa";
expect(count).toBe(2);
a1.last = "c";
a1.last = "d";
expect(count).toBe(2);
}); Output:
So the 'producer' callback isn't even triggered on changes of the |
Beta Was this translation helpful? Give feedback.
I call what you call "selective", "usage tracking".
btw,
get()
returns a proxy object, not a snapshot object.watch
is simply wrapped aroundsubscribe
. So, it's by design.The mental model is, all vanilla functions are simple ones without snapshot nor usage tracking. Usage tracking is only implemented in
useSnapshot
.Now, implementing usage tracking in
watch
is a little complex, and usage comes with gotchas, andwatch
's implementation is actually already fairly complex.Creating a new util extending
subscribe
with usage tracking is rather reasonable, but it might not be that intuitive, in terms of API surface.Overall, while the question/suggestion is reasonable, it's by design, and I'm n…