This synthesizes every layer from Modules 1 through 4: AuthContext for session state, Redux Toolkit's cartSlice and productsSlice for cross-cutting global state, a createAsyncThunk-driven product catalog, and Bootstrap for a fully responsive layout — nav bar, product grid, cards, alerts, and loading spinners.

Responsive Nav Bar: AuthContext + Cart Count

src/components/NavBar.tsxtypescript
import { useAuth } from '../context/AuthContext'
import { useAppSelector } from '../app/hooks'
import { selectCartItemCount } from '../features/cart/cartSlice'

function NavBar() {
  const { user, isAuthenticated, logout } = useAuth()
  const cartItemCount = useAppSelector(selectCartItemCount)

  return (
    <nav className="navbar navbar-expand-lg navbar-dark bg-dark">
      <div className="container">
        <a className="navbar-brand fw-bold" href="/">
          <i className="bi bi-bag-check-fill me-2" />
          RTK Store
        </a>

        <div className="d-flex align-items-center gap-3 ms-auto">
          <a href="/cart" className="position-relative text-light text-decoration-none">
            <i className="bi bi-cart3 fs-5" />
            {cartItemCount > 0 && (
              <span className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
                {cartItemCount}
                <span className="visually-hidden">items in cart</span>
              </span>
            )}
          </a>

          {isAuthenticated ? (
            <div className="dropdown">
              <button
                className="btn btn-outline-light btn-sm dropdown-toggle"
                type="button"
                data-bs-toggle="dropdown"
              >
                {user?.name}
              </button>
              <ul className="dropdown-menu dropdown-menu-end">
                <li>
                  <button className="dropdown-item" onClick={logout}>
                    Sign Out
                  </button>
                </li>
              </ul>
            </div>
          ) : (
            <a href="/login" className="btn btn-outline-light btn-sm">
              Sign In
            </a>
          )}
        </div>
      </div>
    </nav>
  )
}

export default NavBar

Product Grid: Async Thunk + Typed Add-to-Cart

src/components/ProductCatalog.tsxtypescript
import { useEffect } from 'react'
import { useAppDispatch, useAppSelector } from '../app/hooks'
import {
  fetchProducts,
  selectProducts,
  selectProductsStatus,
  selectProductsError
} from '../features/products/productsSlice'
import { itemAdded } from '../features/cart/cartSlice'

function ProductCatalog() {
  const dispatch = useAppDispatch()
  const products = useAppSelector(selectProducts)
  const status = useAppSelector(selectProductsStatus)
  const error = useAppSelector(selectProductsError)

  useEffect(() => {
    if (status === 'idle') {
      dispatch(fetchProducts('all'))
    }
  }, [status, dispatch])

  function handleAddToCart(productId: string) {
    const product = products.find((p) => p.id === productId)
    if (!product) return

    dispatch(
      itemAdded({
        productId: product.id,
        name: product.name,
        price: product.price
      })
    )
  }

  if (status === 'loading' || status === 'idle') {
    return (
      <div className="d-flex flex-column align-items-center py-5">
        <div className="spinner-border text-primary mb-3" role="status">
          <span className="visually-hidden">Loading products…</span>
        </div>
        <p className="text-muted">Loading catalog…</p>
      </div>
    )
  }

  if (status === 'failed') {
    return (
      <div className="alert alert-danger d-flex align-items-center gap-2" role="alert">
        <i className="bi bi-exclamation-triangle-fill" />
        <span>{error}</span>
      </div>
    )
  }

  if (products.length === 0) {
    return <div className="alert alert-info">No products available right now.</div>
  }

  return (
    <div className="row g-4">
      {products.map((product) => (
        <div key={product.id} className="col-12 col-sm-6 col-lg-4">
          <div className="card h-100 shadow-sm">
            <img
              src={product.imageUrl}
              className="card-img-top"
              alt={product.name}
              style={{ objectFit: 'cover', height: 180 }}
            />
            <div className="card-body d-flex flex-column">
              <h5 className="card-title">{product.name}</h5>
              <span className="badge text-bg-secondary align-self-start mb-2">
                {product.category}
              </span>
              <p className="card-text fw-bold fs-5 mt-auto">
                ${product.price.toFixed(2)}
              </p>
              <button
                type="button"
                className="btn btn-primary"
                onClick={() => handleAddToCart(product.id)}
              >
                <i className="bi bi-cart-plus me-1" />
                Add to Cart
              </button>
            </div>
          </div>
        </div>
      ))}
    </div>
  )
}

export default ProductCatalog

Assembling the App

src/App.tsx (final capstone wiring)typescript
import { AuthProvider } from './context/AuthContext'
import NavBar from './components/NavBar'
import ProductCatalog from './components/ProductCatalog'

function App() {
  return (
    <AuthProvider>
      <NavBar />
      <main className="container py-4">
        <h1 className="mb-4 fw-bold">Product Catalog</h1>
        <ProductCatalog />
      </main>
    </AuthProvider>
  )
}

export default App

Note the layering: AuthProvider (Context, Module 3) wraps the whole tree for session state, while NavBar and ProductCatalog both read from the Redux store (Module 4) via useAppSelector — two different state mechanisms coexisting deliberately, each used for the kind of state it fits.


ToolIdeal use caseRe-render costKey TypeScript pattern
useStateComponent-local UI state: form fields, toggles, hover stateOnly the owning component re-rendersExplicit generic for object shapes: useState<ShippingAddress>({...})
useEffectSyncing with an external system: fetch, subscriptions, DOM APIsN/A — runs a side effect, not a render trigger by itselfCleanup function return type: () => void; AbortController for cancellable fetches
useMemo / useCallbackPreserving reference equality for React.memo children or hook dependency arraysNo re-render cost of their own; skips re-renders in dependentsuseCallback<(id: string) => void>(...) or let inference handle it from the function literal
Context APILow-frequency data read broadly: auth session, theme, localeEvery consumer re-renders on any value change — no selector layerDefault to undefined, throw in a custom hook (useAuth) if accessed outside the Provider
Redux Toolkit (slice)Synchronous cross-cutting state: cart, UI flags shared across featuresScoped via useAppSelector — only re-renders on the selected slice changingPayloadAction<T> on every reducer; Immer allows mutation-style updates safely
Redux Toolkit (createAsyncThunk)Asynchronous data flows: API fetches with loading/error lifecycleSame as above — status field drives conditional renderingcreateAsyncThunk<Returned, ThunkArg>(...) + typed extraReducers via the builder API

Capstone Takeaways

  • A real application layers state deliberately: Context for broad, low-frequency data (auth); Redux Toolkit for high-frequency, cross-cutting data (cart, product catalog) — not one tool for everything.
  • Bootstrap's interactive components (dropdowns, offcanvas, modals) work via data-bs-* attributes and the bundled JS, independent of React state — don't build custom open/close state for them unless you need programmatic control.
  • Every dispatch call and every useAppSelector call is fully typed end-to-end, from the store's inferred RootState through each slice's action creators — this is what "complete type safety" means in practice, not just typing props.
  • The cheat sheet's re-render cost column is the deciding factor most teams skip — picking Context for fast-changing state, or Redux for a single component's private toggle, are the two most common architecture missteps in production React + TS codebases.