Skip to main content
Legend-State provides powerful React integration that enables fine-grained reactivity, automatic re-renders, and exceptional performance. The React integration is designed to be simple, type-safe, and blazingly fast.

Why Use Legend-State with React?

1. Fine-Grained Reactivity

Legend-State lets you make your renders super fine-grained, so your apps will be much faster because React has to do less work. The best way to be fast is to render less, less often.
import { useObservable } from '@legendapp/state/react'
import { Memo } from '@legendapp/state/react'

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

    useInterval(() => {
        count$.set(v => v + 1)
    }, 600)

    // The text updates itself so the component doesn't re-render
    return (
        <div>
            Count: <Memo>{count$}</Memo>
        </div>
    )
}

2. The Fastest React State Library

Legend-State beats every other state library on just about every metric and is so optimized for arrays that it even beats vanilla JS on the “swap” and “replace all rows” benchmarks. At only 4kb and with the massive reduction in boilerplate code, you’ll have big savings in file size too.

3. Simple API with No Boilerplate

There is no boilerplate and there are no contexts, actions, reducers, dispatchers, sagas, thunks, or epics. It doesn’t modify your data at all.
import { observable } from '@legendapp/state'
import { observer } from '@legendapp/state/react'

const settings$ = observable({ theme: 'dark' })

const Component = observer(function Component() {
    const theme = settings$.theme.get()

    return <div>Theme: {theme}</div>
})

4. Automatic Dependency Tracking

Components and hooks automatically track which observables they access and re-render only when those specific values change.

Key Concepts

Observables

Observables are the core primitive. They hold reactive state that can be observed for changes:
import { observable } from '@legendapp/state'

const state$ = observable({ count: 0, user: { name: 'John' } })

// Get values
const count = state$.count.get()  // 0

// Set values
state$.count.set(1)
state$.user.name.set('Jane')
By convention, observable variables are suffixed with $ to make them easy to identify.

Selectors

A selector can be:
  • An observable: count$
  • A function that reads observables: () => count$.get()
  • A computed observable: observable(() => count$.get() * 2)

Fine-Grained Rendering

Legend-State provides multiple ways to achieve fine-grained rendering:
  1. observer HOC - Wraps components to auto-track observables
  2. Reactive components - Components that accept observable props
  3. useSelector hook - Re-renders only when selector value changes
  4. Memo component - Updates DOM directly without re-rendering parent

Installation

bun add @legendapp/state
# or
npm install @legendapp/state
# or
yarn add @legendapp/state

Basic Usage

Creating Local State

Use useObservable to create component-local observable state:
import { useObservable } from '@legendapp/state/react'

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

    return (
        <div>
            <div>Count: {count$.get()}</div>
            <button onClick={() => count$.set(v => v + 1)}>
                Increment
            </button>
        </div>
    )
}

Using Global State

Create observables outside components for global state:
import { observable } from '@legendapp/state'
import { observer } from '@legendapp/state/react'

const store$ = observable({
    count: 0,
    user: { name: 'John' }
})

const Counter = observer(function Counter() {
    return (
        <div>
            <div>Count: {store$.count.get()}</div>
            <button onClick={() => store$.count.set(v => v + 1)}>
                Increment
            </button>
        </div>
    )
})

Reactive Components

Reactive components accept observable props prefixed with $:
import { Reactive } from '@legendapp/state/react'
import { useObservable } from '@legendapp/state/react'

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

    return (
        <div>
            {/* Component re-renders only when count$ changes */}
            <Reactive.div $children={count$} />
            <button onClick={() => count$.set(v => v + 1)}>
                Increment
            </button>
        </div>
    )
}

Core Exports

Legend-State exports its React integration from @legendapp/state/react:
import {
    // Hooks
    useObservable,
    useSelector,
    useComputed,
    useObserve,
    useObserveEffect,
    useMount,
    useUnmount,
    useIsMounted,
    useWhen,
    useObservableReducer,
    
    // HOCs
    observer,
    reactive,
    reactiveObserver,
    
    // Components
    Memo,
    Computed,
    Show,
    For,
    Switch,
    Reactive,
} from '@legendapp/state/react'

Type Safety

Legend-State is built with TypeScript and provides excellent type inference:
import { observable } from '@legendapp/state'
import { useObservable } from '@legendapp/state/react'

interface User {
    id: string
    name: string
    email: string
}

const user$ = observable<User>({
    id: '1',
    name: 'John',
    email: '[email protected]'
})

function UserProfile() {
    const localUser$ = useObservable<User>({
        id: '2',
        name: 'Jane',
        email: '[email protected]'
    })

    // TypeScript knows these are strings
    const name = localUser$.name.get()
    const email = user$.email.get()

    return <div>{name} - {email}</div>
}

Performance Tips

  1. Use fine-grained updates - Use Memo or Reactive components to update specific parts without re-rendering the parent
  2. Use observer wisely - Wrap components that read observables with observer for automatic tracking
  3. Avoid unnecessary .get() calls - Only call .get() when you need the value and want to track it
  4. Use computed observables - For derived state, use computed observables instead of calculating in render

Next Steps

React Hooks

Learn about all available React hooks

observer() HOC

Auto-track observables in components

Reactive Components

Use components with observable props

Fine-Grained Rendering

Advanced patterns for optimal performance

Build docs developers (and LLMs) love