Deepen PracticeOrdered learning track

State Machine Thinking for React

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

Memahami state machine thinking untuk React: finite states, events, transitions, guards, actions, effects, statecharts, hierarchy, parallel states, dan kapan reducer harus naik kelas menjadi machine.

9 min read1710 words
PrevNext
Lesson 88123 lesson track68–101 Deepen Practice
#react#hooks#state-machine#statecharts+3 more

Part 088 — State Machine Thinking for React

Part sebelumnya membahas workflow state sebagai fase perjalanan UI. Part ini menambahkan bahasa yang lebih presisi: state machine.

State machine thinking bukan berarti semua UI harus memakai library state machine. Ini adalah cara berpikir untuk membuat behavior eksplisit.

Tujuannya sederhana:

Tidak semua event valid dari semua state.
Tidak semua kombinasi state legal.
Tidak semua side effect boleh terjadi kapan saja.

State machine membuat tiga hal terlihat:

state sekarang
 event yang diterima
state berikutnya yang legal

Di React, state machine thinking sangat berguna ketika UI memiliki:

  • multi-step workflow;
  • async command lifecycle;
  • retry/cancel/undo;
  • permission guard;
  • modal/overlay orchestration;
  • form submission lifecycle;
  • optimistic update;
  • parallel task;
  • nested sub-flow;
  • long-lived process yang harus bisa di-debug.

1. Reducer vs State Machine

Reducer adalah function:

(state, event) => nextState

State machine adalah model yang membatasi:

Dari state S, event E boleh/tidak boleh terjadi.
Jika boleh, next state harus T.

Semua state machine bisa diimplementasikan dengan reducer. Tetapi tidak semua reducer mengekspresikan state machine dengan jelas.

Reducer longgar:

function reducer(state: State, event: Event): State {
  switch (event.type) {
    case 'SUBMIT':
      return { ...state, isSubmitting: true };
    case 'SUCCESS':
      return { ...state, isSubmitting: false, isSuccess: true };
    case 'FAIL':
      return { ...state, isSubmitting: false, error: event.error };
    default:
      return state;
  }
}

State machine reducer:

function reducer(state: State, event: Event): State {
  switch (state.tag) {
    case 'editing':
      switch (event.type) {
        case 'SUBMIT':
          return { tag: 'submitting', draft: state.draft, requestId: event.requestId };
        default:
          return state;
      }

    case 'submitting':
      switch (event.type) {
        case 'SUCCESS':
          if (event.requestId !== state.requestId) return state;
          return { tag: 'submitted', id: event.id };
        case 'FAIL':
          if (event.requestId !== state.requestId) return state;
          return { tag: 'failed', draft: state.draft, error: event.error };
        default:
          return state;
      }
  }
}

Perbedaan utamanya: machine reducer menjadikan state saat ini sebagai pintu masuk utama.


2. Finite State

Finite state berarti jumlah state legal terbatas dan bisa disebutkan.

Contoh upload:

empty
selected
validating
invalid
uploading
uploaded
failed

Jika Anda tidak bisa menyebutkan daftar state legal, berarti behavior UI masih implicit.

Finite tidak berarti kecil. Artinya bounded.

Aplikasi besar boleh punya banyak workflow.
Setiap workflow harus punya state space yang bisa dimengerti.

3. Events

Event adalah sesuatu yang terjadi terhadap machine.

type Event =
  | { type: 'FILE_SELECTED'; file: File }
  | { type: 'VALIDATION_PASSED' }
  | { type: 'VALIDATION_FAILED'; reason: string }
  | { type: 'UPLOAD_STARTED'; requestId: string }
  | { type: 'UPLOAD_SUCCEEDED'; requestId: string; assetId: string }
  | { type: 'UPLOAD_FAILED'; requestId: string; error: string }
  | { type: 'CANCEL' }
  | { type: 'RETRY'; requestId: string };

Event harus dinamai berdasarkan makna, bukan detail UI.

