Module 00 · App Structure
Module 00 · Questions 110–115

How a React App is Wired

The files every React project has — index.html, main.tsx, App.tsx — and how a click on a link ends up rendering a component. Interviewers often open with "walk me through your project structure", and this is that answer.

Q110

How does a React app actually start?

In one line

The browser loads one HTML file with one empty <div>. main.tsx then attaches React to that div, and React builds everything else.

index.html <div id="root"></div> ← one empty box │ <script src="/src/main.tsx"> ▼ main.tsx createRoot(#root).render(<App />) │ + global providers (theme, auth, query client) ▼ App.tsx the router: which URL shows which page │ ▼ AppLayout sidebar + header + <Outlet /> │ ▼ BrandsPage useState / useEffect / JSX │ ▼ brandService getBrands() │ ▼ api-client fetch + JWT header ──► .NET API

That's the whole chain. Three files do the wiring, and everything below is your app.

FileJob
index.htmlThe single page. Holds <div id="root"> and the script tag.
src/main.tsxEntry point. Mounts React and wraps the app in global providers.
src/App.tsxRouting. Maps URLs to pages, and applies the layout and route guards.
Why it's called a Single Page Application

There is genuinely only one HTML file. Navigating doesn't fetch a new page — React Router changes the URL with the history API and swaps which component renders inside that same #root div (Module 08).

Q111

What goes in main.tsx?

In one line

Two things only: mount React onto the DOM, and wrap the app in everything that must be available globally.

// index.html (at the project root, not in src/)
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";                        // global styles / Tailwind

import { ThemeProvider } from "./context/ThemeContext";
import { AuthProvider }  from "./context/AuthContext";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: { queries: { staleTime: 30_000, retry: 1 } }
});

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <ThemeProvider>
        <AuthProvider>
          <App />
        </AuthProvider>
      </ThemeProvider>
    </QueryClientProvider>
  </React.StrictMode>
);
LineWhat it does
createRoot(...)Connects React to that empty div. React 18+ API — the old one was ReactDOM.render().
.render(<App />)Draws your app inside it.
<React.StrictMode>Development-only checks. Deliberately mounts twice to expose missing cleanup (Q87). Removed automatically in the production build.
ProvidersAnything the whole app needs: auth, theme, query cache, i18n, MSAL.
! after getElementByIdTypeScript non-null assertion — "trust me, this div exists".
Trap — React 17 vs React 18

The old ReactDOM.render(<App />, document.getElementById("root")) still works but logs a warning and disables concurrent features. React 18+ uses createRoot, and React 19 removed the old API completely. Knowing which one you're looking at dates a codebase instantly.

30-second interview answer

main.tsx is the entry point. It imports the root component, finds the #root div from index.html, and calls ReactDOM.createRoot(...).render(<App />) — createRoot being the React 18 API that enables concurrent rendering. It's also where I put everything that must be global: the QueryClientProvider, auth and theme contexts, and StrictMode, which double-mounts in development to catch effects with missing cleanup. No business logic lives there — it's purely wiring.

Q112

What goes in App.tsx?

In one line

The routing table — which URL renders which page, wrapped in the layout and the auth guard.

// src/App.tsx
export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* public */}
        <Route path="/login" element={<SignIn />} />

        {/* private: guard → layout → pages */}
        <Route element={<ProtectedRoute />}>
          <Route element={<AppLayout />}>
            <Route index path="/"            element={<Dashboard />} />
            <Route path="/brands"            element={<BrandsPage />} />
            <Route path="/brands/:id"        element={<BrandDetailPage />} />
            <Route path="/orders"            element={<OrdersPage />} />
          </Route>
        </Route>

        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Read the nesting out loud, because that's how you'd explain it in an interview:

URL /brands → ProtectedRoute : logged in? no → /login → AppLayout : draws sidebar + header, leaves a hole <Outlet /> → BrandsPage : renders into that hole
// src/layout/AppLayout.tsx — rendered ONCE, not re-mounted on navigation
export default function AppLayout() {
  return (
    <div className="layout">
      <AppSidebar />
      <div className="content">
        <AppHeader />
        <main><Outlet /></main>      {/* the matched page appears here */}
      </main>
    </div>
  );
}
Two upgrades to mention

Lazy routes keep the first load small — const Reports = React.lazy(() => import("./pages/Reports")) wrapped in <Suspense> (Q72). And an error boundary per route means one broken page doesn't blank the whole app (Q64).

Q113

Why does the order of providers matter?

In one line

A provider can only be used by components inside it — so anything that depends on another provider must sit further in.

