# State

> A pure reducer, a useReducer binding and a throwing context accessor — the whole state convention, with no state library in the app.

Page: https://sdk.chatfuel.com/docs/concepts/state
Markdown: https://sdk.chatfuel.com/docs/concepts/state.md

There is no state library in the app, and no router library either. The vendored dependency set
stays frozen, so everything a module needs to hold state is already in React — and the convention
that follows is three files with three jobs.

## The triad [#the-triad]

| File                      | What it is                                                            |
| ------------------------- | --------------------------------------------------------------------- |
| `lib/<name>Store.ts`      | a pure reducer — no React, no clock, no fetching, directly testable   |
| `hooks/use<Name>Store.ts` | the `useReducer` binding, and the effects that feed it                |
| `<Name>Context.ts`        | a throwing accessor, so a component outside the provider fails loudly |

The third one is four lines and earns them. `useContacts()` throws
`useContacts must be used inside <ContactsApp>` rather than handing back `null` for somebody to
optional-chain past — a component in the wrong place is a mistake you want at mount time, in a
message that names the provider.

A module can have several of these. `bookings` has seven stores, because a calendar range, a service
catalog and a staff form are three unrelated pieces of state that happen to share a screen.

## Why the reducer is the half worth testing [#why-the-reducer-is-the-half-worth-testing]

Splitting the reducer out is not tidiness. It is what makes the hard parts assertable, and every
store in the app is built to be tested this way:

* **The reducer never reads the clock.** `now` arrives in the action. So a test about a row that
  flashes for 1400 ms, or a live arrival that stays marked for 2000 ms, is exact rather than
  timing-dependent.
* **The stale-response guard is in state, not in a ref.** Every request-shaped action carries the
  epoch it was issued under and is dropped if that epoch has moved on. In a ref that guard would be
  invisible to a test; in state, a test can drive it.
* **There is one record cache and one order.** Rows live in a `byId` map and the views hold ordered
  ids, so a live echo, a page append and an optimistic edit all write the same cache and two
  surfaces cannot show two versions of one record.

The tests import the reducer, its initial state and its selectors — and nothing from React. That is
the point: no render, no fake timers, no waiting.

There are exceptions, and they are written down where they live. In the deals board a live batch is
guarded by `loading` rather than by the epoch, because gating it on the epoch would make the
subscription effect depend on the epoch and tear the WebSocket down on every refetch.

## The URL keys live in one pure file per module [#the-url-keys-live-in-one-pure-file-per-module]

Deep-link state has its own file, `lib/<name>Params.ts`, and it is pure the same way the store is:
parse in, serialize out, no React. Two rules hold the whole thing together.

**An unknown value falls back silently.** A hand-edited or stale address renders the default. It must
never throw and never white-screen, because the link somebody is opening was minted three releases
ago.

**A default is omitted from what gets written.** Otherwise every mount would rewrite the URL with the
full schema, and a link you shared would carry ten parameters that say nothing.

What stays out of the URL is as deliberate as what goes in. Attribute filter groups are unbounded in
size — a link carrying twenty predicates is not a link — so they live in saved views instead.

## What it costs [#what-it-costs]

You get no devtools, no time-travel and no selector library. Nothing memoises for you, and nothing
stops two modules from solving the same problem twice, because they cannot import each other's
solution.

The trade is that a store is a file you can read end to end in one sitting, and its test needs no
runtime at all.

The other half of a module's state is its address, and that half belongs to the shell — see
[routing](/docs/concepts/routing).