Buruk:

BUTTON_CLICKED
INPUT_CHANGED
MODAL_CLOSED

Lebih baik:

SUBMIT_REQUESTED
DECISION_DRAFT_CHANGED
REVIEW_CANCELLED

Tentu ada konteks. Untuk komponen primitive, onOpenChange masuk akal. Untuk workflow domain, SUBMIT_DECISION_REQUESTED lebih jelas.


4. Transitions

Transition adalah edge dari state ke state lain.

Machine bukan hanya daftar state. Machine adalah graph.

Pertanyaan inti:

Dari state ini, event apa yang valid?
Jika event valid, masuk ke state apa?
Jika event tidak valid, apa yang harus terjadi?

Ada tiga policy untuk invalid event:

PolicyCocok untuk
IgnoreUI event yang harmless, seperti double click saat disabled
Throw in developmentWorkflow invariant violation
Convert to explicit errorDomain/process violation yang perlu ditampilkan

Production biasanya ignore untuk event user yang harmless, tapi log invariant violation di development.


5. Guards

Guard adalah condition yang menentukan transition boleh terjadi atau tidak.

function canSubmit(state: WorkflowState): boolean {
  return state.tag === 'editing'
    && isValidDraft(state.draft)
    && state.permissions.canSubmit;
}

Transition:

case 'editing':
  if (event.type === 'SUBMIT_REQUESTED') {
    if (!canSubmit(state)) {
      return { tag: 'validationFailed', draft: state.draft, issues: validateDraft(state.draft) };
    }

    return {
      tag: 'submitting',
      draft: state.draft,
      requestId: event.requestId,
    };
  }

Guard harus pure. Jangan call API di guard. Guard menjawab boleh/tidak berdasarkan snapshot yang sudah tersedia.


6. Actions vs Effects

Dalam state machine literature, action sering berarti kerja yang terjadi saat transition. Di React, kita harus hati-hati membedakan:

pure transition action = menghitung next state
side effect action     = call API, log analytics, navigate, set timer

Untuk React reducer, side effect tidak boleh berada di reducer.

Gunakan boundary:

UI event handler → dispatch event → reducer calculates state
command/effect boundary → external system → dispatch result event

Diagram:


7. Entry and Exit Actions

State machines sering punya konsep:

entry action = saat masuk state
exit action  = saat keluar state

Di React manual implementation, ini biasanya dilakukan dengan effect yang menonton state phase.

useEffect(() => {
  if (state.tag !== 'submitting') return;

  const controller = new AbortController();

  submitDecision(state.draft, { signal: controller.signal })
    .then((result) => dispatch({ type: 'SUBMIT_SUCCEEDED', requestId: state.requestId, id: result.id }))
    .catch((error) => {
      if (controller.signal.aborted) return;
      dispatch({ type: 'SUBMIT_FAILED', requestId: state.requestId, error: normalizeError(error) });
    });

  return () => controller.abort();
}, [state]);

Ini valid jika submitting benar-benar berarti “sedang sinkron dengan external system”. Tetapi tetap hati-hati:

  • dependency harus benar;
  • cleanup harus idempotent;
  • Strict Mode development bisa menjalankan setup/cleanup ekstra;
  • stale response harus dicegah dengan request id.

8. Statechart: State Machine yang Lebih Kuat

Finite state machine datar cukup untuk workflow kecil. Untuk workflow kompleks, statechart menambahkan:

  • nested/hierarchical states;
  • parallel/orthogonal states;
  • history state;
  • delayed transitions;
  • invoked services/actors;
  • event communication.

Statecharts dipopulerkan oleh David Harel sebagai visual formalism untuk complex systems. Ide besarnya: state diagrams biasa cepat meledak ketika butuh hierarchy dan concurrency; statecharts memperkenalkan clustering/hierarchy, orthogonality/concurrency, dan communication.

Contoh explosion tanpa hierarchy:

editingClean
editingDirty
editingSubmitting
reviewClean
reviewDirty
reviewSubmitting

Dengan hierarchy:

form
  editing
    clean
    dirty
    submitting
  review
    clean
    dirty
    submitting

Diagram:


9. Hierarchical State

Hierarchical state mengurangi duplikasi behavior.

Contoh: semua substate dalam authenticated boleh LOGOUT.

Tanpa hierarchy:

Dashboard + LOGOUT → unauthenticated
Settings + LOGOUT → unauthenticated
Billing + LOGOUT → unauthenticated
Profile + LOGOUT → unauthenticated

Dengan hierarchy:

authenticated
  dashboard
  settings
  billing
  profile

LOGOUT dari authenticated → unauthenticated

Manual React implementation bisa dilakukan dengan nested union:

type AppState =
  | { tag: 'unauthenticated' }
  | {
      tag: 'authenticated';
      user: User;
      screen:
        | { tag: 'dashboard' }
        | { tag: 'settings' }
        | { tag: 'billing' };
    };

Reducer:

function reducer(state: AppState, event: Event): AppState {
  if (state.tag === 'authenticated' && event.type === 'LOGOUT') {
    return { tag: 'unauthenticated' };
  }

  switch (state.tag) {
    case 'unauthenticated':
      // auth events
      return state;
    case 'authenticated':
      return reduceAuthenticated(state, event);
  }
}

Hierarchy harus dipakai untuk mengurangi duplication, bukan untuk membuat model makin sulit dibaca.


10. Parallel State

Parallel state berarti beberapa region aktif bersamaan.

Contoh editor:

Document region:
  clean | dirty | saving | saveFailed
Connection region:
  online | offline | reconnecting
Presence region:
  idle | active | away

Jika dipaksa ke flat state, kombinasi meledak:

cleanOnlineIdle
cleanOnlineActive
cleanOfflineIdle
savingOnlineActive
saveFailedReconnectingAway
...

Dengan parallel regions:

Manual React representation:

type EditorState = {
  document: DocumentRegion;
  connection: ConnectionRegion;
  presence: PresenceRegion;
};

Reducer delegates:

function editorReducer(state: EditorState, event: EditorEvent): EditorState {
  return {
    document: reduceDocument(state.document, event),
    connection: reduceConnection(state.connection, event),
    presence: reducePresence(state.presence, event),
  };
}

Caveat: event bisa mempengaruhi beberapa region. Pastikan invariant lintas-region dites.


11. History State

History state berarti kembali ke substate terakhir.

Contoh:

User sedang di wizard step 3.
Modal help dibuka.
Modal ditutup.
User kembali ke step 3, bukan step 1.

Manual representation:

type WizardState =
  | { tag: 'running'; step: Step; history?: never }
  | { tag: 'helpOpen'; previousStep: Step };

Transition:

case 'running':
  if (event.type === 'OPEN_HELP') {
    return { tag: 'helpOpen', previousStep: state.step };
  }
  return state;

case 'helpOpen':
  if (event.type === 'CLOSE_HELP') {
    return { tag: 'running', step: state.previousStep };
  }
  return state;

Jangan simpan history jika tidak ada requirement. History menambah kompleksitas dan potensi stale reference.


12. Actor Thinking

Actor adalah unit behavior independen yang:

  • punya state sendiri;
  • menerima event/message;
  • bisa mengirim event/message;
  • punya lifecycle;
  • bisa spawn child actor;
  • bisa dihentikan.

Untuk React UI, actor thinking berguna saat satu screen punya banyak workflow kecil independen:

  • setiap upload item;
  • setiap chat conversation;
  • setiap background job row;
  • setiap document editor tab;
  • setiap modal instance;
  • setiap workflow case card.

Contoh:

UploadManager actor
  ├── UploadItem actor #1
  ├── UploadItem actor #2
  └── UploadItem actor #3

Manual React implementation bisa berupa store map:

type UploadActorState = {
  id: string;
  state: UploadWorkflow;
};

type UploadManagerState = {
  actors: Record<string, UploadActorState>;
};

Tetapi setelah actor lifecycle dan event routing makin kompleks, library seperti XState/Stately mulai masuk akal. XState v5 memodelkan logic dengan state machines dan actors, serta bisa dipakai lintas React/Vue/Svelte/backend.


13. Machine Config from Scratch

Sebelum memakai library, kita bisa membangun mini machine config untuk memahami ide dasarnya.

type MachineConfig<State extends string, Event extends string> = {
  initial: State;
  states: Record<State, {
    on?: Partial<Record<Event, State>>;
  }>;
};

const uploadMachine = {
  initial: 'empty',
  states: {
    empty: {
      on: { FILE_SELECTED: 'selected' },
    },
    selected: {
      on: { VALIDATE: 'validating', RESET: 'empty' },
    },
    validating: {
      on: { VALID: 'uploading', INVALID: 'invalid' },
    },
    invalid: {
      on: { FILE_SELECTED: 'selected', RESET: 'empty' },
    },
    uploading: {
      on: { UPLOAD_OK: 'uploaded', UPLOAD_FAILED: 'failed' },
    },
    failed: {
      on: { RETRY: 'uploading', RESET: 'empty' },
    },
    uploaded: {
      on: { RESET: 'empty' },
    },
  },
} as const;

Interpreter minimal:

function transition<State extends string, Event extends string>(
  config: MachineConfig<State, Event>,
  state: State,
  event: Event,
): State {
  return config.states[state].on?.[event] ?? state;
}

Hook:

function useMachine<State extends string, Event extends string>(
  config: MachineConfig<State, Event>,
) {
  const [state, setState] = useState(config.initial);

  const send = useCallback((event: Event) => {
    setState((current) => transition(config, current, event));
  }, [config]);

  return [state, send] as const;
}

Ini hanya untuk belajar. Production machine butuh context, guard, action, invoked service, delayed transition, actor, devtools, serialization, testing, dan type inference lebih kuat.


14. Machine with Context

State tag tidak cukup untuk data.

type UploadContext = {
  file?: File;
  assetId?: string;
  error?: string;
  attempts: number;
};

Machine state:

type MachineState = {
  value: 'empty' | 'selected' | 'uploading' | 'uploaded' | 'failed';
  context: UploadContext;
};

Transition bisa mengubah state value dan context.

function reducer(state: MachineState, event: UploadEvent): MachineState {
  switch (state.value) {
    case 'empty':
      if (event.type === 'FILE_SELECTED') {
        return { value: 'selected', context: { file: event.file, attempts: 0 } };
      }
      return state;

    case 'selected':
      if (event.type === 'UPLOAD') {
        return { value: 'uploading', context: state.context };
      }
      return state;

    case 'uploading':
      if (event.type === 'UPLOAD_OK') {
        return { value: 'uploaded', context: { ...state.context, assetId: event.assetId } };
      }
      if (event.type === 'UPLOAD_FAIL') {
        return {
          value: 'failed',
          context: {
            ...state.context,
            error: event.error,
            attempts: state.context.attempts + 1,
          },
        };
      }
      return state;

    default:
      return state;
  }
}

Alternative union state lebih TypeScript-safe untuk data yang hanya valid di state tertentu. Machine context lebih cocok untuk library machine/statechart yang memisahkan value dan context.

Trade-off:

ApproachKelebihanRisiko
Discriminated unionData validity kuat per stateBisa verbose
Machine value + contextMirip XState, mudah introspectContext bisa punya optional field liar
HybridValue + typed context per stateButuh type design lebih serius

15. Guards in Config

Mini config dengan guard:

type Transition<S, C, E> = {
  target: S;
  guard?: (context: C, event: E) => boolean;
};

Contoh:

