- Notifications
You must be signed in to change notification settings - Fork9
API Reference
Theo function wraps an object with an observable proxy (sorry, no IE11 support). These proxies are transparent, which means they look and act just like the object they wrap. The only difference is that now they're observable!
Even custom class instances can be wrapped with an observable proxy!
Passing the same object intoo twice always returns the same proxy.
By wrapping a function witho, you get an observable getter, which memoizes its result until one of its observed values is changed. Calling an observable getter triggers an observation! To prevent leaks, call thedispose method before releasing an observable getter.
Passing an observable getter intoo is a no-op.
Passing a primitive value intoo is a no-op.
Note: Nested objects arenot made observable. You'll need to wrap them witho calls too.
import{o}from'wana'conststate:any[]=o([])conststate=o({a:1})conststate=o(newSet())conststate=o(newMap())
Theauto function runs the given effect immediately and tracks which observables are used. Upon changes to any of those observables,auto repeats the same process.
It does its best to react only to changes that affect its outcome.
import{o,auto}from'wana'conststate=o({count:0})constobserver=auto(()=>{console.log(state.count%2 ?'odd' :'even')})// logs "even"state.count++// logs "odd"state.count++// logs "even"// Remember to call "dispose" to stop observing.observer.dispose()
Theauto function accepts a config object:
sync?: booleanonError?: (this: Auto, error: Error) => void
By default, reactions are batched using themicrotask queue. Whensync is true, batching is skipped entirely.
By default,auto errors are rethrown. To customize error handling, provide anonError callback.
auto(effect,{onError(error){// The `run` method lets you replace the effect.this.run(newEffect)}})
TheAuto object returned byauto is kept alive by the values being observed. If all values being observed are garbage collected, theAuto object is too, unless you keep it alive elsewhere (eg: by assigning it to some long-lived object).
Thewhen function creates a promise that resolves when the given condition returns true.
Any observable access within the condition is tracked. The condition is rerun whenever a change is detected.
import{o,when}from'wana'constobj=o({count:0})constpromise=when(()=>obj.count>1)obj.count++// "promise" stays pendingobj.count++// "promise" is resolved
The promise is rejected when the condition throws an error.
Theno function takes any observable object and returns the underlying object that isn't observable.
import{o,auto,no}from'wana'constobj=o({a:1,b:2})auto(()=>{// This will only be logged once.console.log(no(obj).a+no(obj).b)})// This change will not be observed.obj.a=2
Pass a function to wrap it with a new function that disables implicit observation for each call.If the wrapped function is used as a method, it has itsthis value preserved.
conststate=o({count:1,})constincrement=no((n:number)=>{state.count=state.count+n})auto(()=>{increment(1)// Nothing will be observed in here.})assert(state.count==2)
Pass anything else and you get the same value back.
Thenoto function (pronounced "not oh") is theexact opposite of theauto function.The function you pass tonoto is called immediately (with implicit observation disabled)and whatever you return is passed through. Your function is never called again after that.
import{o,auto,noto}from'noto'conststate=o({count:0})// Create an auto observer.auto(()=>{// Do something you want observed.noto(()=>{// Do something you don't want observed.})})
It's also useful inside methods of an observable object:
conststate=o({count:0,// Calling "increment" in an observable scope does// *not* result in "count" being observed.increment(){noto(()=>this.count++)}})
Thewatch function lets you listen for deep changes within an observable object.
import{o,watch}from'wana'constobj=o({arr:o([])})constobserver=watch(obj,change=>console.log('changed:',change))// Every observable object in `obj` is watched.obj.x=trueobj.arr.push(1)// You can even add new observables!constfoo=o({})obj.arr.push(foo)foo.x=true// Call "dispose" to stop observing.observer.dispose()
Note: When an object is made observableafter being added to a watched object, it won't be watched. Be sure you pass objects too() before adding them to a watched object!
Just likewatch, but only the given object is observed.
import{shallowChanges}from'wana'constobserver=shallowChanges(obj,change=>{console.log('changed:',change)})
ThewithAuto function wraps a React component, giving it the ability to track which observables are used during render. Upon changes to any of those observables,withAuto re-renders the component.
For convenience, you can add aref argument to your component, andwithAuto will wrap it withReact.forwardRef for you. ✨
Note: Class components arenot supported.
import{o,withAuto}from'wana'constMyView=withAuto(props=>(<div>{props.user.name}</div>))constuser=o({name:'Alec'})constview=<MyViewuser={user}/>// renders "Alec"user.name='Alice'// renders "Alice"
TheuseAuto hook callsauto within auseEffect callback, allowing you to run an effect in response to observable changes.
import{useAuto}from'wana'constMyView=props=>{useAuto(()=>{console.log(props.user.name)})returnnull}constuser=o({name:'John Lennon'})constview=<MyViewuser={user}/>// logs "John Lennon"user.name='Yoko Ono'// logs "Yoko Ono"
By passing two functions, the 1st function will be observed and the 2nd function will run whenever the 1st function's result is a new value. The 2nd function isnever observed.
// Usually, you will ignore the returned `Auto` instance, but it can be useful in some cases.constauto=useAuto(// Derive a value from 1+ observables.()=>props.user.name,// Run an effect when the derived value is changed.name=>{console.log(name)},// Pass dependencies to avoid running on rerender.// The memoized value is reset when a dependency changes.[props.user])
TheuseO hook memoizes an observable object. When a non-observable object is passed, it gets wrapped with theo function.
import{useO}from'wana'conststate=useO({a:1})assert(state.a==1)
When adeps array is passed, its values are compared with the previous render. If any values have changed, the memoized state is replaced with whatever object you passed in. When nodeps array is passed, the state is never replaced.
conststate=useO(newSet(),deps)
When a function is passed, the function is called as if it were passed toReact.useMemo, which means it will only be called again if any values in thedeps array are changed.
conststate=useO(()=>[1,2,3],deps)assert(state[0]==1)
When a plain object is passed, its properties are scanned for "observable getters" (created withuseDerived oro). In this case, these functions are converted into actual getters.
conststate=useO({a:1,b:useDerived(()=>state.a+1)})assert(state.b==2)
TheuseDerived hook creates an observable getter. You can pass a deps array as the last argument if you want to mix non-observable props into the memoized value.
import{o,useDerived,useAuto}from'wana'conststate=o({count:0})constMyView=props=>{constfoo=useDerived(()=>state.count+props.foo,[props.foo])useAuto(()=>{console.log('foo:',foo())})return<div/>}
TheuseChanges hook lets you listen forChange events on an observable object. Only shallow changes are reported.
import{o,useChanges}from'wana'conststate=o({count:0})constMyView=()=>{useChanges(state,console.log)returnnull}
TheuseEffects hook works differently depending on the type of object you pass it.
For arrays and sets, an effect is mounted for eachunique value. When a uniquevalue is added, an effect is mounted. When removed, its effect is unmounted, usingthe cleanup function returned by its effect.
import{o,useEffects}from'wana'constarr=o([1,2])constMyView=()=>{useEffects(arr,value=>{console.log('Added value:',value)return()=>{console.log('Removed value:',value)}})returnnull}
For maps and plain objects, an effect is mounted for eachunique key. When aunique key is added, an effect is mounted. When removed, its effect is unmounted,using the cleanup function returned by its effect. Whenever a key's value isreplaced, the effect is remounted.
import{o,useEffects}from'wana'constobj=o({a:1,b:2})constMyView=()=>{useEffects(obj,(value,key)=>{console.log('Added key/value:',key,'=>',value)return()=>{console.log('Removed key/value:',key,'=>',value)}})returnnull}
TheuseBinding hook re-renders your component whenever the given object/property is changed. Any observable object can be passed, including observable getters. You can also bind to a specific property (includinglength of an array).
import{o,useBinding}from'wana'consttodo=o({text:''})constTodoView=()=>{consttext=useBinding(todo,'text')return<span>{text}</span>}
Why not usewithAuto? Youshould usewithAuto whenever possible. This hook has a few unavoidable performance pitfalls (seehere). Nonetheless, this hook is useful when you're not the end user, so you can't guarantee anything. See the example below.
constpages=o({foo:{...}// Let's say the page data is static, but pages can be replaced.})// We're not the end user, so we need useBinding to ensure// the component observes the required page data.exportconstusePage=(name:string)=>useBinding(pages,name)