React Basics
Legend-State enables a new way of thinking about how React components update, to observe state changing, not renders. But it can also work in a more familiar React style, so there are two ways to use Legend-State in React (that you can mix and match per component).
- Re-render when state changes: the normal React behavior. You can use an
observer
HOC to make components automatically track observables (similar to libraries like MobX), oruseSelector
to track specific observables. - Fine-grained reactivity: individual elements re-render themselves. You can render observable strings directly to create mini self-updating components, use reactive props to update props based on state, and a set of control-flow components to optimize conditional rendering and arrays to re-render as little as possible.
This page describes #1, the basic usage. See Fine-grained reactivity and React Examples for more details about #2.
Two ways to re-render on state changes
observer
HOC enables automatically tracking observable access with the compnentuseSelector
to get individual values and track them
observer
The observer
HOC makes the component automatically track the accessed observables for changes. See Tracking for more about when it tracks.
import { observable } from "@legendapp/state"
import { observer } from "@legendapp/state/react"
const state = observable({ count: 0 })
const Component = observer(function Component() {
// Accessing state automatically makes this component track changes to re-render
const count = state.count.get();
// Re-renders whenever count changes
return <div>{count}</div>
})
useSelector
useSelector
computes a value and automatically listens to any observables accessed while running, and only re-renders if the computed value changes. It is slightly more efficient than observer
so you may prefer it for components that only need to track one observable or selector.
Props:
selector
: Observable or compute function that listens to observables accessed while running
import { observable } from "@legendapp/state"
import { useSelector } from "@legendapp/state/react"
const state = observable({ selected: 1 })
const Component = ({ id }) => {
const isSelected = useSelector(() => id === state.selected.get())
// Only re-renders if the return value changes
return <div>Selected: {isSelected}</div>
}
See Tracking for details about how Legend-State automatically tracks changes.
Local or global state
Legend-State supports both local and global state. Most applications will use a mixture of both. State that is relevant for the whole app should likely be global while state that is only relevant to a subtree of components could be local.
Global state
One way to do global state is to have a single file that defines all state for the application. Then you can import it in other files and access the pieces of state that you need.
Another way could be to have multiple files that own their own state, like a Settings.ts
could define the user settings while Data.ts
handles content from a CMS, for example. Either way is fine - it's up to you!
For brevity this example has one global state object.
import { observable } from '@legendapp/state' import { persistObservable } from '@legendapp/state/persist' import { ObservablePersistLocalStorage } from '@legendapp/state/persist-plugins/local-storage' export const State = observable({ settings: { showSidebar: false, theme: 'light' }, user: { profile: { name: '', avatar: '' }, messages: {} } }) // Persist state persistObservable(State, { local: 'example', persistLocal: ObservablePersistLocalStorage, })
Local state
You can use the hooks to create local state (state owned by a React component). This can replace useState
and useReducer
. Local state could also be passed down a component tree through props or Context. Observables are objects that will never change, so they will never cause Context to re-render and will not re-render child components that use memo
.
import { useRef } from 'react' import { observer, reactive, useComputed, useObservable } from '@legendapp/state/react' import { Legend } from '@legendapp/state/react-components' export default observer(function App() { const renderCount = ++useRef(0).current const first = useObservable('') const last = useObservable('') const name = useComputed(() => (first.get() + ' ' + last.get()).trim() || '(unknown)' ) return ( <div className="flex-1 p-4"> <div className="text-gray-500 text-sm pb-4"> Renders: {renderCount} </div> <h2 className="pb-4 font-bold"> Hi {name.get()} </h2> <div>First:</div> <Legend.input className={classNameInput} value$={first} /> <div>Last:</div> <Legend.input className={classNameInput} value$={last} /> </div> ) }) const classNameInput = "border rounded border-gray-300 px-2 py-1 mt-2 mb-4"