Skip to main content
Published / updated

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.

ReactAngular
ScopeView libraryFull framework
Routing, HTTP, formsChosen per projectBuilt in
LanguageJavaScript or TypeScriptTypeScript, enforced
StructureYour decisionPrescribed
Learning curveGentler startSteeper 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:

HTMLJSX
classclassName
forhtmlFor
onclickonClick
tabindextabIndex
maxlengthmaxLength
readonlyreadOnly
aria-labelaria-label (unchanged)
data-iddata-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

MessageCauseFix
Adjacent JSX elements must be wrapped in an enclosing tagTwo siblings returnedWrap in a fragment <>...</>
Objects are not valid as a React childRendered an objectRender a property
Each child in a list should have a unique "key" propMissing keyUse a stable unique id
X is not definedComponent not importedImport it
Component name renders as literal textLowercase nameComponents must be Capitalised
Cannot read properties of undefined on first renderData not loaded yetGuard before rendering

A lowercase component name is treated as an HTML tag. <studentCard /> renders nothing and reports nothing.

Common mistakes

  • class= instead of className=
  • A component name starting with a lowercase letter
  • Missing key, or key={index} on a list with state
  • key={Math.random()}
  • {students.length && …} rendering a stray 0
  • Mutating a prop
  • An if statement inside JSX braces
  • style="color: red" instead of an object
  • dangerouslySetInnerHTML with user data
  • A wrapper <div> inside a <table> instead of a fragment
  • Only handling the success state
  • Assuming a StrictMode double-render is a bug rather than a signal
  • Starting a new project with create-react-app

Practice

The course exercise is build reusable cards.

  1. Create a project with npm create vite@latest --template react-ts. Run it.
  2. Adopt the features / components / api structure.
  3. Build StudentCard with typed props, an optional prop with a default, and two callbacks.
  4. Build StudentList rendering three hard-coded students through the card.
  5. Write class= instead of className=. Confirm the style does not apply and no error appears.
  6. Rename the component to studentCard and use <studentCard />. Record what renders.
  7. 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.
  8. Fix it with key={student.publicId} and confirm the values follow.
  9. Use key={Math.random()} and watch every card rebuild on each render.
  10. Write {students.length && <Table />} with an empty array. Find the stray 0 on the page.
  11. Implement all four states with early returns. Force each.
  12. Mutate a prop inside the child and confirm the parent's data changed.
  13. Build a Card component using children and a footer prop.
  14. 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

  1. Why must a component name start with a capital letter?
  2. What breaks when key={index} is used on a reorderable list?
  3. Why does {count && <Component />} render a stray zero?
  4. What does StrictMode do, and why is a double render not a bug?

Next: State and events