autorun ​
autorun
is one of the most powerful and extraordinary capabilities of the Observable API.
It registers a function (called an effect
) that runs immediately once, and then re-runs whenever any observable value read by the effect – changes.
Syntax ​
import { autorun } from 'kr-observable'
function effect() {};
autorun(effect);
Return value ​
A disposer function that, when invoked, stops tracking observables and unsubscribes from all dependencies, effectively cleaning up the effect.
import { autorun } from 'kr-observable';
const dispose = autorun(effect)
// later
dispose()
How it works ​
During the execution of effect
, autorun
will automatically subscribe to all observables and computed values that are directly or indirectly read
by effect. Once they change, the autorun
will trigger effect
again, repeating the entire process.
import { autorun, makeObservable } from 'kr-observable'
const state = makeObservable({
count: 0,
get text() {
return `Current count is ${this.count}`
}
})
autorun(() => {
console.log(state.text)
});
// will print to console the current count infinitely
setInterval(() => ++state.count, 1000)
Tips ​
TIP
You can safely mutate any state inside an effect
because tracking only reacts to what is read, not what is written.
That's ability allows to do incredible things!
import { Observable, autorun, observer } from 'kr-observable'
class State extends Observable {
loading = true;
postId = 1;
post = null;
async getPost() {
try {
this.loading = true;
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${this.postId}`
);
this.post = await response.json();
} catch (e) {
this.post = null;
console.error(e);
} finally {
this.loading = false;
}
}
}
const state = new State();
autorun(state.getPost); // magic is here
const Post = observer(function post() {
return (
<div>
<div>Loading: {String(state.loading)}</div>
<div>Autorun was invoked: {state.count} times</div>
{state.post ? (
<div>{state.post.title}</div>
) : (
<div>No post</div>
)}
<div>
<button onClick={() => state.postId -= 1}>Prev</button>
<button onClick={() => state.postId += 1}>Next</button>
</div>
</div>
);
});
In this case this.postId
is read from within getPost
, therefore, only changes to postId
will trigger a re-run of getPost
. All other mutations (this.loading = true
, this.count += 1
, etc.) do not affect tracking, because they are writes, not reads.
See this example on stackblitz.com
TIP
Another great feature of autorun
is that it only subscribes to the observables that are actually need — ignoring any unnecessary ones.
import { autorun, makeObservable } from 'kr-observable';
const state = makeObservable({ a: 'value', b: true });
autorun(() => {
console.log(state.b ? state.a : 'none');
});
// effect will re-run
state.b = false;
// effect won't re-run, because `b` is `false`,
// so the value of `a` will not be read
state.a = 'new value'
Restrictions ​
autorun
captures dependencies only during the synchronous phase of the effect
’s execution. Any reads that occur asynchronously — for example, inside setTimeout
or Promise.then
can't be tracked.