React Fundamentals and Project Setup
Before you start
You need: HTML, CSS, JavaScript and ideally TypeScript (Track 09).
You need installed: Node 20 LTS and VS Code. Create the project with npm create vite@latest — React work is done in VS Code, not Visual Studio.
Time: about 50 minutes, plus the practice.
Learning objective
Create a React project, explain its structure, and write components that render data with JSX.
Topics
- What React is, and where it fits
- Creating a project with Vite
- Project structure
- JSX rules
- Components
- Rendering lists
- Conditional rendering
- Fragments and keys
What React is
React is a library for building user interfaces. It renders components and manages updates; everything else — routing, HTTP, forms, state management — is a separate choice.
| React | Angular | |
|---|---|---|
| Scope | View library | Full framework |
| Routing, HTTP, forms | Chosen per project | Built in |
| Language | JavaScript or TypeScript | TypeScript, enforced |
| Structure | Your decision | Prescribed |
| Learning curve | Gentler start | Steeper start, fewer later decisions |
That freedom is React's advantage and its cost. Two React projects can look entirely different; two Angular projects rarely do.
This track uses TypeScript. JavaScript works, and the type safety pays for itself the first time an API response shape changes.
Prerequisites: the Web Development Foundation track — HTML, CSS, modern JavaScript and TypeScript basics. React errors assume you know which layer failed.
Creating a project
npm create vite@latest school-portal -- --template react-ts
cd school-portal
npm install
npm run dev
Vite is the current standard: near-instant dev server start, fast hot reload, and a small production build.
create-react-app is deprecated and no longer maintained. You will meet it in existing projects; do not start new ones with it.
npm run dev # dev server on http://localhost:5173
npm run build # production build into dist/
npm run preview # serve the production build locally
Project structure
Vite generates very little, because React prescribes nothing:
school-portal/
├── index.html the single HTML page
├── vite.config.ts
├── tsconfig.json
└── src/
├── main.tsx entry point
├── App.tsx root component
├── index.css global styles
└── vite-env.d.ts
Adopt a structure before it becomes a problem:
src/
├── main.tsx
├── App.tsx
├── api/ HTTP client and per-resource services
├── components/ shared presentational components
├── features/ one folder per business area
│ ├── students/
│ │ ├── StudentList.tsx
│ │ ├── StudentCard.tsx
│ │ ├── StudentForm.tsx
│ │ └── useStudents.ts
│ └── fees/
├── hooks/ shared custom hooks
├── types/ shared interfaces
└── lib/ helpers
Group by feature, not by file type. A components/ folder holding sixty unrelated components is unnavigable; features/students/ holds everything about students together.
// main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
StrictMode deliberately double-invokes components and effects in development. That is not a bug — it surfaces impure renders and missing effect cleanup. If a component behaves differently under StrictMode, it has a real defect that would appear in production under concurrent rendering.
JSX
JSX looks like HTML and compiles to function calls.
const element = <h1 className="title">Students</h1>;
// compiles to
const element = jsx('h1', { className: 'title', children: 'Students' });
The rules
One root element. A component returns one node — use a fragment when you have several:
return (
<>
<h1>Students</h1>
<p>Manage admissions.</p>
</>
);
Attributes are camelCase, and some are renamed because their HTML names are JavaScript keywords:
| HTML | JSX |
|---|---|
class | className |
for | htmlFor |
onclick | onClick |
tabindex | tabIndex |
maxlength | maxLength |
readonly | readOnly |
aria-label | aria-label (unchanged) |
data-id | data-id (unchanged) |
aria-* and data-* keep their hyphens. Everything else is camelCase, and class= silently does nothing.
Every tag must close. <br> is <br />, <img> is <img />.
Braces embed expressions, not statements:
<h1>{student.name}</h1>
<p>{student.className} - {student.section}</p>
<p>{marks >= 35 ? 'Pass' : 'Fail'}</p>
<p>{student.address ?? 'Not recorded'}</p>
<img src={student.photoUrl} alt={student.name} />
<div style={{ color: 'red', fontSize: '1.2rem' }}>Styled</div>
An if statement cannot go inside braces — only expressions. Compute before the return, or use a ternary.
style takes an object with camelCase properties, hence the double braces: one for JSX, one for the object literal.
Automatic escaping
const parentName = "<script>alert('hacked')</script>";
<p>{parentName}</p> // renders as visible text — safe
React escapes every embedded value. XSS requires dangerouslySetInnerHTML, which is named that way on purpose:
<div dangerouslySetInnerHTML={{ __html: userContent }} />
Never pass user or API data through it. Sanitise server-side first, or do not use it.
Components
A component is a function returning JSX. Its name must start with a capital letter — lowercase names are treated as HTML elements, so <studentCard /> renders an unknown tag with no error.
// features/students/StudentCard.tsx
export interface Student {
publicId: string;
name: string;
rollNumber: string;
className: string;
section: string;
status: 'Active' | 'Inactive' | 'Graduated' | 'Transferred';
}
interface StudentCardProps {
student: Student;
showActions?: boolean;
onSelect: (publicId: string) => void;
onDelete?: (publicId: string) => void;
}
export function StudentCard({
student,
showActions = true,
onSelect,
onDelete
}: StudentCardProps) {
return (
<article className="student-card">
<h3>{student.name}</h3>
<p className="roll">{student.rollNumber}</p>
<p>{student.className} - {student.section}</p>
{showActions && (
<div className="actions">
<button type="button" onClick={() => onSelect(student.publicId)}>
View
</button>
{onDelete && (
<button type="button" onClick={() => onDelete(student.publicId)}>
Delete
</button>
)}
</div>
)}
</article>
);
}
Props are the function's parameters, destructured with defaults. showActions?: boolean with = true gives an optional prop with a sensible default.
Props are read-only. Assigning to one is a bug React will not stop:
// Wrong
function StudentCard({ student }: StudentCardProps) {
student.name = student.name.toUpperCase(); // mutates the parent's object
}
Data flows down; changes flow up through callbacks.
Rendering lists
export function StudentList({ students }: { students: Student[] }) {
return (
<div className="card-grid">
{students.map(student => (
<StudentCard
key={student.publicId}
student={student}
onSelect={handleSelect}
/>
))}
</div>
);
}
Keys
key is required and must be stable. React uses it to decide which DOM nodes to keep when the list changes.
// Wrong — index changes when the list is sorted or filtered
{students.map((student, index) => <StudentCard key={index} student={student} />)}
Two visible consequences:
- Lost state. Text typed into a row's input, an expanded row, a focused field — destroyed on the next re-render.
- Wasted work. Rebuilding 200 rows when one changed.
Use key={index} only for a static list of primitives with no per-item state.
Never key={Math.random()}. Every render produces new keys, so React rebuilds the entire list every time — the worst possible outcome, and it looks like it works.
Conditional rendering
// Ternary
{isLoading ? <Spinner /> : <StudentTable students={students} />}
// && for "render or nothing"
{error && <p className="error" role="alert">{error}</p>}
// Early return for a whole-component branch
if (isLoading) {
return <p>Loading students…</p>;
}
return <StudentTable students={students} />;
The && trap
// Renders "0" when the list is empty
{students.length && <StudentTable students={students} />}
0 is falsy, so && returns 0 — and React renders the number 0. An empty list shows a stray zero on the page.
// Correct
{students.length > 0 && <StudentTable students={students} />}
Always compare explicitly. This catches everyone once.
Handling four states properly:
export function StudentListScreen() {
if (isLoading) {
return <p className="muted">Loading students…</p>;
}
if (error) {
return (
<div role="alert">
<p className="error">{error}</p>
<button type="button" onClick={retry}>Try again</button>
</div>
);
}
if (students.length === 0) {
return <p className="muted">No students match this search.</p>;
}
return <StudentTable students={students} />;
}
Loading, error, empty and loaded. Most student projects handle one — and a blank screen for "no results" is indistinguishable from a bug.
Fragments
// Shorthand
<>
<td>{student.rollNumber}</td>
<td>{student.name}</td>
</>
// Long form — required when a key is needed
{rows.map(row => (
<Fragment key={row.id}>
<td>{row.label}</td>
<td>{row.value}</td>
</Fragment>
))}
Fragments group without adding a DOM element — essential inside <table> and <select>, where a wrapper <div> is invalid HTML.
The shorthand <> cannot take a key, so a keyed fragment needs the full <Fragment> form.
Composition with children
interface CardProps {
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
export function Card({ title, children, footer }: CardProps) {
return (
<section className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</section>
);
}
<Card title="Fee summary" footer={<button type="button">Collect</button>}>
<p>Outstanding: ₹8,000</p>
</Card>
children is whatever the caller nests inside the tag. Passing a component through a prop — footer here — is how React handles what Angular does with content projection.
Composition replaces inheritance in React. There is no component base class; a component that needs to vary takes a prop or a child.
Errors you will hit
| Message | Cause | Fix |
|---|---|---|
Adjacent JSX elements must be wrapped in an enclosing tag | Two siblings returned | Wrap in a fragment <>...</> |
Objects are not valid as a React child | Rendered an object | Render a property |
Each child in a list should have a unique "key" prop | Missing key | Use a stable unique id |
X is not defined | Component not imported | Import it |
| Component name renders as literal text | Lowercase name | Components must be Capitalised |
Cannot read properties of undefined on first render | Data not loaded yet | Guard before rendering |
A lowercase component name is treated as an HTML tag. <studentCard /> renders nothing and reports nothing.
Common mistakes
class=instead ofclassName=- A component name starting with a lowercase letter
- Missing
key, orkey={index}on a list with state key={Math.random()}{students.length && …}rendering a stray0- Mutating a prop
- An
ifstatement inside JSX braces style="color: red"instead of an objectdangerouslySetInnerHTMLwith user data- A wrapper
<div>inside a<table>instead of a fragment - Only handling the success state
- Assuming a
StrictModedouble-render is a bug rather than a signal - Starting a new project with
create-react-app
Practice
The course exercise is build reusable cards.
- Create a project with
npm create vite@latest --template react-ts. Run it. - Adopt the
features/components/apistructure. - Build
StudentCardwith typed props, an optional prop with a default, and two callbacks. - Build
StudentListrendering three hard-coded students through the card. - Write
class=instead ofclassName=. Confirm the style does not apply and no error appears. - Rename the component to
studentCardand use<studentCard />. Record what renders. - Use
key={index}. Add a text input to each card, type into one, then reverse the list. Confirm the values move to the wrong cards. - Fix it with
key={student.publicId}and confirm the values follow. - Use
key={Math.random()}and watch every card rebuild on each render. - Write
{students.length && <Table />}with an empty array. Find the stray0on the page. - Implement all four states with early returns. Force each.
- Mutate a prop inside the child and confirm the parent's data changed.
- Build a
Cardcomponent usingchildrenand afooterprop. - Render a student named
<script>alert(1)</script>and confirm it displays as text.
Exercises 7 and 10 are the two React bugs that reach production most often.
You can now
- Create a React project and explain its structure
- Write components with typed props
- Render lists with stable keys
- Use fragments for sibling elements
- Guard against data that has not arrived yet
Review questions
- Why must a component name start with a capital letter?
- What breaks when
key={index}is used on a reorderable list? - Why does
{count && <Component />}render a stray zero? - What does
StrictModedo, and why is a double render not a bug?
Next: State and events