Skip to Content
LearnArchitectureHow Reactivity Works

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 payloads

Precise 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

Last updated on