Web Development Fundamentals — Part 8: React — Building Modern User Interfaces
Series Overview
This is an 8-part series covering the core technologies of web development:
- HTML — The Structure of the Web — Elements, attributes, block vs inline, and document structure
- CSS — Styling the Web — Selectors, properties, the box model, and layout basics
- JavaScript — Adding Interactivity — Variables, data types, functions, arrays, and control flow
- The DOM — Connecting JavaScript to HTML — Selecting elements, changing styles, and modifying attributes
- jQuery — Write Less, Do More — Selectors, events, and DOM manipulation with jQuery
- Bootstrap — Responsive Layouts Made Easy — Grid system, responsive breakpoints, and rapid prototyping
- HTTP — How the Web Communicates — Requests, responses, methods, and status codes
- React — Building Modern User Interfaces (this article) — Components, JSX, props, state, hooks, and thinking in React
What Is React?
React is a JavaScript library for building user interfaces, created by Meta (Facebook). Instead of manipulating the DOM directly — as we did with vanilla JavaScript in Part 4 and jQuery in Part 5 — React lets you describe what the UI should look like and handles the DOM updates for you.
React’s key ideas:
- Component-based — Build UIs from small, reusable, self-contained pieces
- Declarative — Describe the desired state, React figures out how to update the DOM
- Unidirectional data flow — Data flows down from parent to child via props
Getting Started
The recommended way to start a new React project is with a framework like Next.js or with a build tool like Vite:
1
2
3
4
5
# Using Vite
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
Note: Create React App (CRA) is deprecated and no longer recommended.
Components — The Building Blocks
A React component is a JavaScript function that returns JSX (markup):
1
2
3
4
5
function MyButton() {
return (
<button>I'm a button</button>
);
}
Components compose into larger UIs by nesting them:
1
2
3
4
5
6
7
8
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
Key rule: React component names must always start with a capital letter. HTML tags must be lowercase. This is how React tells them apart — <button> is an HTML element, <MyButton /> is a React component.
JSX — HTML in JavaScript
JSX is a syntax extension that looks like HTML but works inside JavaScript. It’s stricter than HTML:
- Every tag must be closed:
<br />,<img /> - A component can only return one root element — use a Fragment (
<>...</>) to wrap multiple elements without adding extra DOM nodes
1
2
3
4
5
6
7
8
function AboutPage() {
return (
<>
<h1>About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}
Embedding JavaScript in JSX
Use curly braces {} to embed JavaScript expressions inside JSX:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user = { name: "Alice", avatar: "alice.jpg" };
function Profile() {
return (
<div>
<h1>{user.name}</h1>
<img
className="avatar"
src={user.avatar}
alt={"Photo of " + user.name}
style=
/>
</div>
);
}
Note: use className instead of class for CSS classes — class is a reserved word in JavaScript.
Props — Passing Data to Components
Props let you pass data from a parent component to a child component, just like HTML attributes:
1
2
3
4
5
6
7
8
9
10
11
// Parent passes props
function App() {
return <Greeting name="Alice" age={30} />;
}
// Child receives props
function Greeting({ name, age }) {
return (
<p>Hello, {name}! You are {age} years old.</p>
);
}
Props are read-only — a child component should never modify the props it receives. If data needs to change, use state instead.
Conditional Rendering
Use standard JavaScript conditionals to render different UI based on state:
if/else
1
2
3
4
5
6
7
8
9
function Dashboard({ isLoggedIn }) {
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return <div>{content}</div>;
}
Ternary Operator
1
2
3
4
5
6
7
function Dashboard({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <AdminPanel /> : <LoginForm />}
</div>
);
}
Logical AND (&&)
When you only need to render something or nothing:
1
2
3
4
5
6
7
function Notification({ hasMessages }) {
return (
<div>
{hasMessages && <span>You have new messages!</span>}
</div>
);
}
Rendering Lists
Use JavaScript’s map() to transform an array into a list of elements:
1
2
3
4
5
6
7
8
9
10
11
12
13
const products = [
{ title: "Cabbage", id: 1 },
{ title: "Garlic", id: 2 },
{ title: "Apple", id: 3 },
];
function ProductList() {
const listItems = products.map((product) => (
<li key={product.id}>{product.title}</li>
));
return <ul>{listItems}</ul>;
}
Important: Every list item must have a key prop with a unique identifier. Keys help React track which items have changed, been added, or removed — this is critical for performance.
Handling Events
Pass event handler functions (not calls) to JSX elements:
1
2
3
4
5
6
7
8
9
10
11
function MyButton() {
function handleClick() {
alert("You clicked me!");
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
Note: pass handleClick (the function), not handleClick() (which would call it immediately during render).
Common event handlers: onClick, onChange, onSubmit, onMouseEnter, onKeyDown.
State with useState
State lets a component “remember” information between renders. Import useState from React:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
useState returns two things:
- The current state value (
count) - A setter function to update it (
setCount)
When you call the setter, React re-renders the component with the new value.
Sharing State Between Components
If two sibling components need the same state, lift the state up to their common parent and pass it down as props:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function App() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<CounterButton count={count} onClick={handleClick} />
<CounterButton count={count} onClick={handleClick} />
</div>
);
}
function CounterButton({ count, onClick }) {
return (
<button onClick={onClick}>
Clicked {count} times
</button>
);
}
Now both buttons share and update the same count.
Side Effects with useEffect
Effects let you synchronise a component with external systems — APIs, browser APIs, timers, third-party libraries, etc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState, useEffect } from "react";
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
// Cleanup: runs when roomId changes or component unmounts
return () => {
connection.disconnect();
};
}, [roomId]); // Only re-run when roomId changes
return <h1>Welcome to {roomId}!</h1>;
}
The Dependency Array
The second argument to useEffect controls when the effect runs:
| Dependency | When it runs |
|---|---|
| No array | After every render |
[] | Only on mount (once) |
[a, b] | When a or b changes |
Cleanup Functions
The function returned from useEffect is the cleanup. It runs:
- Before the effect re-runs (when dependencies change)
- When the component unmounts
Common patterns:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Event listeners
useEffect(() => {
function handleScroll() {
console.log(window.scrollY);
}
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
// Data fetching with race condition prevention
useEffect(() => {
let ignore = false;
async function fetchData() {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!ignore) {
setUser(data);
}
}
fetchData();
return () => { ignore = true; };
}, [userId]);
When NOT to Use Effects
- Transforming data for rendering — do it during render instead
- Handling user events — use event handlers
- Resetting state when a prop changes — use a
keyto force remount
Effects are an “escape hatch” from React’s declarative model. Use them only when you need to step outside React to interact with external systems.
Other Essential Hooks
Beyond useState and useEffect, React provides several other built-in hooks:
useContext — Avoid Prop Drilling
Read values from a context without passing props through every level:
1
2
3
4
5
6
import { useContext } from "react";
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
useRef — Persist Values Without Re-rendering
Hold a mutable value that doesn’t trigger re-renders (commonly used for DOM references):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useRef } from "react";
function TextInput() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>Focus input</button>
</>
);
}
useMemo — Cache Expensive Calculations
Skip recalculating a value if its dependencies haven’t changed:
1
2
3
4
5
6
7
8
9
import { useMemo } from "react";
function TodoList({ todos, filter }) {
const visibleTodos = useMemo(
() => filterTodos(todos, filter),
[todos, filter]
);
return <ul>{visibleTodos.map(/* ... */)}</ul>;
}
useCallback — Cache Function References
Prevent child components from re-rendering when a callback function hasn’t changed:
1
2
3
4
5
6
7
8
9
import { useCallback } from "react";
function Parent({ productId }) {
const handleSubmit = useCallback((data) => {
post("/product/" + productId, data);
}, [productId]);
return <Form onSubmit={handleSubmit} />;
}
Hooks at a Glance
| Hook | Purpose |
|---|---|
useState | Add state to a component |
useEffect | Synchronise with external systems |
useContext | Read context without prop drilling |
useRef | Reference a DOM node or persist a value |
useMemo | Cache an expensive computation |
useCallback | Cache a function reference |
useReducer | Manage complex state with a reducer |
useTransition | Mark state updates as non-blocking |
useId | Generate unique IDs for accessibility |
Rules of Hooks
- Only call hooks at the top level of a component or custom hook — never inside loops, conditions, or nested functions
- Only call hooks from React function components or custom hooks — never from regular JavaScript functions
Building Custom Hooks
Extract reusable logic into custom hooks — functions that start with use:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return width;
}
// Use it in any component
function Header() {
const width = useWindowWidth();
return <header>{width > 768 ? "Desktop" : "Mobile"}</header>;
}
Custom hooks let you share stateful logic between components without duplicating code.
Thinking in React
React encourages a specific mental model for building UIs:
- Break the UI into a component hierarchy — Each component should do one thing
- Build a static version first — Render the UI from data using props, no state yet
- Identify the minimal state — What’s the smallest set of state your app needs?
- Decide where state should live — Find the common parent of components that need the state
- Add inverse data flow — Pass callbacks down so child components can update parent state
Series Recap
Over this 8-part series, we’ve traced the complete arc of web development:
- HTML — The structure and content of web pages
- CSS — Visual styling, layout, and responsive design
- JavaScript — Programming logic and interactivity
- DOM — The bridge between JavaScript and HTML
- jQuery — A concise API for DOM manipulation
- Bootstrap — Rapid responsive layouts
- HTTP — The protocol that connects it all
- React — Component-based UIs with declarative state management
From hand-crafting HTML tags to building reactive component trees — these fundamentals form the foundation of modern web development.