State of component
since mobx-react-lite@1.3.0
useLocalStore<T, S>(initializer: () => T, source?: S): T
Local observable state can be introduced by using the useLocalStore
hook, that runs its initializer function once to create an observable store and keeps it around for a lifetime of a component.
All properties of the returned object will be made observable automatically, getters will be turned into computed properties, and methods will be bound to the store and apply mobx transactions automatically. If new class instances are returned from the initializer, they will be kept as is.
Note that using a local store might conflict with future React features like concurrent rendering.
import React from 'react'
import { useLocalStore, useObserver } from 'mobx-react' // 6.x
export const SmartTodo = () => {
const todo = useLocalStore(() => ({
title: 'Click to toggle',
done: false,
toggle() {
todo.done = !todo.done
},
get emoji() {
return todo.done ? '😜' : '🏃'
},
}))
return useObserver(() => (
<h3 onClick={todo.toggle}>
{todo.title} {todo.emoji}
</h3>
))
}
What about global store?
The naming useLocalStore
was chosen to indicate that store is created locally in the component. However, that doesn't mean you cannot pass such store around the component tree. In fact it's totally possible to tackle global state management with useLocalStore
despite the naming. You can for example setup bunch of local stores, assemble them in one root object and pass it around the app with a help of the React Context.
Non observable dependencies
since mobx-react-lite@1.4.0 or mobx-react@6.0
It is important to realize that the store is created only once (per component instance)! It is not possible to specify dependencies to force re-creation, nor should you directly be referring to anything non-observable in the initializer function, as changes in those won't propagate.
The useLocalStore
supports passing a second argument with a plain object of anything non-observable you want to be used in store derivations. It might be a value from props, useContext
or even useReducer
if you like to mix things up. The object you pass in the second arg should always have a same shape (no conditionals).
import { observer, useLocalStore } from 'mobx-react' // 6.x
export const Counter = observer(props => {
const store = useLocalStore(
// don't ever destructure source, it won't work
source => ({
count: props.initialCount,
get multiplied() {
// you shouldn't ever refer to props directly here, it won't see a change
return source.multiplier * store.count
},
inc() {
store.count += 1
},
}),
props, // note props passed here
)
return (
<>
<button id="inc" onClick={store.inc}>
{`Count: ${store.count}`}
</button>
<span>{store.multiplied}</span>
</>
)
})
Note that internally the useAsObservableSource
hook is used to wrap around passed object. If you don't need actions or computed properties, feel free to use that hook directly. See more about state outsourcing.