mobx-react-lite Observer Subscription Boundary Behavior
0 reputation · 29 Jun 2020, 18:00 UTC
0 reputation · 29 Jun 2020, 18:00 UTC
The integration relies on MobX 6 observable state within React function components using mobx-react-lite observer.
Reactivity is established solely when observable properties are read during the component function execution phase of a React render.
What is the precise boundary for subscription registration within this integration?
useEffect hook establish a persistent subscription tied to the component lifecycle?35775 reputation · 30 Jun 2020, 02:21 UTC
The precise boundary is the synchronous execution of the component function body during React's render phase. observer tracks only observable reads that happen while React is rendering that component. Reads in event handlers or in useEffect after commit do not register render-cycle subscriptions.
onClick or another handler occurs outside render. It does not subscribe the component, so later changes to that observable will not trigger a re-render.useEffect is a post-commit snapshot. It does not create a persistent lifecycle subscription. To keep reacting, create a MobX reaction or autorun inside the effect and dispose it in the cleanup.useObserver tracks only the function passed to it. Observer-wrapped children track their own renders; non-observer children are tracked by the nearest parent observer during the parent's render.This answer covers MobX 6 with mobx-react-lite function components and hooks. MobX 5 decorators, class components, and React concurrent-rendering edge cases are not addressed. Exact hook names, batching, and StrictMode behavior can differ by installed mobx-react-lite and React versions; pin versions when verifying.
Render a component that reads an observable only in render, only in a click handler, and only in useEffect. Change the observable outside React and confirm only the render read triggers re-renders.
import { observer } from 'mobx-react-lite'
import { observable } from 'mobx'
import { useEffect } from 'react'
const state = observable({ count: 0 })
const Demo = observer(() => {
// Tracked: render read
const renderValue = state.count
useEffect(() => {
// Not tracked as a render subscription
console.log('effect snapshot', state.count)
}, [])
const handleClick = () => {
// Not tracked as a render subscription
console.log('handler read', state.count)
}
return <button onClick={handleClick}>{renderValue}</button>
})If logic inside useEffect needs to respond to future observable changes, create an explicit reaction and dispose it:
import { reaction } from 'mobx'
useEffect(() => {
const dispose = reaction(
() => state.count,
(count) => { /* side effect */ }
)
return dispose
}, [])In React 18 StrictMode, effects may run twice in development. Effect-based subscriptions must be idempotent and dispose cleanly. This does not move the MobX render-tracking boundary.
If your component still fails to update, the one detail that changes the recommendation is whether the failing read is inside a callback passed to a non-observer child or a custom hook. That determines whether the fix is moving the read into the observer render body or wrapping the child in its own observer.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.