Skip to content

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 ​

ts
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.

ts
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.

ts
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!

tsx
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.

ts
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.

kroman@observable.ru