observer
observer is a higher-order component (HOC) that wraps a React function
component to make it reactive. When any observable read during render changes,
the component re-renders.
Signature
function observer<P>(
component: React.FunctionComponent<P>,
): React.FunctionComponent<P>
function observer<P, TRef>(
component: React.ForwardRefExoticComponent<
React.PropsWithoutRef<P> & React.RefAttributes<TRef>
>,
): React.MemoExoticComponent<...>
Basic usage
import { observable } from "@fobx/core"
import { observer } from "@fobx/react"
const store = observable({ count: 0 })
const Counter = observer(() => <p>Count: {store.count}</p>)
When store.count changes, Counter re-renders. Other components are
unaffected.
What observer does
- Wraps the component body in
useObserver, which sets up a reactive tracker. - Applies
React.memoso the component also skips re-renders when parent props haven’t changed. - Preserves
displayNameand hoists enumerable own static properties such aspropTypesanddefaultProps.
With forwardRef
const Input = observer(
React.forwardRef<HTMLInputElement, { label: string }>((props, ref) => (
<label>
{props.label}
<input ref={ref} value={store.value} />
</label>
)),
)
Important rules
Reads must happen inside render
The tracking context only captures observables read during the synchronous execution of the render function. Reads outside render are not tracked:
// ❌ count is read outside the render body — not tracked
const count = store.count
const BadCounter = observer(() => <p>{count}</p>)
// ✅ count is read inside the render body — tracked
const GoodCounter = observer(() => <p>{store.count}</p>)
Don’t capture observable values early
// ❌ Observable values captured before the tracked render starts
const a = store.a
const b = store.b
const BadComponent = observer(() => <p>{a} {b}</p>)
// ✅ Destructuring inside tracked render is fine
const GoodComponent = observer(({ store: { a, b } }) => <p>{a} {b}</p>)
Every component that reads observables needs observer
If a child component reads an observable but is not wrapped in observer, it
won’t re-render when that observable changes:
// ✅ Both parent and child are observer
const Parent = observer(() => <Child />)
const Child = observer(() => <p>{store.value}</p>)
observer vs React.memo
observer already applies React.memo. You do not need to wrap an observer
component in React.memo again, and passing a memoized component to
observer() throws.
| Wrapper | Re-renders on prop change | Re-renders on observable change |
|---|---|---|
| Plain component | Yes | No |
React.memo |
Only if props changed | No |
observer |
Only if props changed | Yes, if tracked observables changed |