Build CoreOrdered learning track

Sibling Communication

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

Sibling communication in React through shared owners, mediator parents, context, URL state, external stores, server cache, command channels, and failure-mode analysis.

8 min read1446 words
PrevNext
Lesson 40123 lesson track24–67 Build Core
#react#component-communication#sibling-communication#state-ownership+2 more

Part 040 — Sibling Communication

Sibling communication sering dibahas seolah-olah masalahnya adalah “bagaimana component A memanggil component B”. Itu framing yang kurang tepat.

Di React, sibling tidak perlu berkomunikasi langsung. Yang perlu terjadi adalah:

state or event from one sibling changes a shared owner
shared owner renders both siblings with a new snapshot

Dengan kata lain, sibling communication adalah masalah state ownership topology.

Jika dua sibling perlu sinkron, cari dulu:

state apa yang disinkronkan?
siapa pemilik state yang paling benar?
apakah state ini local, URL, server cache, external store, atau workflow state?
apakah perubahan sibling A adalah event, command, atau derived view?

Jawaban dari pertanyaan itu menentukan pola komunikasi.


1. Mental Model

Dua sibling tidak memiliki jalur langsung dalam data flow React.

Sibling A tidak “mengirim data ke sibling B”. Sibling A mengirim intent ke owner. Owner mengubah state. React merender ulang B dengan snapshot baru.

Ini menjaga UI tetap deterministic.


2. The Wrong Question

Pertanyaan yang sering muncul:

Bagaimana cara component A memanggil function di component B?

Pertanyaan yang lebih benar:

Apa state atau command yang perlu dimiliki oleh boundary bersama?

Contoh:

SearchBox perlu mengubah Table.

Jangan desain sebagai:

SearchBox -> Table.search(query)

Desain sebagai:

SearchBox emits queryChanged
Page owns query
Table receives query/result snapshot
function CaseSearchPage() {
  const [query, setQuery] = useState("");

  return (
    <>
      <SearchBox value={query} onValueChange={setQuery} />
      <CaseTable query={query} />
    </>
  );
}

Itu sibling communication melalui shared owner.


3. Pattern 1 — Lift State to the Nearest Common Parent

Ini pattern default untuk sibling yang perlu berbagi state lokal.

Sebelum:

function SearchBox() {
  const [query, setQuery] = useState("");
  return <input value={query} onChange={(event) => setQuery(event.target.value)} />;
}

function SearchSummary() {
  return <p>...</p>;
}

SearchSummary tidak bisa tahu query.

Sesudah:

function SearchSection() {
  const [query, setQuery] = useState("");

  return (
    <section>
      <SearchBox value={query} onValueChange={setQuery} />
      <SearchSummary query={query} />
    </section>
  );
}

Child menjadi controlled:

type SearchBoxProps = {
  value: string;
  onValueChange: (value: string) => void;
};

function SearchBox({ value, onValueChange }: SearchBoxProps) {
  return (
    <input
      value={value}
      onChange={(event) => onValueChange(event.target.value)}
    />
  );
}

Rule:

If two children need the same changing value, move that value to their nearest common owner.

Jangan naikkan state lebih tinggi dari yang diperlukan. “Nearest common parent” penting karena state yang terlalu tinggi memperbesar fan-out rerender dan coupling.


4. Pattern 2 — Mediator Parent

Kadang parent tidak sekadar menyimpan state, tetapi mengoordinasikan beberapa sibling.

Contoh: filter panel, table, pagination, summary.

Implementation:

type CaseListState = {
  query: string;
  status: CaseStatus | null;
  page: number;
};

function CaseListPage() {
  const [state, setState] = useState<CaseListState>({
    query: "",
    status: null,
    page: 1,
  });

  const cases = useCasesQuery(state);

  function handleQueryChange(query: string) {
    setState((current) => ({ ...current, query, page: 1 }));
  }

  function handleStatusChange(status: CaseStatus | null) {
    setState((current) => ({ ...current, status, page: 1 }));
  }

  function handlePageChange(page: number) {
    setState((current) => ({ ...current, page }));
  }

  return (
    <>
      <FilterPanel
        query={state.query}
        status={state.status}
        onQueryChange={handleQueryChange}
        onStatusChange={handleStatusChange}
      />
      <ResultSummary total={cases.data?.total ?? 0} />
      <CaseTable rows={cases.data?.rows ?? []} isLoading={cases.isLoading} />
      <Pagination
        page={state.page}
        totalPages={cases.data?.totalPages ?? 1}
        onPageChange={handlePageChange}
      />
    </>
  );
}

