Build CoreOrdered learning track

State Taxonomy

Learn React Hooks, State Management, Component Composition, Context Passing, Component Communications & Orchestration - Part 049

A production-grade taxonomy for React state: local, shared, derived, server, URL, persistent, external, workflow, capability, and ephemeral state with ownership, lifecycle, invalidation, and failure modes.

9 min read1737 words
PrevNext
Lesson 49123 lesson track24–67 Build Core
#react#state-management#state-taxonomy#frontend-architecture+2 more

Part 049 — State Taxonomy

State management yang matang dimulai bukan dari pertanyaan:

Pakai Redux, Zustand, Context, TanStack Query, atau useState?

Pertanyaan yang benar:

State apa ini?
Siapa owner-nya?
Berapa lama lifecycle-nya?
Siapa yang boleh mengubahnya?
Apakah data ini canonical atau derived?
Apakah sumber kebenarannya ada di client, URL, server, browser, atau external system?
Bagaimana invalidation-nya?
Apa failure mode paling mahal jika state ini salah tempat?

Banyak codebase React rusak bukan karena kekurangan library, tetapi karena semua jenis state diperlakukan sama.

UI toggle disimpan di global store.
Server response disimpan di context.
Form draft disamakan dengan entity canonical.
Permission diperlakukan sebagai boolean lokal.
URL filter disimpan hanya di component state.
Async mutation diperlakukan sebagai satu flag isLoading.
Workflow multi-step disusun dari 11 useState tanpa transition invariant.

Part ini membangun taxonomy. Tujuannya agar setiap state bisa ditempatkan secara defensible.


1. Definisi State di React

Dalam React, state adalah informasi yang memengaruhi output render sebuah component atau subtree.

Tetapi dalam arsitektur aplikasi, definisi itu belum cukup.

Kita butuh definisi operasional:

State adalah fakta sementara atau canonical yang memengaruhi UI,
memiliki owner,
lifecycle,
aturan update,
aturan invalidation,
dan konsekuensi ketika nilainya salah.

Beda jenis state punya konsekuensi berbeda.

Modal open salah -> annoying.
Permission salah -> security bug.
Cache server basi -> data integrity risk.
Form draft hilang -> user trust issue.
Workflow stuck -> business process failure.
URL state tidak sinkron -> shareability/back-navigation rusak.

Jadi taxonomy bukan teori. Ini alat risk management.


2. High-Level State Map

Setiap bucket punya rule berbeda.

Local state       -> dekatkan ke component yang memakai.
Shared state      -> naikkan ke owner bersama atau scoped store.
Derived state     -> hitung dari source, jangan duplikasi.
Server state      -> cache, revalidate, invalidate.
URL state         -> jadikan alamat UI yang shareable.
Persistent state  -> simpan hati-hati, versioning, privacy.
External state    -> subscribe dengan contract aman.
Workflow state    -> modelkan sebagai transition graph.
Capability state  -> pass sebagai dependency, bukan data volatile.
Ephemeral state   -> jangan naikkan jika tidak perlu.

3. Taxonomy Table

Jenis StateSource of TruthOwner UmumUpdate OlehLifecycleTool Umum
Local UI stateComponentComponentEvent handler lokalSelama component mounteduseState, useReducer
Derived stateState/props lainTidak canonicalRender calculation/selectorMengikuti sourceexpression, selector, useMemo jika perlu
Shared client stateClientCommon parent / scoped provider / storeDomain commandSelama screen/app aktiflifted state, context+reducer, Zustand/Redux
URL stateBrowser URL/routerRouteNavigation/update paramsPersist via historyrouter search params
Server stateServerServer, mirrored by cacheFetch/mutation/invalidationTergantung cache policyTanStack Query, SWR, framework cache
Persistent browser stateBrowser storageBrowser/client appPersistence adapterAcross reload/sessionlocalStorage, sessionStorage, IndexedDB
External system stateNon-React systemExternal sourceExternal emitter/storeOutside React lifecycleuseSyncExternalStore
Workflow stateProcess/UI machineWorkflow ownerEvents/transitionsSampai workflow selesaireducer, state machine, XState
Capability/dependency stateProvider/config/envApp/scope providerRare updateScope lifetimecontext, DI provider
Ephemeral interaction stateInteractionComponent/DOMPointer/keyboard/timerVery shortref, local state, browser API

4. Local UI State

Local state adalah state yang hanya relevan untuk satu component atau subtree kecil.

Contoh:

