# Little State Machine

Little State Machine is a small, action-based global state manager for React. It lets components share state without a provider tree. State can persist in `sessionStorage` or `localStorage`.

- Package: `little-state-machine`
- Current documented API: v5
- React peer support: React 18 and 19
- Runtime dependencies: none besides React
- License: MIT
- Author site: [bluebill1049.dev](https://bluebill1049.dev)
- Source: [GitHub](https://github.com/beekai-oss/little-state-machine)

## Install

```sh
npm install little-state-machine
```

## Create the store

Call `createStore` once near application startup. The initial state should be a plain object.

```js
import { createStore } from 'little-state-machine'

createStore(
  { profile: { firstName: '', lastName: '' } },
  {
    name: 'my-app',
    storageType: window.sessionStorage,
    persist: 'action',
  },
)
```

### `createStore(initialState, options?)`

Options:

- `name`: storage key and store name.
- `middleWares`: array of middleware functions invoked for actions.
- `storageType`: a Web Storage object, such as `window.sessionStorage` or `window.localStorage`. The default is session storage when available.
- `persist`: `'action'` saves after each action; `'beforeUnload'` saves before the page unloads; `'none'` disables persistence. Action persistence is the default.

## Define actions and read state

An action receives the current state and the payload passed to its dispatcher. Return the next state. Spread nested objects when you want to retain their other fields.

```js
import { useStateMachine } from 'little-state-machine'

const updateProfile = (state, payload) => ({
  ...state,
  profile: { ...state.profile, ...payload },
})

function ProfileName() {
  const { state, actions, getState } = useStateMachine({
    actions: { updateProfile },
  })

  return (
    <button onClick={() => actions.updateProfile({ firstName: 'Ada' })}>
      Hello, {state.profile.firstName}
    </button>
  )
}
```

### `useStateMachine(config?)`

Returns:

- `state`: current global state.
- `actions`: dispatchers created from the configured `actions` object. Call each as `actions.actionName(payload)`.
- `getState()`: reads the current store state imperatively.

Config options:

- `actions`: map of action names to `(state, payload) => nextState` functions.
- `selector`: function that returns the state slice or derived value this component needs. It can limit rerenders to changes in that selection.

## Middleware

Pass middleware functions through `middleWares` when creating the store. Middleware runs for each action and can inspect or transform the current state. A middleware function receives the state, action name, and payload; return a replacement state to change it.

## TypeScript

Augment the `GlobalState` interface in a declaration file to type the shared store:

```ts
import 'little-state-machine'

declare module 'little-state-machine' {
  interface GlobalState {
    profile: {
      firstName: string
      lastName: string
    }
  }
}
```

## v5 notes

- `StateMachineProvider` was removed; no provider wrapper is needed.
- Actions are configured as an object with `useStateMachine({ actions: { ... } })`.
- React 18 or later is required.

## Related project

[React Hook Form](https://react-hook-form.com/) is a separate project by bluebill1049.dev. Little State Machine and React Hook Form have different APIs and purposes.

## Persisted state and migrations

If data is saved for the configured store name, `createStore` loads it as-is. Defaults apply only when no saved value exists, so adding a default key does not initialize it for existing users. The library has no migration hook.

Version and migrate saved data before calling `createStore`. Merge nested defaults, map renamed fields, and remove obsolete fields explicitly:

```js
const key = 'my-app'
const storage = window.localStorage
const defaults = { profile: { name: '', email: '' }, version: 2 }
const saved = JSON.parse(storage.getItem(key) || 'null')

if (saved && (saved.version ?? 1) < 2) {
  const migrated = {
    ...defaults,
    ...saved,
    profile: {
      ...defaults.profile,
      ...saved.profile,
      name: saved.profile?.fullName ?? saved.profile?.name ?? '',
    },
    version: 2,
  }
  delete migrated.profile.fullName
  delete migrated.legacyField
  storage.setItem(key, JSON.stringify(migrated))
}

createStore(defaults, { name: key, storageType: storage })
```

Handle malformed saved JSON before parsing it in production.
