How Reactivity Works
Deep dive into Gluonic’s MobX + Proxy-based reactivity system.
The Magic Explained
When you write this:
const PostDetail = observer(({ postId }) => {
const post = useModel<Post>('post', postId)
return <h1>{post.title}</h1>
})It feels synchronous - no async/await, no loading states, no manual updates.
But under the hood, a sophisticated reactivity system makes it all work automatically.
Step-by-Step Breakdown
1. Component Renders
const post = useModel<Post>('post', '123')What happens:
- Calls
IdentityMap.getModel('post', '123') - Returns Proxy wrapping Post instance
- Instance is cached (identity mapping)
2. Field Access
return <h1>{post.title}</h1>What happens:
- Reads
post.title - JavaScript Proxy intercepts with GET trap
- GET trap executes:
get(target, prop) {
if (prop === 'title') {
// Read from pool
const row = store.pool.get('post', '123')
const value = row.p.title
// MobX tracks this access automatically!
// Records: "Current component depends on pool['post:123'].p.title"
return value
}
}3. MobX Tracking
// MobX has now recorded:
{
component: PostDetail,
dependencies: [
'pool.map[post:123].p.title'
]
}4. Data Changes
// Elsewhere in the app
await store.save('post', '123', { title: 'Updated Title' })What happens:
- Pool updates:
pool.upsert({ t: 'post', id: '123', p: { title: 'Updated Title' } }) - MobX detects: “pool[‘post:123’].p.title changed”
- MobX checks: “Which components depend on this?”
- MobX finds: “PostDetail depends on this”
5. Re-Render
// MobX schedules re-render
// React re-runs component
const post = useModel<Post>('post', '123')
// Returns same instance (identity mapping)
return <h1>{post.title}</h1>
// Proxy GET trap reads from pool again
// Returns "Updated Title"
// UI updates ✓Why This Design?
Single Source of Truth
// Pool is the ONLY place that stores current state
class ObjectPool {
map = observable.map() // MobX observable
}
// Models are VIEWS over pool data
// They don't store data themselves
// Proxy GET trap always reads from pool
// Result:
// - One copy of data (pool)
// - Multiple views (model instances)
// - All views stay in sync automatically ✓Automatic Dependency Tracking
// Component A
const A = observer(() => {
const post = useModel<Post>('post', '123')
return <h1>{post.title}</h1>
// Depends on: title
})
// Component B
const B = observer(() => {
const post = useModel<Post>('post', '123')
return <p>{post.content}</p>
// Depends on: content
})
// Update title
store.save('post', '123', { title: 'New' })
// → Component A re-renders ✓
// → Component B doesn't re-render ✓ (doesn't depend on title)
// Efficient! 🎉Transparent Writes
// You can write to model properties directly
post.title = 'New Title'
// Proxy SET trap intercepts:
set(target, prop, value) {
if (prop === 'title') {
// Automatically calls store.save()
store.save('post', '123', { title: value })
return true
}
}
// Same as:
await store.save('post', '123', { title: 'New Title' })Reactivity Chain
User types in input
↓
onChange → store.save('post', '123', { title: e.target.value })
↓
pool.upsert({ t: 'post', id: '123', p: { title: 'New' } })
↓
MobX detects observable change
↓
MobX finds dependent components
↓
MobX batches updates (microtask)
↓
React re-renders affected components
↓
Components read post.title (via Proxy)
↓
Proxy reads from pool (updated value)
↓
UI shows new value ✓Total time: < 1-2ms for full cycle!
Performance Optimizations
Batching
// Multiple updates in same tick
store.save('post', '123', { title: 'A' })
store.save('post', '123', { content: 'B' })
store.save('post', '123', { published: true })
// MobX batches into single re-render ✓Shallow Observation
Pool uses shallow observation:
pool.map = observable.map({}, { deep: false })
// Only tracks map itself, not nested objects
// More efficient for large payloadsPrecise Dependencies
// Component only re-renders if accessed fields change
const Component = observer(() => {
const post = useModel<Post>('post', '123')
return <h1>{post.title}</h1>
// Only depends on title
// Doesn't re-render if content changes ✓
})Next Steps
- Reactive Models Concept - User-friendly explanation
- Identity Mapping - Why same instance matters
- System Overview - Full architecture
Last updated on