Skip to main content
Get up and running with Legend-State in just a few minutes. This guide will walk you through installation, basic usage, and your first reactive component.

Installation

Install Legend-State using your preferred package manager:
npm install @legendapp/state

Basic usage

1

Create an observable

Create observables to hold your state. Observables can contain any value - primitives, objects, arrays, or even functions.
import { observable } from '@legendapp/state';

// Primitive observable
const count$ = observable(0);

// Object observable
const user$ = observable({
  name: 'Alice',
  age: 30
});

// Array observable
const todos$ = observable([
  { id: 1, text: 'Learn Legend-State', completed: false },
  { id: 2, text: 'Build something awesome', completed: false }
]);
2

Get and set values

Use get() to read values and set() to update them. It’s that simple!
// Get values
console.log(count$.get()); // 0
console.log(user$.name.get()); // 'Alice'

// Set values
count$.set(1);
user$.name.set('Bob');

// Update with a function
count$.set(prev => prev + 1);

// Update nested objects
user$.set({ name: 'Charlie', age: 25 });
3

Observe changes

React to changes with the observe() function. It runs immediately and whenever tracked observables change.
import { observe } from '@legendapp/state';

// Observe runs whenever count$ changes
observe(() => {
  console.log('Count changed:', count$.get());
});

// Will log: "Count changed: 2"
count$.set(2);
4

Create computed observables

Computed observables automatically update when their dependencies change.
// Computed observable using a function
const doubled$ = observable(() => count$.get() * 2);

console.log(doubled$.get()); // 4 (count is 2)

count$.set(5);
console.log(doubled$.get()); // 10

React integration

Legend-State provides powerful React integration with hooks and components for building reactive UIs.
1

Use the observer HOC

The observer HOC makes components automatically re-render when observables they access change.
import { observable } from '@legendapp/state';
import { observer } from '@legendapp/state/react';

const state$ = observable({
  count: 0
});

const Counter = observer(function Counter() {
  // Component automatically tracks access to count
  const count = state$.count.get();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => state$.count.set(c => c + 1)}>
        Increment
      </button>
    </div>
  );
});
2

Use the useObservable hook

Create local component state with useObservable(). It works just like observables but is scoped to the component.
import { useObservable } from '@legendapp/state/react';

function TodoList() {
  const todos$ = useObservable([
    { id: 1, text: 'Learn Legend-State', completed: false }
  ]);

  const addTodo = () => {
    const newTodo = {
      id: Date.now(),
      text: 'New todo',
      completed: false
    };
    todos$.set(prev => [...prev, newTodo]);
  };

  return (
    <div>
      {todos$.get().map(todo => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <button onClick={addTodo}>Add Todo</button>
    </div>
  );
}
3

Use fine-grained rendering

The Memo component enables fine-grained reactivity - it updates itself without re-rendering the parent component.
import { observable } from '@legendapp/state';
import { Memo } from '@legendapp/state/react';

const state$ = observable({ count: 0 });

function FineGrainedCounter() {
  // This component NEVER re-renders!
  return (
    <div>
      Count: <Memo>{state$.count}</Memo>
      <button onClick={() => state$.count.set(c => c + 1)}>
        Increment
      </button>
    </div>
  );
}

Complete example

Here’s a complete todo list example that demonstrates multiple concepts:
import { observable } from '@legendapp/state';
import { observer, useObservable, For, Memo } from '@legendapp/state/react';

const state$ = observable({
  todos: []
});

const TodoApp = observer(function TodoApp() {
  const input$ = useObservable('');

  const addTodo = () => {
    const text = input$.get();
    if (!text.trim()) return;

    state$.todos.set(prev => [
      ...prev,
      { id: Date.now(), text, completed: false }
    ]);
    input$.set('');
  };

  const toggleTodo = (id: number) => {
    const todo = state$.todos.find(t => t.id.peek() === id);
    if (todo) {
      todo.completed.set(c => !c);
    }
  };

  return (
    <div>
      <h1>Todo List</h1>
      
      <div>
        <input
          value={input$.get()}
          onChange={e => input$.set(e.target.value)}
          placeholder="Add a todo..."
        />
        <button onClick={addTodo}>Add</button>
      </div>

      <For each={state$.todos}>
        {todo$ => (
          <div>
            <input
              type="checkbox"
              checked={todo$.completed.get()}
              onChange={() => toggleTodo(todo$.id.peek())}
            />
            <span
              style={{
                textDecoration: todo$.completed.get() ? 'line-through' : 'none'
              }}
            >
              <Memo>{todo$.text}</Memo>
            </span>
          </div>
        )}
      </For>
    </div>
  );
});

Key concepts

Observables

Learn about creating and working with observables

React Hooks

Explore React integration hooks like useObservable and useSelector

Fine-Grained Rendering

Master fine-grained reactivity for maximum performance

Persistence & Sync

Set up automatic persistence and real-time sync

Next steps

Core concepts

Dive deep into observables, computed values, and reactivity

React integration

Learn about all React hooks and components

Sync & persistence

Add local-first sync and persistence to your app

TypeScript guide

Get the most out of TypeScript with Legend-State

Build docs developers (and LLMs) love