---
title: 'State Types'
source: 'https://academia.sh/en/courses/frontend-architecture/state-types'
course: 'Application Architecture: Routing, State and Data'
language: en
updated: '2026-08-17T18:11:05+00:00'
license: 'CC BY-SA 4.0'
---

# State Types

Classifying state by kind — local, shared, URL, and server state, along with derived values; the decision procedure that places each kind, the single source of truth principle, and the cost of misplacement.

Routing is complete: the address decides which screen appears, who it is open to, and
which code downloads. One kind of state the address carries has also been defined —
filter, sort order, page. The rest of the North Slope Measurement Station application's
state is scattered: session information sits in guards, the station list sits somewhere
else, open panels sit inside components, and measurements from the server sit in yet
another place.

Calling all of this "state" hides the most important difference among them. Some are
values the application itself produces and solely owns; some are copies of a remote
source. This lesson separates state into kinds, gives the criterion that decides where
each kind belongs, and shows the cost of misplacing it.

## Five Kinds

**Local state** is a value a single component reads and writes: whether a dropdown is
open, a text box's current content. It is born with the component and dies with it.

**Shared state** is a value multiple components read, produced by the application
itself: the interface theme, whether the side navigation is open, the selected unit
system. Its owner is the application; nothing else changes it.

**URL state** is state carried in the address. The previous topic defined it: state
expected to return when shared, bookmarked, and returned to with the back button.

**Server state** is the client-side copy of a remote source: the station list,
measurement records, the signed-in user's information. What sets it apart from the
other four is that ownership does not sit with the client.

**Derived value**, however, is not actually state. Anything computable from other state
falls into this class: the length of a filtered list, the total of selected items, a
flag showing whether a request is still in flight.

This set of five can be turned into a decision procedure.

```js
// state-types.mjs — decision procedure that places state and the cost of storing a derived value
function classify(item) {
  if (item.derivedFrom) return ["derived", `computed from ${item.derivedFrom}`];
  if (item.source === "server") return ["server", "owner is remote, a copy is kept"];
  if (item.inUrl) return ["URL", "must return when shared"];
  if (item.consumers > 1) return ["shared", `${item.consumers} separate consumers read it`];
  return ["local", "a single component reads it"];
}

const ITEMS = [
  { name: "station list", source: "server", consumers: 2 },
  { name: "measurement history page no", inUrl: true, consumers: 2 },
  { name: "selected measurement type filter", inUrl: true, consumers: 3 },
  { name: "signed-in user", source: "server", consumers: 6 },
  { name: "side nav open", consumers: 2 },
  { name: "interface theme", consumers: 5 },
  { name: "form field current value", consumers: 1 },
  { name: "dragged slider live value", consumers: 1 },
  { name: "filtered measurement count", derivedFrom: "measurements + filter" },
  { name: "loading indicator", derivedFrom: "server state" },
];

console.log("-- placement --");
for (const item of ITEMS) {
  const [kind, reason] = classify(item);
  console.log(item.name.padEnd(38), kind.padEnd(12), reason);
}

// Storing a derived value versus computing it on every read.
const filterBy = (measurements, type) => measurements.filter((m) => type === "all" || m.type === type);

let state = {
  measurements: [{ type: "temperature", v: -4.2 }, { type: "humidity", v: 71 }, { type: "temperature", v: -3.8 }],
  filter: "temperature",
  storedCount: 2,                       // derived value updated manually
};
const printLine = (label) => console.log(
  label.padEnd(34),
  "stored:", String(state.storedCount).padEnd(3),
  "computed:", filterBy(state.measurements, state.filter).length);

console.log("-- derived value --");
printLine("start");

state = { ...state, measurements: [...state.measurements, { type: "temperature", v: -5.1 }] };
printLine("new temperature reading added");

state = { ...state, filter: "humidity" };
printLine("filter switched to humidity");

state = { ...state, storedCount: filterBy(state.measurements, state.filter).length };
printLine("stored count corrected manually");

state = { ...state, measurements: state.measurements.filter((m) => m.type !== "humidity") };
printLine("humidity readings removed");
```