Mediator parent menjaga invariant:

changing query resets page
changing status resets page
page change does not clear filters
server query key follows current state

Jika logic makin kompleks, pindahkan ke reducer.


5. Pattern 3 — Reducer as Sibling Coordinator

Jika banyak sibling mengirim event ke owner yang sama, reducer membuat event model eksplisit.

type Event =
  | { type: "queryChanged"; query: string }
  | { type: "statusChanged"; status: CaseStatus | null }
  | { type: "pageChanged"; page: number }
  | { type: "sortChanged"; sort: SortSpec }
  | { type: "filtersReset" };

function reducer(state: State, event: Event): State {
  switch (event.type) {
    case "queryChanged":
      return { ...state, query: event.query, page: 1 };
    case "statusChanged":
      return { ...state, status: event.status, page: 1 };
    case "pageChanged":
      return { ...state, page: event.page };
    case "sortChanged":
      return { ...state, sort: event.sort, page: 1 };
    case "filtersReset":
      return initialState;
  }
}

Parent wiring:

function CaseListPage() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <FilterPanel
        query={state.query}
        status={state.status}
        onQueryChange={(query) => dispatch({ type: "queryChanged", query })}
        onStatusChange={(status) => dispatch({ type: "statusChanged", status })}
      />
      <SortBar
        sort={state.sort}
        onSortChange={(sort) => dispatch({ type: "sortChanged", sort })}
      />
      <Pagination
        page={state.page}
        onPageChange={(page) => dispatch({ type: "pageChanged", page })}
      />
    </>
  );
}

Reducer adalah tempat sibling event bertemu.


6. Pattern 4 — Context for Deep Sibling Families

Lifted state cukup jika sibling dekat. Jika sibling tersebar dalam subtree besar, props bisa menjadi noisy.

Context bisa menjadi scoped communication channel.

type CaseListContextValue = {
  state: CaseListState;
  dispatch: React.Dispatch<CaseListEvent>;
};

const CaseListContext = createContext<CaseListContextValue | null>(null);

function useCaseListContext() {
  const value = useContext(CaseListContext);
  if (!value) {
    throw new Error("useCaseListContext must be used inside CaseListProvider");
  }
  return value;
}

function CaseListProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  const value = useMemo(() => ({ state, dispatch }), [state]);

  return (
    <CaseListContext.Provider value={value}>
      {children}
    </CaseListContext.Provider>
  );
}

Consumers:

function FilterPanel() {
  const { state, dispatch } = useCaseListContext();

  return (
    <input
      value={state.query}
      onChange={(event) => dispatch({ type: "queryChanged", query: event.target.value })}
    />
  );
}

function ResultSummary() {
  const { state } = useCaseListContext();
  return <p>Search: {state.query || "All"}</p>;
}

Context menyelesaikan prop drilling, tetapi menambah implicit dependency. Jangan pakai context untuk semua sibling hanya karena malas passing props.

Gunakan context jika:

subtree adalah satu feature scope
banyak descendants butuh value yang sama
provider boundary jelas
state update frequency masih wajar
implicit dependency dapat diterima

Jika update sangat sering atau consumer sangat banyak, pertimbangkan external store atau selector-based store.


7. Pattern 5 — URL State for Shareable Sibling Synchronization

State tertentu lebih tepat hidup di URL daripada parent memory.

Contoh:

search query
filters
sort
pagination
selected tab
view mode

Alasan:

browser back/forward harus bekerja
page dapat dibagikan
reload tidak menghapus state
server route/data loader bisa membaca state

Pattern:

function CaseListPage() {
  const [searchParams, setSearchParams] = useSearchParams();

  const query = searchParams.get("q") ?? "";
  const status = searchParams.get("status") as CaseStatus | null;
  const page = Number(searchParams.get("page") ?? "1");

  function updateParams(patch: Record<string, string | null>) {
    const next = new URLSearchParams(searchParams);

    for (const [key, value] of Object.entries(patch)) {
      if (value === null || value === "") next.delete(key);
      else next.set(key, value);
    }

    setSearchParams(next);
  }

  return (
    <>
      <FilterPanel
        query={query}
        status={status}
        onQueryChange={(nextQuery) => updateParams({ q: nextQuery, page: "1" })}
        onStatusChange={(nextStatus) => updateParams({ status: nextStatus, page: "1" })}
      />
      <CaseTable query={query} status={status} page={page} />
      <Pagination
        page={page}
        onPageChange={(nextPage) => updateParams({ page: String(nextPage) })}
      />
    </>
  );
}

Di sini sibling disinkronkan oleh URL, bukan local state.

Decision rule:

If user expects reload/share/back-forward to preserve it, consider URL state.

8. Pattern 6 — External Store for Wide or High-Frequency Sibling Communication

Jika sibling berada di cabang berbeda atau state update sangat sering, lifted parent/context bisa mahal atau awkward.

External store cocok untuk:

global-ish UI shell state
cross-route selection
real-time connection status
large editor state
collaboration presence
high-frequency drag/drop state
complex client cache not owned by server

Minimal shape:

type SelectionStore = {
  getSnapshot: () => SelectionState;
  subscribe: (listener: () => void) => () => void;
  select: (id: string) => void;
  clear: () => void;
};

React integration memakai useSyncExternalStore:

function useSelectedCaseId(store: SelectionStore) {
  return useSyncExternalStore(
    store.subscribe,
    () => store.getSnapshot().selectedCaseId,
    () => null,
  );
}

A dan B tidak berkomunikasi langsung. Keduanya membaca store yang sama.

function CaseList({ store }: { store: SelectionStore }) {
  const selectedCaseId = useSelectedCaseId(store);

  return rows.map((row) => (
    <button
      key={row.id}
      aria-current={row.id === selectedCaseId}
      onClick={() => store.select(row.id)}
    >
      {row.title}
    </button>
  ));
}

function CaseInspector({ store }: { store: SelectionStore }) {
  const selectedCaseId = useSelectedCaseId(store);

  if (!selectedCaseId) return <p>No case selected</p>;
  return <CaseDetail caseId={selectedCaseId} />;
}

Store menjadi shared owner.

Peringatan:

external store increases power and coupling
make it scoped when possible
avoid turning every UI interaction into global store

9. Pattern 7 — Server State Cache for Remote Data Synchronization

Jika sibling perlu data remote yang sama, jangan copy data remote ke local parent state hanya agar dua sibling sinkron.

Contoh:

function CasePage({ caseId }: { caseId: string }) {
  return (
    <>
      <CaseHeader caseId={caseId} />
      <CaseTimeline caseId={caseId} />
      <CaseActions caseId={caseId} />
    </>
  );
}

Masing-masing bisa membaca query cache yang sama:

function CaseHeader({ caseId }: { caseId: string }) {
  const caseQuery = useCaseQuery(caseId);
  return <h1>{caseQuery.data?.caseNumber}</h1>;
}

function CaseActions({ caseId }: { caseId: string }) {
  const caseQuery = useCaseQuery(caseId);
  const mutation = useApproveCaseMutation();

  return (
    <button
      disabled={caseQuery.data?.status !== "READY" || mutation.isPending}
      onClick={() => mutation.mutate({ caseId })}
    >
      Approve
    </button>
  );
}

Cache menjadi sinkronisasi remote data. Mutation invalidates/updates cache. Sibling tidak saling tahu.

Rule:

If the source of truth is server, use a server-state cache instead of duplicating remote data in sibling-local state.

10. Pattern 8 — Command Channel / Event Bus, With Restraint

Event bus bisa berguna, tetapi sering menjadi jalan pintas yang membuat data flow tidak terlihat.

Gunakan event bus untuk:

integration boundary
analytics event
toast notification
legacy bridge
global keyboard shortcut
non-stateful broadcast

Jangan gunakan event bus sebagai default sibling communication.

Buruk:

bus.emit("filter:changed", query)

lalu table diam-diam subscribe:

bus.on("filter:changed", setQuery)

Masalah:

ownership hilang
subscriber tersembunyi
cleanup rawan bocor
ordering sulit dijamin
testing sulit