const submitTransition = {
  target: 'submitting',
  guard: (ctx: FormContext) => isValid(ctx.draft) && ctx.permissions.canSubmit,
};

Guard harus:

pure
synchronous
based on current context/event
free from side effects

Jika butuh server validation, itu bukan guard lokal. Itu invoked service/async command yang menghasilkan event SERVER_VALIDATION_PASSED atau SERVER_VALIDATION_FAILED.


16. Actions in Config

Transition action bisa dipakai untuk update context atau emit internal event.

type Assign<C, E> = (context: C, event: E) => C;

Contoh:

const assignError = (ctx: UploadContext, event: UploadEvent): UploadContext => {
  if (event.type !== 'UPLOAD_FAIL') return ctx;
  return { ...ctx, error: event.error, attempts: ctx.attempts + 1 };
};

Prinsip production:

Pure assign/action boleh di transition calculation.
Impure side effect harus di boundary lain.

17. Invoked Service

Invoked service adalah external work yang dimulai saat masuk state tertentu.

Contoh konseptual:

state: submitting
invoke: submitDecision
onDone: submitted
onError: failed

Manual React:

useEffect(() => {
  if (state.tag !== 'submitting') return;

  const controller = new AbortController();
  const { requestId, draft } = state;

  submitDecision(draft, { signal: controller.signal })
    .then((result) => dispatch({ type: 'SUBMIT_DONE', requestId, result }))
    .catch((error) => {
      if (!controller.signal.aborted) {
        dispatch({ type: 'SUBMIT_ERROR', requestId, error });
      }
    });

  return () => controller.abort();
}, [state]);

Dengan XState, konsep invoke sudah native. Tetapi memahami versi manual penting agar Anda tahu apa yang library abstraksikan.


18. Delayed Transition

Contoh snackbar:

visible → hidden after 5 seconds

Manual:

useEffect(() => {
  if (state.tag !== 'visible') return;

  const id = window.setTimeout(() => {
    dispatch({ type: 'TIMEOUT' });
  }, 5000);

  return () => window.clearTimeout(id);
}, [state]);

Machine concept:

after 5000ms → hidden

Delayed transition harus cleanup saat state keluar. Jika tidak, timeout lama bisa menembakkan event ke workflow baru.


19. UI as Projection of Machine State

Komponen React sebaiknya menjadi projection:

function SubmitPanel({ state, send }: Props) {
  switch (state.tag) {
    case 'editing':
      return <EditForm draft={state.draft} onSubmit={() => send({ type: 'SUBMIT' })} />;

    case 'submitting':
      return <PendingPanel message="Submitting decision..." />;

    case 'failed':
      return <ErrorPanel error={state.error} onRetry={() => send({ type: 'RETRY' })} />;

    case 'submitted':
      return <SuccessPanel decisionId={state.decisionId} />;
  }
}

JSX tidak boleh menjadi tempat utama workflow rule. JSX membaca state dan mengirim event.


20. State Machine and Accessibility

State machine bukan hanya untuk data. Accessibility behavior sering berbentuk state machine:

  • menu: closed/open, focused item, selected item;
  • combobox: idle/open/filtering/selecting;
  • dialog: closed/opening/open/closing;
  • tooltip: hidden/waiting/visible;
  • tabs: selected tab, focus roving;
  • drag and drop: idle/dragging/dropping/cancelled.

Contoh menu:

Jika accessibility primitive diimplementasikan hanya dengan boolean isOpen, edge case keyboard/focus biasanya bocor.


21. Machine Boundary in React Architecture

Letakkan machine pada boundary yang tepat.

BoundaryCocok untuk
Component-local hookmodal kecil, upload kecil, local wizard
Provider-scoped machinescreen workflow, multi-part form
External actor/storelong-lived workflow lintas route
Server-backed workflowproses bisnis yang authoritative di backend
Component library primitivemenu, tabs, dialog, combobox

Jangan menaruh semua machine global. State machine tetap punya ownership dan lifetime.


