Store Observer & Reactions (quick reference)
A 5-minute cheat sheet: two small templates that let you make your own components auto-respond to store changes, or fire side effects (auto-save, analytics, logging) whenever data changes.
๐ For a deep dive into MobX tracking rules, all built-in events, and performance tips, read Reactivity & Events. This page is the short, copy-paste version.
Wrap your component in observer โ auto-respond to store changesโ
store is a reactive object. To make your React component re-render automatically when store changes, wrap it with observer from mobx-react-lite:
import { observer } from 'mobx-react-lite';
import { createStore } from '@ydesign/react-editor';
const store = createStore({ key: 'YOUR_API_KEY' });
// Re-renders whenever store.objects changes (add/remove element)
const ElementsCount = observer(({ store }) => (
<div>{store.objects.length} elements on the canvas</div>
));
The rules that matter:
- โ MobX tracks by field โ whatever you read, you subscribe to
- โ Only re-renders when the fields you read actually change โ free performance
- โ Don't forget
observer, or the component never updates
Watching the current selection? Just read store.selectedElements:
const Hint = observer(({ store }) => {
const el = store.selectedElements[0];
if (!el) return <div>Select an element first</div>;
return (
<div>
Selected {el.type} ยท at ({Math.round(el.left)}, {Math.round(el.top)})
</div>
);
});
All built-in Ydesign panels and toolbars already use observer internally โ you only need to add it to your own components.
Listen for changes: store.on('change') and reactionโ
Don't want to render UI โ just run a side effect when data changes (auto-save, analytics, sync to a backend)? Two approaches, pick by scenario.
Option A: the easy one โ store.on('change')โ
A high-level subscription to any canvas-object change (add / remove / modify). Under the hood it deep-compares snapshots, so only real changes fire:
const dispose = store.on('change', (objects) => {
console.log('Canvas changed:', objects.length, 'elements');
// Typical use: auto-save
fetch('/api/designs/current', {
method: 'PUT',
body: JSON.stringify(store.toJSON()),
});
});
// Stop subscribing when you're done
dispose();
Good for:
- Auto-save
- Dirty tracking / "unsaved changes" hints
- Simple "something changed, do X"
Option B: any field โ mobx.reactionโ
Want to track one specific field instead of the whole canvas? Use MobX's reaction:
import { reaction } from 'mobx';
// Log canvas size whenever it changes
const dispose = reaction(
() => [store.width, store.height], // 1) source
([width, height]) => { // 2) side effect
console.log('Canvas size โ', width, 'ร', height);
},
);
dispose(); // when done
reaction's first argument is a pure read function (selector), the second runs whenever its return value changes. More granular than store.on('change') โ unrelated field changes don't bother you.
๐ก Use both at once.
store.on('change')handles "something on canvas changed",reactionhandles "this specific field changed".
Managing subscriptions in Reactโ
Both options above return a dispose function. In React always put them in useEffect so they're cleaned up on unmount โ otherwise you'll leak memory and handlers:
import { useEffect } from 'react';
import { reaction } from 'mobx';
import { observer } from 'mobx-react-lite';
const AutoSave = observer(({ store }) => {
useEffect(() => {
// Auto-save with 1s throttle
const dispose = reaction(
() => store.toJSON(),
(json) => fetch('/api/save', { method: 'PUT', body: JSON.stringify(json) }),
{ delay: 1000 },
);
return dispose; // ๐ unsubscribe on unmount
}, [store]);
return null;
});
Same pattern for store.on('change', ...):
useEffect(() => {
const dispose = store.on('change', (objects) => {
setDirty(true);
});
return dispose;
}, [store]);
Ydesign pitfall checklistโ
| Symptom | Cause | Fix |
|---|---|---|
| Component doesn't update | Forgot observer | export default observer(MyComp) |
reaction fires dozens of times per second, overwhelms backend | store.objects changes rapidly during drag / typing | Add { delay: 1000 } to throttle |
| Still logging after unmount | dispose never called | Return it from useEffect |
| Changed a field but component doesn't react | Field read is stashed inside useMemo / useCallback | Move the read into the render body |
store.editor is sometimes null | Editor is created when <Workspace /> mounts | Use optional chaining: store.editor?.xxx |
Going deeperโ
- ๐ Reactivity & Events โ full event list,
autorun, MobX + events together, performance - ๐ Store overview โ all reactive fields
- ๐ MobX docs ยท mobx-react-lite