Jika tetap perlu event bus, bungkus sebagai capability dengan lifecycle-safe hook.

function useEventSubscription<T>(eventName: string, handler: (payload: T) => void) {
  useEffect(() => {
    return eventBus.subscribe(eventName, handler);
  }, [eventName, handler]);
}

Tetapi sebelum memakai bus, tanyakan:

Bisakah ini menjadi lifted state?
Bisakah ini menjadi context command?
Bisakah ini menjadi URL state?
Bisakah ini menjadi external store dengan explicit subscription?

11. Communication Strategy Matrix

ScenarioBest First PatternKenapa
SearchBox mengubah Table dalam satu pageLift state / mediator parentShared state lokal dengan owner jelas
Filter/pagination/sort harus shareableURL stateBack/forward/reload/share bekerja
Banyak descendants dalam satu feature perlu dispatchContext + reducerScoped feature coordination
High-frequency state dipakai banyak cabangExternal storeGranular subscription
Remote data dipakai banyak siblingServer-state cacheServer tetap source of truth
Modal/toast dari banyak siblingCapability contextCommand tanpa prop drilling
Legacy component perlu broadcast eventEvent bus adapterIntegration boundary, bukan domain state
Wizard step saling memengaruhiReducer/state machine ownerTransition dan invariant eksplisit
Cross-route selected itemURL atau scoped external storeTergantung shareability dan lifecycle

12. Build From Scratch: Selection Coordinator

Kita buat coordinator kecil untuk sibling list dan detail panel.

Requirement:

CaseList menampilkan list case
CaseInspector menampilkan selected case
Selection harus bisa clear
Selection tidak perlu masuk URL
Selection hanya berlaku dalam CaseWorkspace

Step 1 — State Owner

type SelectionState = {
  selectedCaseId: string | null;
};

type SelectionEvent =
  | { type: "caseSelected"; caseId: string }
  | { type: "selectionCleared" };

function selectionReducer(state: SelectionState, event: SelectionEvent): SelectionState {
  switch (event.type) {
    case "caseSelected":
      return { selectedCaseId: event.caseId };
    case "selectionCleared":
      return { selectedCaseId: null };
  }
}

Step 2 — Mediator Workspace

function CaseWorkspace() {
  const [state, dispatch] = useReducer(selectionReducer, {
    selectedCaseId: null,
  });

  return (
    <div className="case-workspace">
      <CaseList
        selectedCaseId={state.selectedCaseId}
        onCaseSelected={(caseId) => dispatch({ type: "caseSelected", caseId })}
      />
      <CaseInspector
        selectedCaseId={state.selectedCaseId}
        onSelectionCleared={() => dispatch({ type: "selectionCleared" })}
      />
    </div>
  );
}

Step 3 — Siblings Stay Simple

type CaseListProps = {
  selectedCaseId: string | null;
  onCaseSelected: (caseId: string) => void;
};

function CaseList({ selectedCaseId, onCaseSelected }: CaseListProps) {
  const rows = useCaseRows();

  return (
    <ul>
      {rows.map((row) => (
        <li key={row.id}>
          <button
            aria-current={row.id === selectedCaseId}
            onClick={() => onCaseSelected(row.id)}
          >
            {row.title}
          </button>
        </li>
      ))}
    </ul>
  );
}
type CaseInspectorProps = {
  selectedCaseId: string | null;
  onSelectionCleared: () => void;
};

function CaseInspector({ selectedCaseId, onSelectionCleared }: CaseInspectorProps) {
  if (!selectedCaseId) {
    return <p>Select a case</p>;
  }

  return (
    <aside>
      <button onClick={onSelectionCleared}>Close</button>
      <CaseDetail caseId={selectedCaseId} />
    </aside>
  );
}

No sibling direct call. No global store. No event bus. Ownership clear.


13. Build From Scratch: Same Problem With URL State

Jika selection harus shareable:

function CaseWorkspace() {
  const [params, setParams] = useSearchParams();
  const selectedCaseId = params.get("caseId");

  function selectCase(caseId: string) {
    const next = new URLSearchParams(params);
    next.set("caseId", caseId);
    setParams(next);
  }

  function clearSelection() {
    const next = new URLSearchParams(params);
    next.delete("caseId");
    setParams(next);
  }

  return (
    <div className="case-workspace">
      <CaseList
        selectedCaseId={selectedCaseId}
        onCaseSelected={selectCase}
      />
      <CaseInspector
        selectedCaseId={selectedCaseId}
        onSelectionCleared={clearSelection}
      />
    </div>
  );
}