isPopoverOpen
activeTab dalam isolated Tabs
input draft lokal
hover/focus visual state
expanded row dalam table kecil
selected option dalam uncontrolled widget

Rule utama:

Jika state hanya dibaca dan diubah oleh satu component/subtree kecil,
jangan naikkan.

Local state sehat karena:

scope kecil,
rerender terbatas,
owner jelas,
test sederhana,
removal mudah,
dan tidak menciptakan coupling global.

Contoh

function Disclosure() {
  const [open, setOpen] = useState(false);

  return (
    <section>
      <button onClick={() => setOpen(value => !value)}>
        {open ? 'Hide' : 'Show'} details
      </button>
      {open ? <p>Advanced details...</p> : null}
    </section>
  );
}

Ini tidak butuh Context, Zustand, Redux, URL, atau query cache.

Failure Mode

// Buruk: state lokal dipromosikan ke global store tanpa alasan.
const open = useAppStore(state => state.sidebar.open);
const setOpen = useAppStore(state => state.sidebar.setOpen);

Jika sidebar.open hanya dipakai oleh satu layout shell, global store membuat state terlalu terlihat.

Visibility yang terlalu besar meningkatkan coupling.
Coupling meningkatkan biaya refactor.

5. Derived State

Derived state adalah nilai yang bisa dihitung dari source lain.

Contoh:

fullName = firstName + lastName
visibleItems = items filtered by query
isSubmitDisabled = !isValid || isPending
selectedItem = items.find(id)
totalPrice = cartLines sum

Rule utama:

Jika bisa dihitung secara deterministic dari state/props yang sudah ada,
jangan simpan sebagai state canonical.

Buruk

function CartSummary({ lines }: { lines: CartLine[] }) {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    setTotal(lines.reduce((sum, line) => sum + line.price * line.qty, 0));
  }, [lines]);

  return <strong>{total}</strong>;
}

Masalah:

render pertama punya total lama/default,
ada extra render,
source of truth dobel,
effect dipakai untuk kalkulasi internal,
dan invariant total == sum(lines) bisa pecah sementara.

Baik

function CartSummary({ lines }: { lines: CartLine[] }) {
  const total = lines.reduce((sum, line) => sum + line.price * line.qty, 0);

  return <strong>{total}</strong>;
}

Jika kalkulasi mahal:

const total = useMemo(() => computeExpensiveTotal(lines), [lines]);

Tetapi useMemo adalah cache, bukan state canonical.


6. Shared Client State

Shared client state dibutuhkan ketika beberapa component harus membaca/mengubah state yang sama.

Contoh:

selected rows di table + toolbar + bulk action dialog
wizard progress dibaca header/sidebar/content
draft filter dipakai filter panel + result summary
layout sidebar state dipakai shell + keyboard shortcut
cart lokal sebelum checkout

Pertanyaan penting:

Apakah state harus shared karena memang domain-nya shared,
atau karena component tree saat ini kebetulan menyulitkan props?

Jika hanya props drilling kecil, composition bisa cukup. Jika banyak node unrelated butuh read/write, gunakan provider/store scoped.

Shared State Options

Lifted State

function SearchPage() {
  const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());

  return (
    <>
      <ResultToolbar selectedCount={selectedIds.size} onClear={() => setSelectedIds(new Set())} />
      <ResultTable selectedIds={selectedIds} onSelectionChange={setSelectedIds} />
    </>
  );
}

Ini tepat jika owner bersama dekat dan tidak membuat parent terlalu gemuk.


7. Server State

Server state adalah data yang sumber kebenarannya ada di server.

Contoh:

user profile dari API
invoice list dari backend
permission policy dari auth service
case details dari regulatory case service
notification count dari server

Server state bukan client state.

Perbedaannya:

DimensiClient StateServer State
Owner canonicalBrowser/clientServer
UpdateEvent lokalMutation/network
FreshnessSelalu sesuai ownerBisa stale
ConflictJarangMungkin
InvalidationBiasanya tidak perluWajib dipikirkan
PersistenceOptionalDi server
FailureLogic/UI bugNetwork, auth, concurrency, conflict

Buruk

const UserContext = createContext<User | null>(null);

Lalu semua user profile API disimpan manual di context.

Masalah:

cache policy tidak jelas,
stale time tidak jelas,
refetch tidak standar,
mutation invalidation manual,
loading/error tersebar,
duplicate fetch mudah terjadi.

Lebih Baik

Gunakan server-state cache abstraction.

function useUserProfile(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserProfile(userId),
    staleTime: 60_000,
  });
}

