Skip to main content
Legend-State provides reactive components that enable ultra-fine-grained rendering. These components can update the DOM directly without re-rendering their parent component, making your app incredibly fast.

Overview

Reactive components accept observables as children or props and update only the specific parts of the UI that need to change:
import { useObservable } from '@legendapp/state/react'
import { Memo } from '@legendapp/state/react'

function App() {
    const count$ = useObservable(0)

    // App never re-renders, only the Memo updates
    return (
        <div>
            Count: <Memo>{count$}</Memo>
            <button onClick={() => count$.set(v => v + 1)}>+</button>
        </div>
    )
}

Memo Component

The Memo component renders an observable’s value and updates only itself when the observable changes.

Signature

function Memo(props: {
    children: ObservableParam | (() => ReactNode)
    scoped?: boolean
}): ReactElement

Basic Usage

import { observable } from '@legendapp/state'
import { Memo } from '@legendapp/state/react'

const count$ = observable(0)

function Counter() {
    // Counter never re-renders when count changes
    return (
        <div>
            Count: <Memo>{count$}</Memo>
            <button onClick={() => count$.set(v => v + 1)}>
                Increment
            </button>
        </div>
    )
}

Performance Benefits

import { useObservable } from '@legendapp/state/react'
import { Memo } from '@legendapp/state/react'

let parentRenders = 0

function ParentComponent() {
    parentRenders++
    const count$ = useObservable(0)

    return (
        <div>
            <div>Parent rendered {parentRenders} times</div>
            <div>Count: <Memo>{count$}</Memo></div>
            <button onClick={() => count$.set(v => v + 1)}>+</button>
        </div>
    )
}

// ParentComponent only renders once!
// Clicking the button updates only the Memo

Show Component

Conditionally renders children based on a predicate, with support for else clauses and ready state.

Signature

function Show<T>(props: {
    if?: Selector<T>
    ifReady?: Selector<T>
    else?: ReactNode | (() => ReactNode)
    $value?: Observable<T>
    wrap?: FC<{ children: ReactNode }>
    children: ReactNode | ((value?: T) => ReactNode)
}): ReactElement

Basic Usage

import { useObservable } from '@legendapp/state/react'
import { Show } from '@legendapp/state/react'

function Component() {
    const isLoggedIn$ = useObservable(false)

    return (
        <div>
            <Show if={isLoggedIn$}>
                <div>Welcome back!</div>
            </Show>
            <button onClick={() => isLoggedIn$.set(true)}>
                Log In
            </button>
        </div>
    )
}

For Component

Renders lists efficiently, automatically keying items and updating only changed items.

Signature

function For<T, TProps>(props: {
    each?: ObservableParam<T[] | Record<any, T> | Map<any, T>>
    optimized?: boolean
    item?: FC<ForItemProps<T, TProps>>
    itemProps?: TProps
    sortValues?: (A: T, B: T, AKey: string, BKey: string) => number
    children?: (value: Observable<T>, id: string | undefined) => ReactElement
}): ReactElement | null

type ForItemProps<T, TProps = {}> = {
    item$: Observable<T>
    id?: string
} & TProps

Basic Usage

import { observable } from '@legendapp/state'
import { For } from '@legendapp/state/react'
import { observer } from '@legendapp/state/react'

const todos$ = observable([
    { id: '1', text: 'Learn Legend-State', done: false },
    { id: '2', text: 'Build an app', done: false }
])

const TodoItem = observer(function TodoItem({ item$ }) {
    return (
        <div>
            <input 
                type="checkbox"
                checked={item$.done.get()}
                onChange={(e) => item$.done.set(e.target.checked)}
            />
            {item$.text.get()}
        </div>
    )
})

function TodoList() {
    return (
        <div>
            <For each={todos$} item={TodoItem} />
        </div>
    )
}

Optimized Mode

import { observable } from '@legendapp/state'
import { For } from '@legendapp/state/react'

const items$ = observable([1, 2, 3, 4, 5])

function OptimizedList() {
    return (
        <For each={items$} optimized>
            {(item$) => <div>{item$.get()}</div>}
        </For>
    )
}
optimized mode uses shallow tracking to minimize re-renders when only array length changes.

Switch Component

Renders different content based on a value, like a switch statement.

Signature

function Switch<T>(props: {
    value?: Selector<T>
    children: Partial<Record<any, () => ReactNode>>
}): ReactElement | null

Usage

import { useObservable } from '@legendapp/state/react'
import { Switch } from '@legendapp/state/react'

function StatusIndicator() {
    const status$ = useObservable('loading')

    return (
        <Switch value={status$}>
            {{
                loading: () => <div>Loading...</div>,
                success: () => <div>Success!</div>,
                error: () => <div>Error occurred</div>,
                default: () => <div>Unknown status</div>
            }}
        </Switch>
    )
}

Reactive Object

The Reactive object provides reactive versions of all HTML elements that accept observable props.

Usage

import { useObservable } from '@legendapp/state/react'
import { Reactive } from '@legendapp/state/react'

function Component() {
    const text$ = useObservable('Hello World')
    const color$ = useObservable('blue')
    const isVisible$ = useObservable(true)

    return (
        <div>
            {/* Props prefixed with $ are reactive */}
            <Reactive.div 
                $style={() => ({ color: color$.get() })}
                $className={() => isVisible$.get() ? 'visible' : 'hidden'}
            >
                <Reactive.span $children={text$} />
            </Reactive.div>

            <input 
                value={text$.get()}
                onChange={(e) => text$.set(e.target.value)}
            />
        </div>
    )
}

All HTML Elements

Reactive provides reactive versions of all HTML elements:
import { Reactive } from '@legendapp/state/react'

<Reactive.div $children={...} />
<Reactive.span $children={...} />
<Reactive.button $onClick={...} />
<Reactive.input $value={...} />
<Reactive.img $src={...} />
// ... and all other HTML elements

reactive() HOC

Create reactive versions of your own components:
function reactive<T>(component: FC<T>): FC<ShapeWith$<T>>
function reactive<T, K>(
    component: FC<T>,
    keys: K[],
    bindKeys?: BindKeys<T, K>
): FC<ReactifyProps<T, K>>
import { reactive } from '@legendapp/state/react'
import { useObservable } from '@legendapp/state/react'

function Card({ title, subtitle }) {
    return (
        <div>
            <h3>{title}</h3>
            <p>{subtitle}</p>
        </div>
    )
}

const ReactiveCard = reactive(Card)

function App() {
    const title$ = useObservable('Hello')
    const subtitle$ = useObservable('World')

    return <ReactiveCard $title={title$} $subtitle={subtitle$} />
}

Best Practices

  1. Use Memo for simple values - When you just need to display an observable value
  2. Use Show for conditions - Better than ternary operators for reactive conditions
  3. Use For for lists - More efficient than .map() for observable arrays
  4. Use Switch for multiple states - Cleaner than nested conditions
  5. Use Reactive for granular updates - Update specific props without re-rendering
Reactive components are most beneficial when:
  • Parent component is expensive to render
  • Updates are frequent
  • Only small parts of UI need to update
For simple cases, observer might be simpler.

See Also

observer() HOC

Automatic tracking for components

Fine-Grained Rendering

Advanced performance patterns

React Hooks

Complete hooks reference

Build docs developers (and LLMs) love