The back end is finished enough to be useful. Now the screens. React is a library for building user interfaces out of small, reusable pieces called components. Instead of finding elements and changing them by hand, you describe what the screen should look like for the current data, and React updates the page to match. This is the biggest idea in React, and the next six lessons build on it.
We use Vite to run and build the app. It starts in a moment, reloads the browser as you edit, and produces a small, fast production build. Create React App is no longer the recommended way to start, and Vite is the common replacement.
The files Vite gives you
{
"name": "job-tracker-client",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^5.1.1",
"vite": "^7.2.4"
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Job Tracker</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';
// React takes over the <div id="root"> in index.html and draws our components inside it.
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
);
- `index.html` is the only HTML page. It has an empty
<div id="root">and loadsmain.jsxas a module. Everything else is drawn by JavaScript. - `main.jsx` is the entry point.
createRoot(...).render(<App />)tells React to take control of therootdiv and draw theAppcomponent inside it.StrictModeruns some checks in development (and runs effects twice on purpose, to help you find bugs). - `package.json` lists React, ReactDOM, the Vite plugin for React, and Vite. The scripts are
dev,buildandpreview.
The proxy to your API
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// In development, the React app runs on port 5173 and calls /api. Vite forwards those calls to the Express
// server on port 5000, so the browser sees ONE origin and we avoid CORS trouble while developing.
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: { '/api': 'http://localhost:5000' },
},
});
In development the React app runs on port 5173 and the API on 5000. A page on one port that calls another is a cross-origin request, which needs CORS. The proxy setting avoids that: Vite forwards any request that starts with /api to the Express server, so the browser sees only one address. Your React code can then call fetch('/api/jobs') with a plain path, and in production, when Express serves the built app (lesson 38), the same code works without any change.
Components and JSX
// A component is a function that returns JSX: HTML-like markup written inside JavaScript.
export default function Header() {
const year = new Date().getFullYear(); // any JavaScript expression goes inside { }
return (
<header className="site-header">
<strong>Job Tracker</strong>
<span className="muted">Your search, in one place · {year}</span>
</header>
);
}
A component is a function that returns JSX. JSX looks like HTML but is JavaScript underneath: each tag becomes a function call. Four rules cover most of what you need.
- A component's name starts with a capital letter (
Header), which is how React tells it from an ordinary HTML tag (header). - A component returns one root element. Wrap siblings in a parent, or in an empty fragment
<>...</>. - Use
classNameinstead ofclass, andhtmlForinstead offor, becauseclassandforare reserved words in JavaScript. - Put any JavaScript expression inside curly braces:
{year},{items.length},{isOpen ? 'Hide' : 'Show'}. Statements such asifandfordo not go in braces; use the conditional? :,&&, or.map()for lists.
// For now this card always shows the same job. In lesson 26 it will receive the job as props.
export default function JobCard() {
return (
<article className="card">
<h2>Dispatcher</h2>
<p>Acme Logistics · Pune</p>
<span className="badge">applied</span>
</article>
);
}
import Header from './components/Header.jsx';
import JobCard from './components/JobCard.jsx';
export default function App() {
return (
<>
<Header />
<main className="container">
<h1>My jobs</h1>
<JobCard />
<JobCard />
</main>
</>
);
}
App is the root component. It renders a Header, and two JobCards, using each like an HTML tag. Right now both cards show the same text. In the next lesson, props let each card receive its own data. Notice also how the layout is described, not constructed: there is no code that says “create an element and append it”.
The styles
:root { --bg:#f8fafc; --ink:#0f172a; --muted:#64748b; --line:#e2e8f0; --brand:#4f46e5; --brand-ink:#fff; }
* { box-sizing: border-box; }
body { margin:0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background:var(--bg); color:var(--ink); line-height:1.5; }
.container { max-width: 860px; margin: 0 auto; padding: 16px; }
.site-header { display:flex; gap:12px; align-items:baseline; flex-wrap:wrap; padding:12px 16px; background:#fff; border-bottom:1px solid var(--line); }
.muted { color: var(--muted); font-size:.9rem; }
.card { background:#fff; border:1px solid var(--line); border-radius:12px; padding:14px 16px; margin:10px 0; }
.card h2 { margin:0 0 4px; font-size:1.1rem; }
.badge { display:inline-block; padding:2px 10px; border-radius:999px; background:#eef2ff; color:#3730a3; font-size:.8rem; }
We use one plain CSS file. The custom properties at the top (--brand and friends) make colours easy to change in one place. In lesson 34 the file is replaced by a polished responsive version.
Your task
- Run
npm run setupfrom the project root (ornpm installinsideclient), thennpm run devinsideclient. Open the address Vite prints. - Change the text in
Header.jsxand save. Watch the browser update without a reload. - Create a
Footercomponent that shows the year, and use it inApp. - Open the browser tools, Network tab, and find the request for
main.jsx. See how Vite serves your source files in development.
How to check it worked
- The browser shows the header and two cards.
- Editing a component updates the page immediately (this is called hot reloading).
Summary
index.html+main.jsxstart React;Appis the root component.- A component is a capitalised function returning one JSX element; braces hold JavaScript expressions.
- The Vite proxy sends
/apicalls to Express, so the same URLs work in development and production.