Context boleh menyimpan capability seperti currentUserId atau authSession, tetapi entity server tetap sebaiknya melalui cache yang punya lifecycle.


8. URL State

URL state adalah state yang menjadi bagian dari alamat UI.

Contoh:

/search?q=react&page=2&sort=recent
/cases?status=open&assignee=me
/orders?tab=failed
/invoices/123?panel=history

URL state tepat ketika state perlu:

shareable,
bookmarkable,
back/forward-aware,
refresh-safe,
deep-linkable,
atau menjadi identity query server.

Salah Tempat

const [page, setPage] = useState(1);
const [sort, setSort] = useState('recent');
const [query, setQuery] = useState('');

Untuk search page production, ini membuat:

refresh reset filter,
copy URL tidak membawa context,
back button tidak bekerja benar,
analytics/navigation sulit,
query cache key terpisah dari route identity.

Lebih Tepat

function useSearchRouteState() {
  const [params, setParams] = useSearchParams();

  return {
    query: params.get('q') ?? '',
    page: Number(params.get('page') ?? 1),
    sort: params.get('sort') ?? 'recent',
    setQuery(query: string) {
      setParams(previous => {
        const next = new URLSearchParams(previous);
        next.set('q', query);
        next.set('page', '1');
        return next;
      });
    },
  };
}

URL adalah state container dengan semantics sendiri: serialization, history, navigation, dan shareability.


9. Persistent Browser State

Persistent browser state hidup melewati reload.

Contoh:

theme preference
sidebar collapsed preference
last selected workspace
draft offline form
feature onboarding dismissed
local-only queue

Rule utama:

Persist hanya state yang benar-benar boleh hidup melewati reload/session.

Pertanyaan keamanan:

Apakah data sensitif?
Apakah boleh dibaca extension/user lain?
Apakah ada expiry?
Apakah schema berubah antar versi?
Apakah data terkait user tertentu?
Apakah perlu clear saat logout?

Adapter Pattern

Jangan taburkan localStorage di banyak component.

function createJsonStorage<T>(key: string, fallback: T) {
  return {
    read(): T {
      const raw = window.localStorage.getItem(key);
      if (!raw) return fallback;
      try {
        return JSON.parse(raw) as T;
      } catch {
        return fallback;
      }
    },
    write(value: T) {
      window.localStorage.setItem(key, JSON.stringify(value));
    },
  };
}

Lalu bungkus dalam hook/provider yang jelas.


10. External Store State

External store state adalah state yang hidup di luar React tetapi UI perlu subscribe.

Contoh:

browser online/offline status
media query store
WebSocket connection state
legacy observable store
custom event emitter
shared worker state
multi-tab broadcast channel state
Zustand/Redux store

External store butuh contract:

getSnapshot harus mengembalikan snapshot konsisten.
subscribe harus cleanup.
React harus tahu kapan snapshot berubah.
SSR snapshot harus bisa disediakan jika perlu.

Pattern React modern untuk external subscription adalah useSyncExternalStore.

function subscribe(callback: () => void) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);

  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}

function getSnapshot() {
  return navigator.onLine;
}

function useOnlineStatus() {
  return useSyncExternalStore(subscribe, getSnapshot, () => true);
}

Jangan pakai useEffect + useState untuk semua external source jika consistency dan concurrent rendering matters.


11. Workflow State

Workflow state adalah state yang merepresentasikan proses.

Contoh:

idle -> editing -> validating -> submitting -> succeeded
idle -> loading -> loaded -> refreshing -> failed
draft -> pendingApproval -> approved -> rejected
selecting -> confirming -> executing -> completed

Workflow state bukan sekadar banyak boolean.

Buruk

const [isLoading, setLoading] = useState(false);
const [isSubmitting, setSubmitting] = useState(false);
const [isSuccess, setSuccess] = useState(false);
const [error, setError] = useState<Error | null>(null);

Illegal state mudah muncul:

isLoading = true
isSuccess = true
error != null
isSubmitting = true

Lebih Baik

type SaveState =
  | { status: 'idle' }
  | { status: 'saving' }
  | { status: 'saved'; savedAt: Date }
  | { status: 'failed'; error: Error };

Workflow state perlu event dan transition.

type SaveEvent =
  | { type: 'submit' }
  | { type: 'resolve'; savedAt: Date }
  | { type: 'reject'; error: Error }
  | { type: 'retry' };

Reducer atau state machine cocok untuk workflow.


12. Capability / Dependency State

