agentsclimarketplace

Implementing legend state

Skill AlshehriAli0/agent-skills/skills/implementing-legend-state

Agent skill: my custom skills

Install
npx -y skills add AlshehriAli0/agent-skills --skill implementing-legend-state

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Implement Legend-State (v3), the signal-based @legendapp/state library, as the primary state layer in a React or React Native app. Use when a task names Legend-State or @legendapp/state; when adding, refactoring, or designing state management in a project that uses (or is moving to) Legend-State; when migrating from Zustand, Redux, Jotai, Recoil, or plain TanStack Query; or when reviewing or fixing existing Legend-State code. Covers observables, useValue, global stores, computed/derived state, fine-grained reactivity (For/Show/Switch/Memo), local persistence (MMKV, AsyncStorage, localStorage, IndexedDB, expo-sqlite), and remote sync (synced, syncedCrud, Supabase, TanStack Query, fetch, Keel).

SKILL.md

9.6 KB, as published. Nobody here has run it

Implementing Legend-State (v3)

Legend-State is a signal-based state library: you wrap data in an observable, read it with get(), change it with set(), and components re-render only on the exact values they read. It handles local state, global state, persistence, and remote sync in one model, so it can be a project's primary state driver rather than one of several libraries.

This skill encodes the v3 way. The most common failure is a v2 reflex: reaching from memory for observer + .get(), useSelector, computed, or persistObservable. v3 replaced all four, and picking the wrong one compiles but silently breaks reactivity. Trust this skill and the reference files over prior knowledge.

Before you write code

  1. Confirm the version. v3 is currently published under the @beta tag (npm install @legendapp/state@beta). If the project already depends on @legendapp/state, check package.json: a 2.x version means v2, so read references/migration-and-gotchas.md before changing anything. Verify the current beta from npm when you have network access rather than assuming a version number.
  2. Read the reference file(s) for the task. This SKILL.md is a map. Load the specific reference below for the area you're working in; it has the full API surface and worked examples.
  3. Match the platform. Reactive component imports differ: web uses @legendapp/state/react-web ($React.div), React Native uses @legendapp/state/react-native ($TextInput, $View, $Text). Persistence plugins differ too.

Reference map (load on demand)

You are doing...Read
Creating observables, get/set/peek/assign/delete, computeds, linked, lookup tables, arraysreferences/observables.md
Listening/deriving outside React: observe, when, onChange, observing contexts, batchingreferences/reactivity.md
Anything React: reading state, hooks, fine-grained components, Context, two-way bindingreferences/react.md
Persistence (MMKV/AsyncStorage/localStorage/IndexedDB/expo-sqlite) or remote sync (CRUD/Supabase/Query/fetch/Keel)references/sync-and-persistence.md
Optimizing renders, large lists/arrays, debugging extra rendersreferences/performance.md
Helper functions/observables: ObservableHint, mergeIntoObservable, trackHistory, undoRedo, time, pageHashreferences/helpers.md
Worked patterns: persisted store, auto-saving form, validation, list, animation, routerreferences/recipes.md
Migrating from v2, Zustand, Redux, or raw TanStack Query; or fixing wrong/old codereferences/migration-and-gotchas.md

Copy-paste starting points live in assets/ (store, persistence setup, CRUD factory).

The core rules (the parts most likely to be done wrong)

1. Read with useValue, not observer + .get()

In v3 the default way to consume an observable in a React component is the useValue hook. It tracks every observable read while it runs and re-renders only when the computed result changes.

import { useValue } from '@legendapp/state/react'

const theme = useValue(settings$.theme)            // re-renders when theme changes
const isDark = useValue(() => settings$.theme.get() === 'dark') // re-renders when the boolean flips

observer still exists, but in v3 it is an optional optimization (it merges many useValue calls into one hook) and is no longer the recommended default. The old pattern of observer(() => { const x = state$.x.get() }) is discouraged because it is incompatible with React Compiler. useSelector and use$ are former names for useValue; use useValue. Details and the observer optimization case: references/react.md.

2. Name observables with a $ suffix

Convention, not enforced, but it keeps observables visually distinct from plain values and matches every doc and plugin example: const user$ = observable(...), and item observables from For arrive as item$.

3. Change state with set() / assign(), never direct assignment

