You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I used valtio with svelte. And while developing my app, i ran into a pattern where local state needs to sync data with global state. User can change local state freely, but when global state changes, local state will auto sync.
//-- in setting storeconstappSettings={selectedTheme: "Ayu",themeList: [...]
...
};//-- in Theme Editor// get latest theme object from themeList by selectedThemeconsttheme=sample(// proxyObject to subscribe and get data fromappSettings,// it = snapshot of appSettings, so we can't change it by mistakeit=>it.themeList.find(theme=>theme.name===it.selectedTheme))// now user can change the theme freely, if they cancel, the local state isn't commit to global// if they select new theme name, the theme object is updated// we dont need to select the theme name, then find the theme object, set it to local state// this way, we can write code in declarative way
So here is the implementation:
functionsample(proxyObject,computeFn){// compute initial value for the internal proxy from snapshot of proxy object(s)constsam=proxy({value: computeFn(getSnapshot(proxyObject)),});// we need to find *direct* parents of proxy object(s)// to prevent too many runs// for example: we have the flow: a <- b, b <- c// if d samples from b & c, then we only need to subscribe to c's changes// because b's changes will make c to change. instead of 2 runs, we only need 1 run.constparent=getParent(proxyObject);setupLinks(sam,parent);// subcribe to parents' changes, and collect unsubscribe to for unmount operationletunsub=[]parent.forEach((it)=>{constun=subscribe(it,()=>{sam.value=fn(getSnapshot(proxyObject));});unsub.push(un)});// i prefer getter function calling pattern than direct `value` access// i can guard the value with getSnapshot later// sample() is better then sample.value (also i don't like `ref.value` like vue3)constgetter=()=>sam.value;// expose raw so we can subscribe to changesObject.defineProperty(getter,"raw",{value: sam,});// stop the computation, usually on unMountObject.defineProperty(getter,"stop",{value: ()=>unsub.forEach(it=>it())})returngetter;}// check if a value is a sample for other sample related operationconstisSampled=(sus)=>typeofsus==="function"&&!!sus.raw;// helper to get the snapshot from an object, sample or get value from string/number,...functiongetSnapshot(proxyObject: any){if(!proxyObject)returnproxyObject;if(Array.isArray(proxyObject))returnproxyObject.map(getSnapshot);if(isSampled(proxyObject))returnsnapshot(proxyObject.raw).value;if(typeofproxyObject==="object")returnsnapshot(proxyObject);returnproxyObject;}// we save parent and children links here// to help finding direct parent and debuggingconst_sampleParentLinks=newWeakMap();const_sampleChildLinks=newWeakMap();functiongetParent(src){src=Array.isArray(src) ? src : [src];src=src.map((it)=>isSampled(it) ? it.raw : it);// so we just pass all the parent proxy objects at first// then we remove it laterconstparent=[...src];src.forEach((it)=>{if(_sampleParentLinks.has(it)){_sampleParentLinks.get(it).forEach((pa)=>{constidx=parent.indexOf(pa);if(idx<0)return;// because other `src` already depended on current parent // so we remove it from the parent listparent.splice(idx,1);});}});returnparent;}// we add parent to node, so we can pull it out and check if parent exist in the `parent` array abovefunctionsetupLinks(node,parentNodes){_sampleParentLinks.set(node,parentNodes);// setup the child links for debugging purpose only// when parent is `stop`, but children need updates, we can subscribe to grandparent parentNodes.forEach((it)=>{if(_sampleChildLinks.has(it)){_sampleChildLinks.get(it).push(node);}else{_sampleChildLinks.set(it,[node]);}});}// dev helperfunctiongetLinks(node){return{parent: _sampleParentLinks.get(node.raw??node),children: _sampleChildLinks.get(node.raw??node),};}exportconst_DEV={
getLinks,
_sampleChildLinks,
_sampleParentLinks,};
Actually i have set and subscribe for getter above, to use with svelte, but i remove to keep it as vanilla.
What could i improve? What alternative pattern do you use?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hey guys,
I used valtio with svelte. And while developing my app, i ran into a pattern where local state needs to sync data with global state. User can change local state freely, but when global state changes, local state will auto sync.
So here is the implementation:
Actually i have
set
andsubscribe
forgetter
above, to use with svelte, but i remove to keep it as vanilla.What could i improve? What alternative pattern do you use?
Thank you for taking time reading this 👍🤟
Beta Was this translation helpful? Give feedback.
All reactions