Capability state bukan data domain volatile. Ini dependency yang component butuhkan untuk melakukan sesuatu.

Contoh:

analytics.track
permission.can
modal.open
toast.show
logger.error
clock.now
idGenerator.next
featureFlags.isEnabled
apiClient

Capability cocok dengan Context karena:

subtree butuh dependency,
value jarang berubah,
provider scope eksplisit,
test bisa override dependency,
dan consumer tidak perlu prop drilling capability ke semua lapisan.

Contoh

type PermissionCapability = {
  can(action: string, resource: string): boolean;
};

const PermissionContext = createContext<PermissionCapability | null>(null);

function usePermission() {
  const value = useContext(PermissionContext);
  if (!value) throw new Error('usePermission must be used inside PermissionProvider');
  return value;
}

Capability berbeda dari state permission entity server.

Policy data -> server/cache state.
can() API   -> capability interface.

13. Ephemeral Interaction State

Ephemeral state sangat pendek lifecycle-nya.

Contoh:

pointer down coordinates
drag hover target
last focused element
animation frame id
temporary timeout id
composing IME status
current AbortController

Banyak ephemeral state tidak perlu render.

Jika tidak memengaruhi JSX, gunakan ref.

function DragBox() {
  const startRef = useRef<{ x: number; y: number } | null>(null);

  function onPointerDown(event: React.PointerEvent) {
    startRef.current = { x: event.clientX, y: event.clientY };
  }

  function onPointerUp() {
    startRef.current = null;
  }

  return <div onPointerDown={onPointerDown} onPointerUp={onPointerUp} />;
}

Jika state memengaruhi render, gunakan state.

const [dragging, setDragging] = useState(false);

14. State Ownership Matrix

PertanyaanJawabanImplikasi
Siapa yang paling tahu kapan state berubah?Component lokallocal state
Siapa yang harus menjaga invariant?Parent/domain boundarylift/reducer
Apakah server adalah source of truth?Yaquery/cache
Apakah URL harus mewakili state?Yaroute/search params
Apakah value hanya dependency?Yacontext capability
Apakah data datang dari non-React store?Yaexternal subscription
Apakah banyak boolean mewakili proses?Yareducer/state machine
Apakah value hanya cache derivasi?Yaselector/useMemo
Apakah harus survive reload?Yapersistence adapter
Apakah hanya dibutuhkan selama pointer/timer?Yaref/local ephemeral

15. Lifecycle Matrix

LifecycleState Type CandidateContoh
selama event sajalocal variablevalue hasil parse dalam handler
selama render sajaderived calculationisDisabled
selama component mountedlocal state/refpopover open
selama screen aktiflifted/context/reducerselected rows
selama route aktifURL + query cachesearch filters
selama app sessionscoped store/session storageworkspace preference
melewati reloadpersistent storagetheme
controlled serverserver cacheinvoice details
controlled external sourceexternal storeonline status
controlled processstate machineapproval workflow

16. Invalidation Matrix

State yang bagus punya invalidation rule.

State TypeInvalidation Trigger
Local UIunmount, reset action, key change
Derivedsource berubah
Shared clientdomain event/command
URLnavigation/history update
Serverstale time, mutation success, focus reconnect, explicit invalidate
Persistentschema version, logout, expiry, user switch
Externalexternal emitter/store update
Workflowtransition event, terminal state
Capabilityprovider scope change
Ephemeralpointer up, timeout, cleanup

Jika invalidation tidak bisa dijelaskan, state kemungkinan salah tempat.


17. Canonical vs Cached vs Derived

Tiga hal ini sering tercampur.

Canonical = sumber kebenaran.
Cached    = salinan untuk performa/freshness boundary.
Derived   = hasil hitung dari canonical/cached.

Contoh order list:

Server DB          -> canonical orders
TanStack Query     -> cached server state
visibleOrders      -> derived filtered list
selectedOrderIds   -> client state
page/sort/filter   -> URL state

Jangan jadikan semua canonical di client.


18. Example: Search Page Taxonomy

Taxonomy:

ConcernState TypeOwner
q, page, sortURL stateroute
result dataserver stateserver/cache
selected IDsshared client statepage boundary
input currently being typedlocal draftsearch box
permission checkcapabilityprovider
loading/errorquery lifecyclequery cache

19. Example: Approval Workflow UI

Taxonomy:

ConcernState Type
case dataserver state
editable draftlocal/shared client state
validation resultderived/workflow state
submit lifecycleworkflow state
permission to approvecapability + server policy cache
current route tabURL state
toast/modalcapability/context + local overlay state