✅ CORRECT ❌ BROKEN <QueryClientProvider> <AuthProvider> ← needs the query client <AuthProvider> <QueryClientProvider> but sits outside it <Router> <App /> <App /> → "No QueryClient set" at runtime

The usual order, outermost first:

  1. StrictMode — dev checks, wraps everything
  2. ErrorBoundary — so it can catch failures in the providers below
  3. QueryClientProvider / MSAL — data and auth infrastructure
  4. ThemeProvider, i18n — no dependencies, safe anywhere high up
  5. AuthProvider — often calls an API, so it goes inside the query/HTTP layer
  6. BrowserRouter — inside anything its routes need; a guard that reads auth must be inside AuthProvider
  7. App
Trap

Providers re-render everything below them when their value changes, so don't put fast-changing state in a top-level provider, and memoize the value object (Q94).

Q114

What's in a Vite React project, file by file?

my-app/ ├── index.html ← the single page, with <div id="root"> ├── package.json ← dependencies + scripts (dev / build / preview) ├── vite.config.ts ← plugins, dev-server port, proxy, path aliases ├── tsconfig.json ← TypeScript settings ├── .env / .env.production ← VITE_API_URL=... (only VITE_ vars reach the browser!) ├── public/ ← files copied as-is: favicon, robots.txt └── src/ ├── main.tsx ← entry point (Q111) ├── App.tsx ← routes (Q112) ├── index.css ← global styles / Tailwind ├── api/ ← api-client.ts (base URL, JWT, error envelope) ├── services/ ← brandService.ts, orderService.ts ├── types/ ← interfaces mirroring the backend DTOs ├── pages/ ← one folder per feature ├── components/ ← shared UI: ui/, form/, tables/ ├── context/ ← AuthContext, ThemeContext ├── hooks/ ← useDebounce, useModal ├── routes/ ← ProtectedRoute └── layout/ ← AppLayout, AppSidebar, AppHeader
CommandWhat it does
npm run devDev server with hot reload (usually port 5173)
npm run buildType-check + bundle into dist/ for deployment
npm run previewServe dist/ locally to test the production build
Environment variables — a likely question
# .env
VITE_API_URL=https://localhost:7001

// used in code
const BASE_URL = import.meta.env.VITE_API_URL;

Only variables prefixed VITE_ are exposed, and they are baked into the bundle at build time — so they are public. Never put a secret there; secrets belong in the .NET API.

Vite vs Create React App

CRA is deprecated. Vite starts in milliseconds because it serves native ES modules in development and only bundles for production (with Rollup). If a codebase still has react-scripts in package.json, it's CRA — and migrating to Vite is a good thing to suggest.

Q115

"Walk me through your project" — the answer

In one line

Describe it as layers with one direction of dependency, exactly as you'd describe Clean Architecture on the .NET side.

Pages / Components render only — no URLs, no tokens │ call ▼ Services one file per feature, knows the endpoints + types │ call ▼ API client base URL · JWT header · consistent { isSuccess, data, messageCode } │ ▼ .NET API Controller → Application service → Repository → SQL Server Cross-cutting: context/ (auth, theme) · hooks/ (reusable logic) · routes/ (guards) · layout/

Then give the reasons, not just the boxes:

  • One place for cross-cutting concerns. Auth, base URL and error shape live in the API client — if auth changes, one file changes, not forty components.
  • Pages stay dumb, services stay smart. That keeps pages readable and lets tests mock a service instead of the network.
  • Feature folders scale. With 20+ modules, "everything about stickers is in one folder" beats one giant components/ directory.
  • Types mirror the backend DTOs, so a renamed field is a compile error rather than a production bug.
Use your real project

You've built exactly this: an admin portal with 20+ feature modules, a services/ layer over a single fetch-based api-client returning { isSuccess, data, messageCode }, Context for auth/theme/sidebar, and a ProtectedRoute guard — talking to a .NET 9 Clean Architecture API. Describe that, then add what you'd improve next: React Query for caching, lazy-loaded routes, and a shared usePagedList hook for the repeated list-screen logic. Naming your own improvements is what makes it sound senior.

30-second interview answer

The browser loads a single index.html with an empty root div. main.tsx mounts React with createRoot and wraps the app in the global providers — query client, auth, theme. App.tsx holds the routing: public routes, then a protected group where a guard checks auth, a layout renders the sidebar and header once with an Outlet, and the pages render inside it. Below that it's layered: pages render, services own the endpoints and types, and a single API client owns the base URL, the bearer token and a consistent response envelope. Shared concerns live in context, hooks and components/ui, and the folders are organised by feature rather than by file type so it scales past twenty modules.