Direct assignment to objects/arrays is blocked on purpose (a "footgun guard") and won't notify listeners.

state$.text.set('hi')                 // ✅
state$.assign({ a: 1, b: 2 })         // ✅ shallow merge, batched
state$.count.set(c => c + 1)          // ✅ updater form
state$.obj = {}                       // ❌ throws / no notification

4. Computeds are just functions inside the observable

There is no separate computed in v3; a function is a lazy computed that recomputes when the observables it reads change. It only recomputes while it's being observed.

const state$ = observable({
  fname: 'Annyong',
  lname: 'Bluth',
  fullName: () => state$.fname.get() + ' ' + state$.lname.get(), // computed child
})

5. Don't clone to update; mutate the observable directly

Legend-State is mutable by design (immutability is slower and unnecessary here). The React habit of spreading a new object/array is an anti-pattern: do the targeted operation on the observable.

list$.push(item)            // ✅  not list$.set([...list$.get(), item])
record$.key.set('value')    // ✅  not record$.set({ ...record$.get(), key: 'value' })
list$[i].delete()           // ✅  removes the element

6. get() tracks, peek() doesn't

Inside an observing context (useValue, observe, a computed, a synced get), get() subscribes to changes. Use peek() to read without subscribing; important when reading a value you don't want to re-render on, and when generating keys while mapping arrays.

7. Arrays of objects need a stable id, and render with For

Each object in an observable array should have a unique id (or key, or a ${arr}_keyExtractor). Render lists with the For component so each row tracks itself and the parent doesn't re-render. Inside map, use item.peek().id for the key so you don't accidentally track every element. Full array guidance: references/performance.md.

8. synced is lazy; syncObservable is eager

synced(...) inside observable(...) sets up persistence/sync that activates on first get(). syncObservable(state$, ...) starts immediately on a value you already created. Both take the same options. Pick a persistence plugin for the platform and (optionally) set defaults once with configureSynced. Full sync model: references/sync-and-persistence.md.

A minimal, correct end-to-end example

import { observable } from '@legendapp/state'
import { useValue, For } from '@legendapp/state/react'
import { syncObservable } from '@legendapp/state/sync'
import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv' // RN; use local-storage on web

interface Todo { id: string; text: string; done?: boolean }

const store$ = observable({
  todos: [] as Todo[],
  remaining: (): number => store$.todos.get().filter(t => !t.done).length, // computed
  addTodo: (text: string) =>
    store$.todos.push({ id: crypto.randomUUID(), text }),
})

syncObservable(store$, { persist: { name: 'todos', plugin: ObservablePersistMMKV } })

function Row({ item$ }: { item$: typeof store$.todos[number] }) {
  const done = useValue(item$.done)
  return <Text onPress={() => item$.done.set(d => !d)}>{useValue(item$.text)} {done ? '✓' : ''}</Text>
}

export function TodoList() {
  const remaining = useValue(store$.remaining)
  return (
    <>
      <Text>Remaining: {remaining}</Text>
      <For each={store$.todos} item={Row} />
    </>
  )
}

Using Legend-State as the primary state driver

When the goal is to make Legend-State the main state layer (replacing Zustand/Redux and absorbing server-cache duties), apply these defaults:

  • One source of truth per domain. Either a single large store$ or several feature atoms (both are fine); pick one convention per project and keep it (references/observables.md and assets/store.template.ts).
  • Co-locate sync with the data, not the UI. Define synced/syncedCrud in the observable so components only ever get()/set(); there's no fetching or mutation code in components. This is what replaces most manual TanStack Query usage (references/sync-and-persistence.md).
  • Keep components render-once. Prefer fine-grained reads (useValue on the narrowest node, For, Memo, two-way $-bound inputs) so parents don't re-render on child changes (references/react.md, references/performance.md).
  • Persist by default for local-first. Set a global persist plugin with configureSynced, then name each store (assets/persist.native.template.ts / assets/persist.web.template.ts).

Verifying existing or migrated code

Check the code against the common-mistake list in references/migration-and-gotchas.md. The work is done when every deprecated pattern it names (useSelector/use$, observer + .get(), computed(, persistObservable(, direct-assignment-then-set, clone-then-set) is either fixed or deliberately kept for a reason you can name.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.