20. Decision Tree


21. Smell Catalog

Smell 1 — Everything in Context

Context contains user, permissions, invoices, filters, modal, toast,
selected IDs, loading flags, mutation errors, and form draft.

Diagnosis:

taxonomy missing.

Refactor:

server entities -> query cache
capabilities -> context
page filters -> URL
selection -> page/local state
workflow -> reducer/machine

Smell 2 — Everything in URL

URL bagus untuk shareability, tapi buruk untuk high-frequency ephemeral changes.

Jangan masukkan pointer position, hover, intermediate keystroke draft, atau internal menu open state ke URL.

Smell 3 — Everything in Global Store

Global store memperbesar visibility state.

Gunakan untuk state yang benar-benar global atau cross-cutting dengan value proposition jelas:

observability,
time travel,
cross-route coordination,
external access,
large shared client model,
team governance.

Smell 4 — Effect as State Copier

useEffect(() => {
  setLocalValue(props.value);
}, [props.value]);

Sering berarti ownership tidak jelas.


22. State Review Checklist

Gunakan ini saat code review.

[ ] State ini canonical, cached, derived, atau ephemeral?
[ ] Source of truth-nya jelas?
[ ] Owner-nya satu?
[ ] Update path-nya eksplisit?
[ ] Invalidation rule-nya tertulis?
[ ] Lifecycle-nya sesuai storage/hook/tool?
[ ] State ini perlu URL/shareability?
[ ] State ini server-owned?
[ ] State ini sebenarnya capability/dependency?
[ ] State ini bisa dihitung dari data lain?
[ ] Ada illegal state yang mungkin muncul?
[ ] Ada effect yang hanya menyalin state internal?
[ ] Ada hidden dependency lewat context/store?
[ ] Ada risiko stale cache/security/data integrity?

23. Practical Heuristics

Heuristic A — Prefer Smallest Correct Scope

Letakkan state di scope terkecil yang masih bisa menjaga invariant.

Bukan selalu lokal. Bukan selalu global.

Heuristic B — Owner Should Match Mutation Authority

State harus dimiliki oleh boundary yang punya hak menentukan transisinya.

Jika child hanya mengirim intent,
parent/domain boundary memutuskan next state.

Heuristic C — Source of Truth Beats Convenience

Jangan memilih store karena nyaman. Pilih berdasarkan source of truth.

Heuristic D — URL Is Product Surface

Jika user menganggap kondisi UI sebagai “halaman”, maka URL harus mewakilinya.

Heuristic E — Server State Needs Cache Semantics

Jika data datang dari server dan bisa stale, perlu cache semantics.

Heuristic F — Workflow Needs Transition Semantics

Jika ada proses, jangan pakai boolean soup.


24. Mini Lab: Classify State

Untuk setiap state di bawah ini, tentukan taxonomy.

1. `isTooltipOpen`
2. `currentUser.profile`
3. `invoiceSearch.sort`
4. `checkoutStep`
5. `draftComment`
6. `onlineStatus`
7. `themePreference`
8. `canApproveCase`
9. `selectedTableRows`
10. `modalService.openConfirm`

Jawaban defensible:

StateTaxonomy
isTooltipOpenephemeral/local UI
currentUser.profileserver state cache
invoiceSearch.sortURL state jika shareable, local jika isolated
checkoutStepworkflow state
draftCommentlocal/persistent draft tergantung requirement
onlineStatusexternal store/browser state
themePreferencepersistent preference + capability to design system
canApproveCasederived from permission capability and server policy data
selectedTableRowsshared page/client state
modalService.openConfirmcapability command + overlay local state

25. Summary

State taxonomy adalah kemampuan arsitektural dasar React production.

Intinya:

Jangan mulai dari tool.
Mulai dari jenis state.

Taxonomy yang sehat membuat keputusan tool menjadi jelas.

Local UI state        -> useState/useReducer/ref.
Derived state         -> calculate/select.
Shared client state   -> lift/context/scoped store.
Server state          -> query/cache/invalidation.
URL state             -> router/search params.
Persistent state      -> storage adapter with versioning.
External state        -> useSyncExternalStore/subscription.
Workflow state        -> reducer/state machine.
Capability state      -> context/DI provider.
Ephemeral state       -> ref/local short lifecycle.

Part berikutnya akan memperdalam satu bucket pertama: Local State Design.

Lesson Recap

You just completed lesson 49 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.