```
-- placement --
station list                           server       owner is remote, a copy is kept
measurement history page no            URL          must return when shared
selected measurement type filter       URL          must return when shared
signed-in user                         server       owner is remote, a copy is kept
side nav open                          shared       2 separate consumers read it
interface theme                        shared       5 separate consumers read it
form field current value               local        a single component reads it
dragged slider live value              local        a single component reads it
filtered measurement count             derived      computed from measurements + filter
loading indicator                      derived      computed from server state
-- derived value --
start                              stored: 2   computed: 2
new temperature reading added      stored: 2   computed: 3
filter switched to humidity        stored: 2   computed: 1
stored count corrected manually    stored: 1   computed: 1
humidity readings removed          stored: 1   computed: 0
```

The procedure's ordering is not arbitrary. Derivability is tested first, because a
derived value is not placed anywhere. Server origin comes second: the ownership
question precedes the question of how many components read it. The address test comes
before the sharing test, because state written to the address already becomes readable
application-wide.

## The Cost of Storing a Derived Value

The second part of the output walks through a single defect step by step. The filtered
measurement count is stored in a state field and updated manually.

When a new measurement is added, the stored value stays at two while the computed value
rises to three. When the filter switches to humidity, the gap between them reverses
direction. When the stored value is corrected manually, the two agree for a moment — but
the next change pulls them apart again. Every new write site is a new place where the
update can be forgotten.

This is an instance of a single rule: **the same information is not kept in two
places.** When a derived value is stored, two sources of truth appear and which one is
correct becomes ambiguous. If the cost of computation is a concern, the answer is not
storing it but memoizing the computation; that is the subject of the Derived Values
lesson in the Component-Based Interface Development course, and the result is still a
single source of truth.

Treating the loading indicator as derived rests on the same reasoning. When a separate
flag is kept, the case where the request finishes and the flag is not turned off is
unavoidable — on the error path, on cancellation, on a concurrent second request. The
indicator's true source is the request's own state.

## The Separateness of Server State

The other four are values the application itself produces: the written value is
correct, because the application is the one writing it. This does not hold for server
state. The station list on the client is a copy of the remote list at some past moment.
Another user may have added a new station, someone may have corrected a measurement.

This difference raises four questions, none of which is asked for the other kinds of
state. How long is the copy considered current? When it goes stale, when is it
refreshed? If two components request the same data, how many requests are made? While a
local change is being written to the server, what appears on screen?

Placing server state in the same location as shared state loses these four questions.
The application takes the copy for the truth: data is fetched once, kept in memory, and
never refreshed. The user cannot see, in the list, a station they added in another tab.
For this reason server state is kept in a separate layer, with its own freshness rules;
that layer is the subject of this topic's fourth lesson.

## The Placement Principle

The principle that decides where state belongs is keeping it as low as possible: a
value only one component cares about is not lifted up.

The reasoning is a direct consequence of the one-way flow from the Component-Based
Interface Development course. Every value lifted up creates a dependency the components
in between are forced to see; it also announces its change to components it does not
affect. State kept low concerns only its own component.

There is a trap in the opposite direction: keeping state lower than it needs to be.
When two sibling components each keep the same value inside themselves, two copies form
and drift apart. The criterion is fixed: the **nearest common ancestor** of the
components reading the value. It cannot be kept lower than that, and keeping it higher
is unnecessary.

This principle does not apply to two kinds. URL state's location is already fixed;
server state, meanwhile, does not belong to any node of the component tree, because
components in different parts of the tree request the same data and the copy must be
singular.

## Summary

- State separates into five classes: local, shared, URL, server, and derived; the first
  four are stored, the fifth is computed.
- The decision procedure asks derivability first, then ownership, then the address, and
  consumer count last.
- When a derived value is stored, two sources of truth form and every write site risks
  drifting apart again; the answer is not storing it but memoizing the computation when
  needed.
- Server state's owner is not the client; the questions of freshness, refresh, request
  repetition, and what appears on screen during a write are asked only for this kind.
- Client state is kept at the nearest common ancestor of the components that read it —
  keeping it lower produces copies, keeping it higher creates an unnecessary dependency.

## Next Step

The kinds are separated, but how shared state is held is not yet clear. A value kept at
the common ancestor gets threaded through dozens of components that do not use it as the
tree deepens; worse, who changes this value and in which event scatters through the
code. Selecting a row in the station list, deleting a measurement, signing out — all
three change the same state from different places, and when a bug appears, who made the
change cannot be traced. The next lesson covers the centralized store pattern, which
collects state in a single place, accepts change only through named actions, and defines
transitions with a pure function.
