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).

  1. 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), or useSelector to track specific observables.
  2. 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

  1. observer HOC enables automatically tracking observable access with the compnent
  2. useSelector 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"