Skip to main content

@milajs/mobx-helpers

@milajs/mobx-helpers is a companion package that provides lightweight utilities for observing MobX observable property changes and composing disposer functions.

Installation​

npm i @milajs/mobx-helpers mobx

onChange​

Reacts to changes on one or more observable properties. Returns a disposer function.

import { makeAutoObservable } from 'mobx';
import { onChange } from '@milajs/mobx-helpers';

class UserStore {
name = '';
age = 0;
constructor() { makeAutoObservable(this); }
}

const store = new UserStore();

// Track a single property
const off = onChange(store, 'name', ({ name }) => {
console.log('Name changed to', name);
});

// Track multiple properties
const off2 = onChange(store, ['name', 'age'], ({ name, age }) => {
console.log('Updated:', name, age);
});

// Dispose when no longer needed
off();
off2();

trackChanges​

Like onChange, but fires the callback immediately with the current values of the tracked properties before listening for subsequent changes:

import { trackChanges } from '@milajs/mobx-helpers';

const off = trackChanges(store, ['name', 'age'], (values) => {
// Both first call and subsequent calls: values === { name, age }
console.log(values);
});

This is useful when you need to run initialization logic with the current state and then continue reacting to future changes.

combineFns​

Combines multiple disposer functions into one. This is the key utility for cleanly managing multiple MobX reactions inside a React useEffect.

import { combineFns } from '@milajs/mobx-helpers';

const off = combineFns(disposer1, disposer2, disposer3);

// Calling off() disposes all three
off();

Using with React useEffect​

The most common and powerful pattern: set up several onChange reactions inside a useEffect and compose all their disposers with combineFns so a single cleanup function tears everything down.

import { useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { onChange, combineFns } from '@milajs/mobx-helpers';

const Dashboard = observer(({ authStore, projectStore, notifStore }) => {
useEffect(() => {
const off = combineFns(
onChange(authStore, 'user', ({ user }) => {
analytics.identify(user);
}),
onChange(projectStore, ['selectedId', 'filter'], ({ selectedId, filter }) => {
analytics.track('project_view', { selectedId, filter });
}),
onChange(notifStore, 'unreadCount', ({ unreadCount }) => {
document.title = unreadCount > 0
? `(${unreadCount}) Dashboard`
: 'Dashboard';
}),
);

// Single disposer cleans up all three reactions on unmount
return off;
}, []);

return <div>{/* ... */}</div>;
});

:::tip Why combineFns? Without combineFns, you'd need to track each disposer individually and call them all in your cleanup function. combineFns reduces boilerplate and ensures nothing is accidentally left undisposed. :::