22. When Reducer Is Enough

Reducer cukup jika:

- state datar;
- transition sedikit;
- tidak ada nested/parallel state;
- side effect bisa dikelola di command handler;
- tidak perlu visualization/devtools machine;
- testing transition sederhana cukup.

Contoh:

editing → submitting → success/failed

useReducer dengan discriminated union sudah cukup.


23. When to Move to State Machine Library

Pertimbangkan library jika:

- workflow punya nested state;
- workflow punya parallel regions;
- banyak invoked async services;
- banyak delayed transitions;
- ada actor/child workflow;
- event routing kompleks;
- business stakeholder perlu visual model;
- transition testing manual mulai berat;
- team butuh shared formalism.

XState/Stately memberi bahasa untuk state machines, statecharts, dan actors; dokumentasi resminya menekankan event-driven programming, statecharts, actors, dan penggunaan lintas framework termasuk React.

Tetapi jangan gunakan library untuk menutupi model yang belum dipahami. Library memperjelas model yang benar; library tidak otomatis memperbaiki model yang kacau.


24. State Machine Does Not Replace Server Authority

UI state machine bukan source of truth domain final.

UI bisa memodelkan:

readyToSubmit
submitting
submittedLocally
serverRejected

Tetapi server tetap authority untuk:

apakah command valid
apakah permission valid
apakah entity version masih terbaru
apakah business rule terpenuhi

Jangan berpikir karena UI machine melarang illegal transition, server tidak perlu validasi. UI machine meningkatkan UX dan consistency. Server tetap menjaga system invariant.


25. State Machine and Optimistic UI

Optimistic UI adalah temporary branch.

State shape:

type LikeWorkflow =
  | { tag: 'ready'; liked: boolean }
  | { tag: 'optimistic'; previous: boolean; optimistic: boolean; requestId: string }
  | { tag: 'failed'; liked: boolean; error: string };

State machine membantu menjawab:

Apa yang terjadi jika user klik lagi saat optimistic request berjalan?
Apakah kita queue, ignore, replace, atau cancel?
Apa yang terjadi jika server response pertama datang setelah request kedua?

Tanpa machine thinking, optimistic UI mudah menjadi kumpulan patch yang tidak deterministik.


26. State Machine Testing

Transition table test:

const cases = [
  ['editing', 'SUBMIT', 'submitting'],
  ['submitting', 'SUCCESS', 'submitted'],
  ['submitting', 'FAIL', 'failed'],
  ['failed', 'RETRY', 'submitting'],
] as const;

for (const [from, event, to] of cases) {
  it(`${from} + ${event} -> ${to}`, () => {
    const state = makeState(from);
    const next = reducer(state, makeEvent(event));
    expect(next.tag).toBe(to);
  });
}

Invalid event test:

it('ignores stale success after retry creates a new request', () => {
  const state = {
    tag: 'submitting',
    draft,
    requestId: 'new-request',
  } as const;

  const next = reducer(state, {
    type: 'SUBMIT_SUCCEEDED',
    requestId: 'old-request',
    decisionId: 'd1',
  });

  expect(next).toBe(state);
});

Guard test:

it('does not submit when permission is missing', () => {
  const state = {
    tag: 'editing',
    draft: validDraft,
    permissions: { canSubmit: false },
  } as const;

  expect(canSubmit(state)).toBe(false);
});

State machine makes tests systematic because the graph is finite.


27. Mermaid as Design Artifact

Untuk workflow penting, gambar machine sebelum implementasi.

Diagram bukan dekorasi. Diagram harus sesuai dengan reducer/machine test.

Praktik yang baik:

Diagram → state union → event union → transition reducer → transition tests → component projection

28. Naming Rules

State names harus menyatakan kondisi, bukan aksi.

Baik:

editing
submitting
failed
submitted
waitingForConfirmation

Buruk:

submit
clickSubmit
validate
callApi

Event names harus menyatakan kejadian/intent.

