What Is React?
React is not a full framework β it is a view library. It handles rendering the UI and reacting to state changes. For routing, data fetching, and state management you add other libraries. This composability makes it flexible but requires more architectural decisions than opinionated frameworks like Angular.
React's core ideas:
- Declarative β describe what the UI should look like for a given state; React figures out how to update the DOM.
- Component-based β build encapsulated components that manage their own state, compose them into complex UIs.
- Learn once, write anywhere β React Native reuses the same knowledge for iOS/Android apps.
JSX Syntax
JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. Babel compiles it to React.createElement() calls:
// JSX β what you write
const element = <h1 className="title">Hello, React!</h1>;
// Compiled β what Babel produces
const element = React.createElement('h1', { className: 'title' }, 'Hello, React!');
// JSX rules:
// 1. Return a single root element (or use <Fragment> / <></>)
// 2. All tags must be closed: <img />, <br />
// 3. Use className instead of class
// 4. JavaScript expressions in curly braces {}
const name = 'Alice';
const greeting = (
<div>
<h1>Hello, {name}!</h1>
<p>Today is {new Date().toDateString()}</p>
<p>2 + 2 = {2 + 2}</p>
</div>
);
// Fragment β wraps multiple elements without adding a DOM node
const items = (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
);
Functional Components
A React component is a JavaScript function that returns JSX. Functions are the modern standard (class components are legacy):
// Simplest component β no state, no props
function Hello() {
return <h1>Hello, World!</h1>;
}
// Component with props (read-only data passed from parent)
function UserCard({ name, email, avatar }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
// Using the component β looks like an HTML tag
function App() {
return (
<div>
<Hello />
<UserCard
name="Alice"
email="alice@example.com"
avatar="/alice.jpg"
/>
</div>
);
}
// Render to DOM (React 18+)
import { createRoot } from 'react-dom/client';
createRoot(document.getElementById('root')).render(<App />);
Never modify props directly β doing so violates React's data flow and causes bugs. If a child needs to update data owned by the parent, the parent passes a callback function as a prop, and the child calls it.
State with useState
State is data that can change over time. When state updates, React re-renders the component:
import { useState } from 'react';
function Counter() {
// useState returns [currentValue, setterFunction]
const [count, setCount] = useState(0); // 0 is the initial value
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// State with objects
function UserForm() {
const [user, setUser] = useState({ name: '', email: '' });
const handleChange = (e) => {
// Always spread existing state to avoid losing other fields
setUser(prev => ({ ...prev, [e.target.name]: e.target.value }));
};
return (
<form>
<input name="name" value={user.name} onChange={handleChange} />
<input name="email" value={user.email} onChange={handleChange} />
<p>Preview: {user.name} β {user.email}</p>
</form>
);
}
Click + β Count: 1 β Count: 2 β Count: 3 Click Reset β Count: 0
Side Effects with useEffect
useEffect runs code after render β for data fetching, subscriptions, DOM manipulation, and timers:
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// [] dependency array β run once on mount (componentDidMount equivalent)
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(r => r.json())
.then(data => { setUsers(data); setLoading(false); })
.catch(err => { setError(err.message); setLoading(false); });
}, []);
// [id] dependency array β run whenever id changes
// No array β run after EVERY render (usually a bug)
// Cleanup β return a function to run before next effect or unmount
useEffect(() => {
const timer = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(timer); // cleanup on unmount
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
Lists and Conditional Rendering
// Rendering lists with .map() β key prop is required
function TodoList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id} className={item.done ? 'done' : ''}>
{item.text}
</li>
))}
</ul>
);
}
// Conditional rendering β && operator
function Notification({ message }) {
return (
<div>
{message && <p className="notification">{message}</p>}
</div>
);
}
// Conditional rendering β ternary
function AuthButton({ isLoggedIn }) {
return (
<button>{isLoggedIn ? 'Log Out' : 'Log In'}</button>
);
}
// Early return pattern
function Profile({ user }) {
if (!user) return <p>Please log in</p>;
return <h1>Welcome, {user.name}!</h1>;
}
React uses key to track list items during re-renders. Never use array index as key when items can be reordered or deleted β use a stable unique ID from your data. Unstable keys cause subtle rendering bugs and hurt performance.
React Ecosystem Overview
| Category | Library | Purpose |
|---|---|---|
| Routing | React Router v6 | Client-side navigation, nested routes |
| State (global) | Zustand | Lightweight, simple global state |
| State (global) | Redux Toolkit | Predictable state container, DevTools |
| Data fetching | TanStack Query | Server state, caching, re-fetching |
| Forms | React Hook Form | Performant forms with minimal re-renders |
| Styling | Tailwind CSS / styled-components | Utility CSS or CSS-in-JS |
| Meta-framework | Next.js | SSR, SSG, API routes, file-based routing |
| Mobile | React Native | iOS + Android apps with React |
ποΈ Practical Exercise
Build a colour-picker component in React (Vite or CodeSandbox):
- Create a
ColorPickercomponent with a state variable for the selected colour. - Render 5 colour buttons. Clicking one updates the state.
- Display a large coloured preview box below the buttons.
- Show the hex value of the selected colour as text.
- Pass the selected colour back to the parent via a callback prop.
π₯ Challenge Exercise
Build a fully functional CRUD app: a "Contact Book" that lets you add, view, edit, and delete contacts. Use useState for the contacts array, a separate form component with controlled inputs, a contact list component, and a detail view. Persist contacts to localStorage using useEffect. Add a search filter using derived state (filter the contacts array based on an input value β no extra state needed).
Summary
π Summary
- React is a declarative, component-based view library powered by a virtual DOM.
- JSX is compiled to
React.createElement()β it's JavaScript, not HTML. - Functional components are plain functions that return JSX.
- Props pass read-only data from parent to child; never mutate props.
useStatemanages local state; calling the setter triggers a re-render.useEffectruns side effects after render; the dependency array controls when it runs.- Use
.map()for lists with a stable uniquekeyprop. - Use
&&or ternary for conditional rendering.
Interview Questions
- What is the virtual DOM and why does it improve performance?
- What is the difference between props and state?
- When would you use
useEffectwith an empty dependency array vs a non-empty one? - Why must list items have a
keyprop? What's wrong with using array index? - What is the difference between controlled and uncontrolled components?
Frequently Asked Questions
Create React App (CRA) is the original scaffold tool that uses Webpack under the hood. It's slow to start and build. Vite uses native ES modules and esbuild, making dev startup near-instant. For new projects in 2026, use Vite or Next.js β CRA is no longer actively maintained.
A controlled component has its value driven by React state: value={state} + onChange handler. React owns the data. An uncontrolled component manages its own value in the DOM; you read it via a ref. Controlled components are preferred for validation and dynamic UIs. Use uncontrolled components for simple file inputs or when integrating with non-React code.
React re-renders a component when its state or props change. To prevent unnecessary child re-renders: wrap the component in React.memo() (skips render if props didn't change), use useCallback to memoize callback props, and useMemo to memoize expensive computations. Premature optimization is a trap β profile first, memoize only where there's a measurable benefit.