# Tooling and Routing

# Packages

This lecture targets React 18 with the modern createRoot API. Keep the runtime and its types on the same major version:

Package Role Where it belongs
react Component/hook runtime dependencies
react-dom DOM renderer dependencies
@types/react, @types/react-dom Type declarations only devDependencies
react-router-dom 6 Routing dependencies (ships its own types)

react-router-dom v6 includes built-in TypeScript declarations—do not add @types/react-router-dom, which targeted older major versions. Continue with TypeScript 5, ts-loader 9 and Webpack 5 from the lecture 4 setup. Older projects may use "jsx": "react" and ReactDOM.render; the snippets here show the current equivalents. Nothing is installed into the course repository itself—configure this in your own project.

# TypeScript for JSX

Starting from the lecture 4 tsconfig.json, add one option and keep the rest:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2020", "DOM"],
    "jsx": "react-jsx",
    "strict": true,
    "noEmitOnError": true,
    "sourceMap": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

"jsx": "react-jsx" emits calls to the automatic runtime—no import * as React is needed in files that only use JSX (still import it to use React.useState etc.). The older "jsx": "react" setting compiles JSX to React.createElement, which requires React to be in scope in every file.

# Webpack changes

Extend the lecture 4 webpack.config.cjs rather than writing a second config. Only three fields change—the entry, the loader test, and the resolved extensions:

entry: './src/index.tsx',
module: {
  rules: [
    {
      test: /\.tsx?$/,
      use: 'ts-loader',
      exclude: /node_modules/
    }
  ]
},
resolve: { extensions: ['.tsx', '.ts', '.js'] },

Keep everything else already in the configuration: plugins, output, devtool, and any rules you added earlier (e.g. the CSS style-loader/css-loader rule from the lecture 4 development workflow stays alongside the tsx rule).

# Mounting the app

React 18 replaces the legacy ReactDOM.render API with createRoot from react-dom/client:

import { createRoot } from 'react-dom/client';
import App from './components/App';

const container = document.getElementById('root');
if (!container) {
  throw new Error('Missing #root element in index.html');
}

createRoot(container).render(<App />);

The null guard is required: getElementById returns HTMLElement | null, and an unchecked createRoot(document.getElementById("root")) fails type-checking under strict.

Replace the lecture 4 mount point with a plain wrapper—<div id="root"></div> instead of <main id="root"></main>—because routed pages render their own <main> elements and nested <main> landmarks are invalid HTML. The root container is just a mount point; each page component owns its semantic structure.

Keep the script distinction from Webpack development: build emits assets and exits, watch rebuilds on change, and webpack serve is the dev server. Naming a script "build": "webpack --watch" mislabels a watch command—name it watch instead.

# Routing with react-router-dom v6

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function Home() {
  return (
    <main>
      <h2>Home</h2>
      <Link to="/products">Products</Link>
    </main>
  );
}

function ProductPage() {
  return (
    <main>
      <h2>Products</h2>
      <Link to="/">Home</Link>
    </main>
  );
}

function NotFound() {
  return (
    <main>
      <h2>Not found</h2>
      <Link to="/">Back home</Link>
    </main>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<ProductPage />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

v6 essentials: <Routes> replaces <Switch>, each <Route> takes an element (not component), "*" is the catch-all, and <Link to="…"> navigates without a page reload.

# Serving routes in development and production

Client-side routing means deep links like /products only work if the server returns index.html for document requests. For webpack-dev-server:

devServer: {
  historyApiFallback: true,
  proxy: [
    {
      context: ['/api'],
      target: 'http://127.0.0.1:3000'
    }
  ]
}

historyApiFallback rewrites document requests to index.html; it does not rewrite /api calls (the proxy handles those—note the webpack-dev-server 5 array syntax from lecture 4, not the older object form) and must not swallow real asset requests: a missing bundle.js should 404, not return HTML. In production your web server needs the same fallback rule for document routes, applied after real files and /api routes. This fallback is about document routing only—authentication and CORS are separate concerns.

# A note on Create React App

Create React App (CRA) was the standard 2022 bootstrap but is now deprecated and is not recommended for new projects. The manual TypeScript + Webpack setup above continues what you built in lecture 4 and makes every step visible.

Try it: start from your lecture 4 build, apply the three Webpack edits and the jsx option, mount <App /> with createRoot, then add two routes and confirm that a direct request to /products works with historyApiFallback but fails with a 404 without it.