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 observerables (similar to libraries like MobX), oruseSelector
to grab specific observables and track them. - Fine-grained reactivity: individual elements re-render themselves. You can render observable strings directly to make them self-updating, 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();
// Magic 🦄
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 computation function that listens to observables accessed while runningoptions
:forceRender
: If you already have a forceRender function or are using multiple useSelectors, you can reuse the same hookshouldRender
:true
or(current, previous) => boolean
to determine if the new value should trigger a render.true
can be an optimization to run the selector only once.
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' export const State = observable({ settings: { showSidebar: false, theme: 'light' }, user: { profile: { name: '', avatar: '' }, messages: {} } }) // Persist state persistObservable(State, { local: 'example' })
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 { enableLegendStateReact, reactive, useComputed, useObservable } from '@legendapp/state/react' import { Legend } from '@legendapp/state/react-components' import { motion } from "framer-motion" enableLegendStateReact() export default 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} </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"