Same UI, different owner.

Decision difference:

local reducer -> selection is workspace memory
URL state -> selection is navigation state
external store -> selection is shared application state
server cache -> selection is not state; data ownership is remote

14. Derived Sibling State

Kadang sibling B tidak butuh state baru. Ia butuh derived value dari state yang sudah dimiliki parent.

function CartPage() {
  const [items, setItems] = useState<CartItem[]>([]);

  const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);

  return (
    <>
      <CartItems items={items} onItemsChange={setItems} />
      <CartSummary subtotal={subtotal} itemCount={itemCount} />
    </>
  );
}

Jangan buat subtotal sebagai state terpisah kecuali ada alasan kuat.

Buruk:

const [items, setItems] = useState([]);
const [subtotal, setSubtotal] = useState(0);

Ini membuat dua source of truth. subtotal harus selalu sinkron dengan items; jika satu update lupa, UI inconsistent.

Rule:

If B can be computed from A during render, derive it; do not synchronize it with effects.

15. Avoid Cyclic Sibling Updates

Bug yang sulit dilacak sering muncul ketika sibling saling mengubah state melalui effect.

function A({ valueFromB, onAChange }) {
  useEffect(() => {
    onAChange(transform(valueFromB));
  }, [valueFromB, onAChange]);
}

function B({ valueFromA, onBChange }) {
  useEffect(() => {
    onBChange(transform(valueFromA));
  }, [valueFromA, onBChange]);
}

Ini menciptakan loop konseptual:

Perbaikan:

move derivation into parent
use reducer transition
remove effect-based synchronization
make one value source of truth

16. Prop Drilling vs Explicit Wiring

Tidak semua passing props beberapa level adalah masalah. Kadang itu justru dokumentasi dependency yang jelas.

Prop drilling menjadi masalah jika:

intermediate components tidak peduli props tersebut
banyak props melewati banyak level
perubahan API merembet ke banyak component
dependency sudah menjadi feature-wide capability

Sebelum pindah ke context, coba pecah component dengan composition:

function Page() {
  const [selectedId, setSelectedId] = useState<string | null>(null);

  return (
    <WorkspaceLayout
      sidebar={<CaseList selectedId={selectedId} onSelect={setSelectedId} />}
      inspector={<CaseInspector selectedId={selectedId} />}
    />
  );
}

WorkspaceLayout tidak perlu menerima selectedId. Ia hanya menerima slot.

Ini sering lebih baik daripada context.


17. When Sibling Communication Should Become a State Machine

Jika sibling communication sudah melibatkan workflow, bukan sekadar shared value, reducer biasa bisa kurang eksplisit.

Contoh wizard:

StepA validates identity
StepB validates documents
StepC confirms submission
Sidebar shows completion
Footer controls next/back/submit

State bukan hanya data. State adalah workflow:

editingIdentity
validatingIdentity
editingDocuments
validatingDocuments
reviewing
submitting
submitted
failed

Siblings:

StepPanel
WizardSidebar
WizardFooter
ValidationBanner

Mereka harus disinkronkan oleh workflow owner/state machine, bukan saling memanggil.

function WizardShell() {
  const [state, send] = useWizardMachine();

  return (
    <>
      <WizardSidebar currentStep={state.step} completedSteps={state.completedSteps} />
      <WizardStepPanel state={state} send={send} />
      <WizardFooter
        canGoBack={state.canGoBack}
        canGoNext={state.canGoNext}
        isSubmitting={state.matches("submitting")}
        onBack={() => send({ type: "BACK" })}
        onNext={() => send({ type: "NEXT" })}
        onSubmit={() => send({ type: "SUBMIT" })}
      />
    </>
  );
}

Jika sibling communication punya states, events, guards, dan illegal transitions, pakai state-machine thinking.


18. Failure Modes