Baik:

SUBMIT_REQUESTED
SUBMIT_CONFIRMED
SUBMIT_SUCCEEDED
SUBMIT_FAILED

Buruk:

SET_LOADING
SET_SUCCESS
HANDLE_CLICK

Action/command names harus menyatakan operasi.

submitDecision()
validateDraft()
cancelUpload()
restoreDeletedItem()

29. Failure Modes

29.1 Event Accepted from Wrong State

Gejala:

SUCCESS event mengubah idle menjadi submitted.

Penyebab:

Reducer switch by event first, bukan by state first.

Refactor:

Switch state first, event second.

29.2 Guard Only in UI

Gejala:

Button disabled, tapi keyboard shortcut tetap submit.

Refactor:

Guard di command/machine, UI hanya menampilkan guard result.

29.3 Side Effect in Reducer

Gejala:

Test flaky, Strict Mode aneh, transition tidak deterministic.

Refactor:

Reducer pure. Side effect di command handler/effect/invoked service.

29.4 Flat Machine Explosion

Gejala:

State names makin panjang dan kombinatorial.

Refactor:

Gunakan hierarchy atau parallel regions.

29.5 Context Blob

Gejala:

Machine punya context raksasa dengan banyak optional field.

Refactor:

Pindahkan data ke state-specific union atau pecah machine.

29.6 Machine Too Global

Gejala:

Modal kecil bergantung pada app-level machine dan sulit reuse.

Refactor:

Turunkan ownership. Machine harus punya lifetime yang tepat.

30. Decision Framework

Gunakan ini:

Apakah hanya state sederhana?
→ useState

Apakah update logic mulai bercabang tapi masih lokal?
→ useReducer + discriminated union

Apakah banyak child dalam subtree perlu akses workflow?
→ reducer + context

Apakah workflow punya nested/parallel state atau invoked services?
→ state machine/statechart

Apakah banyak instance workflow independen saling berkomunikasi?
→ actor model

Apakah state sebenarnya milik server?
→ server-state cache + command lifecycle

Jangan pilih tool dari popularitas. Pilih dari struktur masalah.


31. Mini Exercise

Modelkan workflow “review case decision”:

Requirement:

1. Case harus loaded sebelum review.
2. Reviewer bisa mulai draft decision.
3. Draft harus valid sebelum submit.
4. Submit butuh confirmation modal.
5. Server bisa accept, reject karena policy, atau gagal network.
6. Network failure bisa retry.
7. Policy rejection harus kembali ke editing dengan rejection details.
8. Response lama tidak boleh menimpa request baru.

Tugas:

  1. Buat Mermaid state diagram.
  2. Buat union state.
  3. Buat union event.
  4. Buat guard canSubmit.
  5. Buat reducer state-first.
  6. Buat test stale response.
  7. Jelaskan lifetime state: local, context, external store, atau server-backed.

32. Ringkasan

State machine thinking memberi React UI bahasa untuk behavior yang eksplisit.

Prinsip utama:

  • sebutkan finite states;
  • event harus bermakna;
  • transition harus legal dari state tertentu;
  • guard harus pure dan reusable;
  • reducer harus deterministic;
  • side effect harus di boundary;
  • request identity melindungi async workflow;
  • hierarchy mengurangi duplikasi;
  • parallel state mencegah state explosion;
  • actor model berguna untuk banyak workflow independen;
  • UI adalah projection dari machine state;
  • diagram dan tests harus mencerminkan graph yang sama.

State machine bukan silver bullet. Tetapi untuk workflow React yang kompleks, state machine thinking adalah perbedaan antara UI yang “kebetulan jalan” dan UI yang bisa dijelaskan, dites, di-debug, dan dievolusi.

Part berikutnya akan membangun mini state machine hook dari scratch agar konsep ini tidak berhenti sebagai teori.


Referensi

Lesson Recap

You just completed lesson 88 in deepen practice. 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.