Failure ModeGejalaPenyebabPerbaikan
Direct sibling callComponent A mencoba memanggil method BSalah model data flowGunakan shared owner/ref hanya jika imperative UI
Duplicated sibling stateA dan B punya copy state sendiriOwnership tidak jelasLift ke common parent/URL/store
Effect synchronization loopRender berulang tanpa hentiSibling sync via effectDerive di parent/reducer
State lifted too highBanyak rerender dan coupling app-wideCommon parent terlalu atasTurunkan owner ke feature boundary
Prop drilling overreactionContext dipakai terlalu cepatPassing props dianggap selalu burukCoba composition slot dulu
Context fan-outBanyak consumer rerenderContext value volatileSplit context/external store/selectors
Event bus chaosSubscriber tersembunyi, order tidak jelasBus menggantikan ownershipGunakan bus hanya integration boundary
URL overuseURL penuh transient stateSemua state dianggap shareableBedakan navigation vs ephemeral state
Store overuseGlobal state membesarSemua sibling state masuk storeScope store atau local owner
Server data copied locallyUI stale/inconsistentRemote data dipindah ke local stateGunakan query cache dan invalidation
Workflow hidden in booleansImpossible stateBanyak sibling mengubah flagReducer/state machine

19. Debugging Protocol

Saat sibling communication bug muncul, jangan langsung cari library.

Ikuti urutan ini:

1. Identifikasi value yang tidak sinkron.
2. Cari semua tempat yang menyimpan value tersebut sebagai state.
3. Tentukan source of truth yang seharusnya.
4. Hapus duplicated state atau jadikan derived value.
5. Jika harus shared, pilih owner paling dekat.
6. Jika harus shareable, pindah ke URL.
7. Jika remote, pindah ke server-state cache.
8. Jika wide/high-frequency, pindah ke external store.
9. Jika workflow, modelkan event dan transition.
10. Hapus effect yang hanya menyinkronkan dua state lokal.

20. Review Checklist

[ ] Apakah sibling benar-benar butuh komunikasi, atau hanya derived value?
[ ] Apakah state sudah dimiliki nearest common parent?
[ ] Apakah state dinaikkan terlalu tinggi?
[ ] Apakah URL lebih tepat karena state harus shareable?
[ ] Apakah remote data disimpan ulang sebagai client state?
[ ] Apakah context dipakai sebagai shortcut tanpa provider boundary jelas?
[ ] Apakah external store dipakai untuk state yang sebenarnya lokal?
[ ] Apakah ada effect yang hanya menjaga dua sibling tetap sinkron?
[ ] Apakah event bus dipakai untuk domain state?
[ ] Apakah workflow sudah punya transition model eksplisit?

21. Latihan

Latihan 1 — Search + Table + Summary

Bangun page dengan tiga sibling:

SearchBox
ResultTable
ResultSummary

State:

query
page
sort

Invariant:

query change resets page
sort change resets page
page change preserves query and sort

Implementasi pertama dengan useState, lalu refactor ke useReducer.

Latihan 2 — URL State

Pindahkan query, page, dan sort ke URL search params.

Pastikan:

reload preserves state
back button works
empty values are removed from URL
invalid page value is normalized

Latihan 3 — Context Boundary

Ambil feature dengan prop drilling 4 level. Jangan langsung pakai global context. Buat FeatureProvider scoped untuk subtree itu saja.

Latihan 4 — Remove Effect Sync

Diberikan dua sibling yang saling sync lewat useEffect, refactor menjadi single owner + derived state.

Latihan 5 — Workflow Owner

Buat wizard 3 step dengan sidebar dan footer sebagai sibling. Gunakan reducer atau state machine model. Pastikan impossible state tidak bisa terjadi.


22. Ringkasan

Sibling communication bukan jalur rahasia antar component. Ia adalah keputusan ownership.

Gunakan model ini:

same local UI concern        -> nearest common parent
many descendants same scope  -> context + reducer
shareable/navigation state   -> URL
remote source of truth        -> server-state cache
wide/high-frequency client    -> external store
workflow/illegal transitions  -> reducer or state machine
integration broadcast         -> event bus adapter, rarely

Jika dua sibling perlu sinkron, jangan mulai dari “bagaimana A mengirim ke B”. Mulai dari “siapa pemilik state/transition yang benar”.

Itulah perbedaan antara React component tree yang sekadar jalan dan React architecture yang bisa bertahan di codebase besar.


References

Lesson Recap

You just completed lesson 40 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.