React: The Complete Guide — Notes & Patterns
Full React syllabus — JSX, components, forms, Router, all hooks, Suspense, Server Components notes, and a progress-tracker project with real-life examples.
React HOME — what this course covers
This Rishtaara React course teaches you step by step — from your first component to hooks, routing, and a full mini project.
React is a JavaScript library for building user interfaces. You build small reusable pieces called components and combine them into full apps.
Real-life example: React is like LEGO blocks. Each block (component) is simple. You snap many blocks together to build a house, a car, or a whole city (your app).
- Basics: JSX, components, props, events, lists, forms
- Styling, React Router, and advanced UI patterns
- Hooks: useState, useEffect, useContext, and more
- Final project: course progress tracker app
Introduction — what is React?
React was created by Meta (Facebook). It helps you build fast, interactive web apps without reloading the whole page for every small change.
React uses a Virtual DOM — it compares the old UI with the new UI and updates only what changed. Sites like Netflix, Instagram, and Rishtaara use React.
Real-life example: Without React, changing one price on a menu might repaint the entire restaurant wall. With React, only that one price tag gets updated.
- Component-based — build once, reuse everywhere
- Declarative — you describe what UI should look like for each state
- Huge ecosystem — React Router, Next.js, many UI libraries
- Strong job market — one of the most in-demand frontend skills
Get Started — create your first React app
Today most developers use Vite to start a React project. It is fast and simple. Create React App (CRA) was popular for years but is now less recommended for new projects.
You need Node.js installed. Then run a few commands in your terminal. Your app will open at localhost with hot reload — save a file and see changes instantly.
Real-life example: Vite is like a ready-made kitchen with stove and utensils. You walk in and start cooking (coding) instead of building the kitchen from bricks.
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev# Older way — still works but Vite is preferred for new apps
npx create-react-app my-app
cd my-app
npm startReact First App — Hello World
A React app usually has src/main.jsx (entry point) and src/App.jsx (your first component). main.jsx mounts App into a div with id root in index.html.
App is a function that returns JSX — it looks like HTML but lives inside JavaScript.
Real-life example: index.html is the empty picture frame. main.jsx hangs the picture (App) inside the frame on your wall (the browser).
function App() {
return (
<div>
<h1>Hello, Rishtaara!</h1>
<p>Welcome to your first React app.</p>
</div>
);
}
export default App;import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);Render HTML — how React shows UI
React does not write HTML files for every screen. It renders components into the DOM — the live tree of HTML elements in the browser.
The entry file finds an element (usually div#root) and tells React to render your App component inside it.
Real-life example: The DOM is a live plant. React is the gardener who trims one branch when a leaf turns yellow — not replanting the whole garden.
createRoot (React 18+)
React 18 replaced ReactDOM.render with createRoot. createRoot enables concurrent features and is the modern way to start any app.
You call createRoot once on the container, then call .render() with your JSX tree.
Real-life example: createRoot is like hiring a new stage manager (React 18) who can run two rehearsals at once. The old manager (ReactDOM.render) only did one show at a time.
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(<App />);// React 17 and below — upgrade to createRoot
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));Upgrade Notes — moving to React 18+
If you see ReactDOM.render in old tutorials, replace it with createRoot. Wrap your app in StrictMode during development to catch common mistakes.
React 18 also changed how batching works — multiple setState calls in async code now batch into one re-render automatically.
Real-life example: Upgrading React is like switching to a newer phone OS — most apps still work, but a few old settings menus move to new places.
- Use createRoot instead of ReactDOM.render
- StrictMode runs extra checks in development only
- Read the official React 19 upgrade guide when you bump versions
- Test your app after upgrading — especially third-party libraries
ES6 for React — why it matters
Modern React code uses ES6+ JavaScript features every day. You will see them in every tutorial and every job interview.
The four most common in React are: destructuring, arrow functions, import/export modules, and the spread operator.
Real-life example: ES6 is like learning the local language before moving to a new city. You can survive without it, but daily life (React code) is much easier with it.
Destructuring
Destructuring pulls values out of objects and arrays into short variable names. React uses it for props, state, and hook return values.
Real-life example: Destructuring is like opening a lunch box and placing rice, dal, and roti on separate plates instead of eating straight from the box.
const user = { name: "Asha", age: 22, city: "Mumbai" };
const { name, age } = user;
const colors = ["red", "green", "blue"];
const [first, second] = colors;
// In React — props destructuring
function Profile({ name, age }) {
return <p>{name} is {age}</p>;
}Arrow Functions
Arrow functions are a short way to write functions. They are common for event handlers and small components.
Real-life example: Arrow functions are like text shortcuts on your phone — faster to type, same meaning as the long form.
// Classic function
function add(a, b) {
return a + b;
}
// Arrow function
const addArrow = (a, b) => a + b;
// In React — click handler
<button onClick={() => setCount(count + 1)}>+1</button>Modules — import and export
Each React file is a module. You export components and import them in other files. Default export = one main thing per file. Named export = many exports.
Real-life example: Modules are like separate chapters in a textbook. You open Chapter 3 (import) when you need a recipe from it.
// Button.jsx
export function Button({ label }) {
return <button>{label}</button>;
}
// App.jsx
import { Button } from "./Button.jsx";
// Default export pattern
export default function App() {
return <Button label="Join Rishtaara" />;
}Spread Operator
The spread operator (...) copies arrays and objects. In React you use it to update state without mutating the old object.
Real-life example: Spread is like photocopying a form, writing one new answer on the copy, and keeping the original form unchanged.
const user = { name: "Ravi", score: 40 };
// Add/change one field — new object, old one untouched
const updated = { ...user, score: 55 };
const todos = ["Learn JSX", "Learn props"];
const more = [...todos, "Build project"];JSX Intro — JavaScript + XML
JSX lets you write HTML-like syntax inside JavaScript. Babel (or Vite) compiles JSX into React.createElement calls.
JSX is not HTML — it has small differences you must remember.
Real-life example: JSX is like writing a play script that looks like a conversation. The theater (React) turns the script into real actors on stage (DOM elements).
const element = <h1>Hello, React!</h1>;
function Welcome() {
return (
<div>
<h1>Rishtaara React Course</h1>
<p>Learn step by step.</p>
</div>
);
}JSX Expressions — { } curly braces
Put any valid JavaScript expression inside { }. Variables, math, function calls, and ternary operators all work.
Real-life example: Curly braces are like blanks in a Mad Libs story — you fill in dynamic words (JavaScript values) inside a fixed sentence (JSX).
const name = "Asha";
const score = 92;
function Report() {
return (
<div>
<p>Student: {name}</p>
<p>Double score: {score * 2}</p>
<p>Status: {score >= 40 ? "Pass" : "Fail"}</p>
</div>
);
}JSX Attributes — className, htmlFor, camelCase
Use className instead of class. Use htmlFor instead of for. Event names are camelCase: onClick, onChange, onSubmit.
Self-closing tags need a slash: <img />, <input />, <br />.
Real-life example: JSX attributes speak JavaScript dialect — same ideas as HTML, slightly different spelling so JS does not get confused with keywords.
<label htmlFor="email">Email</label>
<input id="email" className="input-field" type="email" />
<img src="/logo.png" alt="Rishtaara logo" />
<button onClick={() => console.log("clicked")}>Click</button>If Statements in JSX
You cannot write if/else directly inside JSX return. Use a ternary (? :), logical &&, or compute the value before return.
Real-life example: JSX return is one sentence on stage. You pick the right word (ternary or &&) before speaking — you do not pause mid-sentence for a full if-block.
function Greeting({ isLoggedIn, name }) {
return (
<div>
{isLoggedIn ? <p>Welcome back, {name}!</p> : <p>Please log in.</p>}
{name === "Admin" && <span className="badge">Admin</span>}
</div>
);
}
// Or compute before return
function Status({ score }) {
let message;
if (score >= 40) {
message = "Pass";
} else {
message = "Fail";
}
return <p>{message}</p>;
}Function Components — the modern standard
A React component is a JavaScript function that returns JSX. Function components are the default style in modern React.
Name components with PascalCase — UserCard, not userCard. React treats lowercase tags as HTML and uppercase as components.
Real-life example: A component is a reusable recipe card. Write the recipe once, cook it many times with different ingredients (props).
function Header() {
return <header><h1>Rishtaara</h1></header>;
}
function Footer() {
return <footer><p>© 2026 Rishtaara</p></footer>;
}
function App() {
return (
<>
<Header />
<main><p>Learn React today.</p></main>
<Footer />
</>
);
}Component composition
Build small components and nest them inside bigger ones. This keeps code readable and reusable — the core idea of React.
Real-life example: Composition is like building a sandwich — bread, filling, and sauce are separate items you stack into one meal (page).
function CourseCard({ title, lessons }) {
return (
<article className="card">
<h2>{title}</h2>
<p>{lessons} lessons</p>
</article>
);
}
function CourseGrid() {
return (
<section>
<CourseCard title="React Guide" lessons={28} />
<CourseCard title="JavaScript" lessons={30} />
</section>
);
}Class Components — what they are
Before hooks (2019), React class components were the main way to hold state and lifecycle logic. You extend React.Component and use this.state.
You will still see class components in old codebases and older tutorials. New projects should use function components + hooks.
Real-life example: Class components are like old landline phones — they still work, but most new homes install smartphones (function components).
import { Component } from "react";
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
// Usage
<Welcome name="Asha" />Class state (legacy pattern)
Classes used this.state and this.setState to update data. Lifecycle methods like componentDidMount ran side effects.
Real-life example: this.setState in a class is like updating a paper ledger — you write a new row instead of erasing the old one incorrectly.
class Counter extends Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}Why function components won
Hooks give function components state and effects without classes. Less boilerplate, easier to test, and better TypeScript support.
Real-life example: Hooks are like a universal remote that replaced five separate remotes (constructor, render, lifecycle methods) with one simple device.
- Write new code with function components + hooks
- Read class code when maintaining legacy apps
- Do not mix patterns in one component
Props — passing data to components
Props (properties) are read-only inputs to a component. Parent passes props down; child uses them in JSX. Props never change inside the child — use state for that.
Real-life example: Props are like a name tag at a conference. The organizer writes your name; you wear it but do not rewrite it yourself.
function UserBadge({ name, role }) {
return (
<div className="badge">
<strong>{name}</strong>
<span>{role}</span>
</div>
);
}
<UserBadge name="Ravi" role="Student" />Props Destructuring
Destructure props in the function parameter for clean code. You can also set default values.
Real-life example: Destructuring props is unpacking a delivery box at the door — take out exactly what you need instead of carrying the whole box inside.
function CourseCard({
title,
instructor = "Rishtaara Team",
rating = 4.5,
featured = false,
}) {
return (
<article className={featured ? "card featured" : "card"}>
<h2>{title}</h2>
<p>By {instructor}</p>
<span>⭐ {rating}</span>
</article>
);
}Children — composition with props.children
Content placed between opening and closing tags becomes props.children. Use it for wrappers, cards, and layout shells.
Real-life example: children is like a picture frame — the frame (component) stays the same; you swap the photo (children) anytime.
function Card({ title, children }) {
return (
<div className="card">
{title && <h3>{title}</h3>}
{children}
</div>
);
}
<Card title="Tip">
<p>Practice JSX every day on Rishtaara-style mini apps.</p>
</Card>React Events — onClick, onChange, onSubmit
React events use camelCase names: onClick, onChange, onSubmit. Pass a function — not a string like HTML onclick.
The event object is a SyntheticEvent — similar to the browser event but works the same across browsers.
Real-life example: React events are like a reception desk bell. You do not shout instructions (inline strings); you register who handles the ring (a function).
function LikeButton() {
function handleClick() {
alert("Thanks for liking Rishtaara!");
}
return <button onClick={handleClick}>Like</button>;
}
function SearchBox() {
function handleChange(event) {
console.log("Typing:", event.target.value);
}
return <input type="text" onChange={handleChange} placeholder="Search courses" />;
}Prevent default and stop propagation
Call event.preventDefault() on form submit to stop page reload. Call event.stopPropagation() when a child click should not bubble to parent.
Real-life example: preventDefault is telling the post office not to redirect your letter to a new address — you handle it locally in JavaScript.
function LoginForm() {
function handleSubmit(e) {
e.preventDefault();
console.log("Form handled in React — no page reload");
}
return (
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Email" />
<button type="submit">Log in</button>
</form>
);
}Conditionals — showing UI based on state
Use ternary (? :), logical &&, or if/else before return to show different UI for different conditions.
Real-life example: Conditionals are like a shop sign — "Open" when the owner is inside, "Closed" when they are away.
function Dashboard({ isLoggedIn, error, items }) {
if (!isLoggedIn) {
return <p>Please log in to see your Rishtaara dashboard.</p>;
}
return (
<div>
{error && <p className="error">{error}</p>}
{items.length === 0 ? (
<p>No courses yet — start learning!</p>
) : (
<ul>{items.map((item) => <li key={item.id}>{item.title}</li>)}</ul>
)}
</div>
);
}Lists — rendering arrays with map()
Use array.map() to turn data into a list of JSX elements. Each item should become one row, card, or list element.
Real-life example: map() is like a printing machine — one template (li or card), many copies (one per array item).
const topics = ["JSX", "Props", "Hooks", "Router"];
function TopicList() {
return (
<ul>
{topics.map((topic) => (
<li key={topic}>{topic}</li>
))}
</ul>
);
}Keys — why React needs them
Each sibling in a list needs a unique key prop. Keys help React know which item changed, was added, or removed.
Use stable IDs from a database — not array index when the list can reorder, filter, or delete items.
Real-life example: Keys are roll numbers in a classroom. When one student moves seats, the teacher still knows who is who by roll number — not by seat position alone.
function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map((todo) => (
<li
key={todo.id}
className={todo.done ? "done" : ""}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</li>
))}
</ul>
);
}React Forms — controlled components
In a controlled component, React state is the single source of truth for input values. Every keystroke updates state, which re-renders the input.
Real-life example: Controlled inputs are like a teacher checking your homework live — every word you write is copied to their notebook (state) instantly.
import { useState } from "react";
function NameForm() {
const [name, setName] = useState("");
return (
<form>
<label>
Name:
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<p>Hello, {name || "guest"}!</p>
</form>
);
}Submit — handle form send
Use onSubmit on the form and e.preventDefault() to handle data in JavaScript without reloading the page.
Real-life example: Submit is pressing Send on WhatsApp — preventDefault keeps you in the chat app instead of opening a new blank browser tab.
function SignupForm() {
const [email, setEmail] = useState("");
function handleSubmit(e) {
e.preventDefault();
if (!email.includes("@")) {
alert("Enter a valid email");
return;
}
console.log("Signed up:", email);
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Sign up</button>
</form>
);
}Textarea
Textarea works like input — bind value and onChange to state for multi-line text.
Real-life example: Textarea is a big notebook page; input is a single-line sticky note.
const [bio, setBio] = useState("");
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
rows={4}
placeholder="Tell us about yourself"
/>Select — dropdown
Select uses value and onChange like input. Option values are strings unless you convert them.
Real-life example: Select is a restaurant menu — you pick one dish (option) from the list.
const [course, setCourse] = useState("react");
<select value={course} onChange={(e) => setCourse(e.target.value)}>
<option value="react">React</option>
<option value="js">JavaScript</option>
<option value="css">CSS</option>
</select>Multiple Inputs — one handler
Store form fields in one state object. Use name attributes and e.target.name to update the right field.
Real-life example: One handler for many inputs is like one receptionist updating different forms — they read the form name tag (name attribute) to know which file to update.
const [form, setForm] = useState({ name: "", email: "", course: "react" });
function handleChange(e) {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
}
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />Checkbox
Checkbox uses checked instead of value. Toggle boolean state on change.
Real-life example: Checkbox is agreeing to terms — checked means yes, unchecked means no.
const [agreed, setAgreed] = useState(false);
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
I agree to the Rishtaara terms
</label>Radio buttons
Radio buttons share one name and one piece of state — only one option selected at a time.
Real-life example: Radio buttons are like choosing one ice cream flavor — pick vanilla OR chocolate, not both in the same group.
const [plan, setPlan] = useState("free");
<label>
<input
type="radio"
name="plan"
value="free"
checked={plan === "free"}
onChange={(e) => setPlan(e.target.value)}
/>
Free
</label>
<label>
<input
type="radio"
name="plan"
value="pro"
checked={plan === "pro"}
onChange={(e) => setPlan(e.target.value)}
/>
Pro
</label>CSS Styling — className and inline styles
Use className to attach CSS classes from a .css file. Inline style={{ }} accepts a JavaScript object with camelCase properties.
Real-life example: className is wearing a school uniform (external CSS). Inline style is putting on one sticker for a single day.
import "./App.css";
function Hero() {
return (
<div className="hero">
<h1 style={{ color: "#0891b2", fontSize: "2rem" }}>Rishtaara</h1>
</div>
);
}.hero {
padding: 2rem;
text-align: center;
background: #f0fdfa;
}CSS Modules
Import styles as an object: import styles from './Button.module.css'. Class names are scoped to the component — no global clashes.
Real-life example: CSS Modules are like name tags on clothes at a hostel laundry — your shirt never gets mixed with someone else's.
import styles from "./Button.module.css";
function Button({ children }) {
return <button className={styles.primary}>{children}</button>;
}.primary {
background: #0891b2;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
}CSS-in-JS (brief)
Libraries like styled-components let you write CSS inside JavaScript template strings. Popular in some teams; not required for beginners.
Real-life example: CSS-in-JS is like writing recipe and serving dish on the same card — convenient, but you need the right tool installed.
// npm install styled-components
import styled from "styled-components";
const Button = styled.button`
background: #0891b2;
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
`;
function App() {
return <Button>Join Rishtaara</Button>;
}Sass note
Sass (SCSS) adds variables, nesting, and mixins. Vite supports .scss files out of the box — import them like normal CSS.
Real-life example: Sass is a neat recipe notebook with shortcuts; the browser still eats plain CSS after compilation.
- Rename file to .scss and import in your component
- Use $variables for brand colors
- Nest selectors for cleaner component-scoped styles
React Router — client-side navigation
React Router lets multiple pages live in one React app without full page reloads. The URL changes; React swaps components.
Install: npm install react-router-dom. Wrap your app in BrowserRouter.
Real-life example: React Router is like TV channels — same TV (app), different shows (pages) when you change the channel (URL).
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/learn">Learn</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/learn" element={<LearnPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}Routes & Link
Route maps a URL path to a component. Link creates accessible navigation anchors that do not reload the page.
Real-life example: Routes are floor numbers in a building directory. Link is the elevator button that takes you there without rebuilding the building.
<Routes>
<Route path="/courses" element={<CourseList />} />
<Route path="/courses/:slug" element={<CourseDetail />} />
</Routes>useNavigate — programmatic navigation
Call navigate('/path') after login, logout, or form success instead of showing a Link.
Real-life example: useNavigate is a GPS redirect — after you pay, the app automatically drives you to the receipt page.
import { useNavigate } from "react-router-dom";
function LoginForm() {
const navigate = useNavigate();
function handleLogin() {
// ... auth logic
navigate("/dashboard");
}
return <button onClick={handleLogin}>Log in</button>;
}URL Params — useParams
Dynamic segments like :slug become params you read with useParams().
Real-life example: Params are the order ID in a food delivery URL — same page layout, different order details each time.
import { useParams } from "react-router-dom";
function CourseDetail() {
const { slug } = useParams();
return <h1>Course: {slug}</h1>;
}
// URL /courses/react-complete-guide → slug = "react-complete-guide"Portals — render outside the parent DOM tree
ReactDOM.createPortal lets you render a modal or tooltip into document.body while keeping it in the same React component tree for events and state.
Real-life example: A portal is a pop-up stall outside the main shop — separate location, still run by the same owner (your React app).
import { createPortal } from "react-dom";
function Modal({ open, children, onClose }) {
if (!open) return null;
return createPortal(
<div className="overlay" onClick={onClose}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}Suspense — loading boundaries
Suspense shows a fallback UI while child components load data or lazy-loaded code. Pair with React.lazy for code splitting.
Real-life example: Suspense is a "Please wait" sign at a restaurant while the kitchen (lazy component) prepares your order.
import { Suspense, lazy } from "react";
const HeavyChart = lazy(() => import("./HeavyChart.jsx"));
function Dashboard() {
return (
<Suspense fallback={<p>Loading chart...</p>}>
<HeavyChart />
</Suspense>
);
}Transitions — useTransition
useTransition marks state updates as non-urgent so typing stays smooth while a heavy list re-filters.
Real-life example: useTransition is letting urgent guests (typing) through the door first while heavy luggage (big re-render) waits in the queue.
import { useState, useTransition } from "react";
function SearchList({ items }) {
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value);
startTransition(() => {
// heavy filter update marked as transition
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <span>Updating...</span>}
</>
);
}Forward Ref — pass refs to child DOM nodes
forwardRef lets a parent get a ref to a child's DOM element — useful for focus, measurements, or third-party libraries.
Real-life example: forwardRef is like giving your manager a direct phone line to your desk — they reach you without going through the whole office chain.
import { forwardRef, useRef } from "react";
const FancyInput = forwardRef(function FancyInput(props, ref) {
return <input ref={ref} className="fancy" {...props} />;
});
function Form() {
const inputRef = useRef(null);
return (
<>
<FancyInput ref={inputRef} placeholder="Focus me" />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
</>
);
}HOC — Higher-Order Components
A HOC is a function that takes a component and returns a new component with extra props or behavior. Hooks replaced many HOC use cases, but you will see withAuth(UserPanel) patterns in legacy code.
Real-life example: A HOC is a gift wrapper — same gift inside (component), extra ribbon and tag (added behavior) outside.
function withLoading(Wrapped) {
return function WithLoading(props) {
if (props.loading) return <p>Loading...</p>;
return <Wrapped {...props} />;
};
}
const UserListWithLoading = withLoading(UserList);Certificate & Exercises
On Rishtaara, the real skill is building small apps — certificates celebrate progress but projects prove ability.
Real-life example: A certificate is a finish-line photo — fun to share, but the training miles (practice) made you strong.
- Redo each lesson example in your own Vite project
- Change props and state to see what breaks — best way to learn
- Build a counter, a todo list, and a login form before hooks section
React Compiler (preview)
The React Compiler automatically optimizes components so you write plain code without manual memo everywhere. It is rolling out with React 19 — you will learn more in the hooks section.
Real-life example: The compiler is like an smart editor fixing grammar — you write naturally; the tool polishes performance.
Quiz & Syllabus
Test yourself after basics: JSX rules, props vs state, keys, controlled forms, and Router terms.
Real-life example: A quiz is a mock exam before the real test — low stakes, high learning.
- MCQ practice: /mcq/react-mcq
- This course syllabus: 28 lessons covering modern React topics
- Full notes: /knowledge/react-complete-guide
Study Plan & Server note
Week 1: lessons 1–10 (JSX through forms). Week 2: styling, router, advanced UI. Week 3: hooks 15–24. Week 4: patterns, server notes, interview, project.
React runs in the browser by default. Server frameworks (Next.js) run React on the server too — covered briefly later.
Real-life example: A study plan is a gym schedule — same exercises daily beat random cramming before exam day.
Interview & Bootcamp notes
Interviewers ask: virtual DOM, props vs state, hooks rules, keys, controlled vs uncontrolled inputs, and React Router basics.
Bootcamps compress this into weeks — this self-paced course lets you replay any lesson.
Real-life example: Interview prep is rehearsing answers before meeting the director — you know your lines, not reading for the first time on stage.
- Interview prep: /interview/react-interview
- Next: Hooks section starting at lesson 15
- Then: Tailwind CSS course for styling at scale
What is Hooks?
Hooks are special functions that let function components use state, side effects, context, and more — without writing class components.
They start with use: useState, useEffect, useContext. You call them at the top level of your component, not inside loops or if statements.
Real-life example: Hooks are power outlets in a modern kitchen. Old kitchens needed built-in appliances (classes). Now you plug in whatever tool you need (useState, useEffect).
Rules of Hooks
Only call hooks at the top level — not inside conditions, loops, or nested functions. Only call hooks from React function components or custom hooks.
Real-life example: Rules of Hooks are like airport security rules — everyone follows the same line order; skipping the line breaks the system for everyone.
- Top level only — before any early return if possible
- Custom hooks must start with use (useFetch, useAuth)
- React DevTools and ESLint plugin help catch mistakes
useState — local component state
useState returns a value and a setter function. Calling the setter tells React to re-render with the new value.
State is private to the component unless you lift it up or share via Context.
Real-life example: useState is a whiteboard in your room. You erase and write new numbers (setState); visitors see the updated board (re-render).
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount((c) => c - 1)}>-1</button>
</div>
);
}Object and array state
Never mutate state directly. For objects and arrays, spread the old value and change one field.
Real-life example: Updating object state is editing a Google Doc copy — you save a new version; you do not scribble on the archived PDF.
const [user, setUser] = useState({ name: "", score: 0 });
setUser((prev) => ({ ...prev, score: prev.score + 10 }));
// Functional update when new state depends on old
setCount((c) => c + 1);useEffect — side effects after render
useEffect runs after React paints the screen. Use it for data fetching, timers, subscriptions, and syncing with non-React systems.
Do not use useEffect for things you can compute during render — that belongs in plain JavaScript or useMemo.
Real-life example: useEffect is checking your mailbox after you finish cooking dinner — a separate task that happens after the main job (render) is done.
import { useState, useEffect } from "react";
function CourseList() {
const [courses, setCourses] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function load() {
try {
const res = await fetch("/api/courses");
const data = await res.json();
if (!cancelled) setCourses(data);
} finally {
if (!cancelled) setLoading(false);
}
}
load();
return () => { cancelled = true; };
}, []);
if (loading) return <p>Loading...</p>;
return <ul>{courses.map((c) => <li key={c.id}>{c.title}</li>)}</ul>;
}Dependency array
[] runs once on mount. [userId] re-runs when userId changes. No array runs every render — rarely needed and often a bug.
Return a cleanup function to cancel timers, abort fetch, or remove listeners.
Real-life example: Dependencies are reminder alarms — [] means once on move-in day; [userId] means every time you change apartment roommate.
- [] — fetch initial data, add one-time listeners
- [dep] — refetch when dep changes
- cleanup — return function from useEffect
useContext — share data without prop drilling
createContext makes a context. Provider wraps part of your tree and passes a value. useContext reads that value in any child — no passing props through every level.
Real-life example: Context is Wi-Fi in a building. You connect once at the router (Provider); every room (component) gets signal without running cables through each door (props).
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext("light");
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
function Toggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
{theme === "light" ? "Dark mode" : "Light mode"}
</button>
);
}When to use Context
Good for theme, auth user, locale — data many components need but that changes rarely.
For fast-changing or complex state, consider Zustand or Redux instead of one giant context.
Real-life example: Context is the school notice board — everyone reads it; you do not pass the same announcement through every classroom door.
- Split contexts — AuthContext, ThemeContext — not one mega context
- Memoize provider value when it is an object to avoid extra re-renders
- Custom hooks (useAuth) hide context details from UI components
useRef — DOM access and mutable values
useRef returns a { current } object that persists across renders. Changing ref.current does not trigger a re-render.
Common uses: focus an input, measure a div, store a timer ID, or keep previous prop value.
Real-life example: useRef is a sticky note on your monitor — you update it anytime without telling the whole office to refresh their screens.
import { useRef } from "react";
function SearchBox() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<>
<input ref={inputRef} placeholder="Search Rishtaara" />
<button onClick={focusInput}>Focus search</button>
</>
);
}Ref vs state
Use state when UI must update. Use ref when you need to remember something without re-rendering.
Real-life example: State is the scoreboard everyone watches. Ref is your private tally marks on paper — useful, but the crowd does not need to see every mark.
const timerRef = useRef(null);
function startTimer() {
timerRef.current = setInterval(() => console.log("tick"), 1000);
}
function stopTimer() {
clearInterval(timerRef.current);
}useReducer — state with actions
useReducer is like useState for complex state logic. You pass a reducer function and initial state. Dispatch actions instead of calling many setters.
Real-life example: useReducer is a bank teller window — you hand a slip (action); the teller (reducer) updates the ledger (state) by fixed rules.
import { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}When to prefer useReducer
Use when next state depends on many fields, or when multiple sub-components dispatch the same actions (often with Context).
Real-life example: useReducer is a traffic light controller — same rules every time; you only send "green" or "red" commands.
- Todo apps with add, toggle, delete, filter
- Multi-step forms with back/next validation
- Combined with useContext for global reducer stores
useCallback — stable function reference
useCallback returns the same function instance between renders unless dependencies change. Useful when passing callbacks to memoized child components.
Real-life example: useCallback is saving a contact number — same number card in your phone unless your friend changes their number (dependency).
import { useCallback, useState, memo } from "react";
const Row = memo(function Row({ item, onSelect }) {
return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});
function List({ items }) {
const [filter, setFilter] = useState("");
const handleSelect = useCallback((id) => {
console.log("Selected", id);
}, []);
return (
<>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<ul>
{items.map((item) => (
<Row key={item.id} item={item} onSelect={handleSelect} />
))}
</ul>
</>
);
}Do not overuse useCallback
Most apps do not need useCallback on every handler. Add it when Profiler shows unnecessary child re-renders.
Real-life example: useCallback is premium packaging — helpful for fragile items, wasteful for every grocery bag.
useMemo — cache expensive calculations
useMemo runs a function and caches the result until dependencies change. Use it for heavy sorting, filtering, or derived data — not for every variable.
Real-life example: useMemo is meal prepping on Sunday — you cook once (calculate), eat all week (reuse) until ingredients (deps) change.
import { useMemo, useState } from "react";
function UserTable({ users }) {
const [sort, setSort] = useState("name");
const sorted = useMemo(() => {
return [...users].sort((a, b) => a[sort].localeCompare(b[sort]));
}, [users, sort]);
return (
<ul>
{sorted.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}useMemo vs useCallback
useMemo caches a value. useCallback caches a function. useCallback(fn, deps) is roughly useMemo(() => fn, deps).
Real-life example: useMemo stores the cooked dish. useCallback stores the recipe card to cook the same dish again.
- Profile first with React DevTools
- React.memo on components + useMemo on props can work together
- Premature memoization adds complexity without speed gains
Custom Hooks — extract reusable logic
A custom hook is a function that calls other hooks. Name it useSomething. It lets you share stateful logic between components without copy-paste.
Real-life example: Custom hooks are family recipes — write once, any kitchen (component) can follow the same steps.
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then((r) => r.json())
.then((d) => { if (!cancelled) setData(d); })
.catch((e) => { if (!cancelled) setError(e.message); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
function Courses() {
const { data, loading, error } = useFetch("/api/courses");
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <ul>{data.map((c) => <li key={c.id}>{c.title}</li>)}</ul>;
}More custom hook ideas
useLocalStorage, useDebounce, useWindowSize, useAuth — all follow the same pattern: hooks inside, return values and functions.
Real-life example: useLocalStorage is a pocket notebook that syncs with a locker (browser storage) — open any component, same notes appear.
function useLocalStorage(key, initial) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}React Compiler — automatic optimization
The React Compiler (formerly React Forget) analyzes your components at build time and inserts memoization where needed. You write simple code; the compiler reduces wasted re-renders.
It is part of the React 19 story and works with modern build tools. You opt in with a Babel plugin or framework support.
Real-life example: The compiler is an automatic proofreader — you write the essay (components); it fixes repetitive sentences (redundant renders) without you marking every paragraph manually.
Why the compiler matters
Before the compiler, teams sprinkled React.memo, useMemo, and useCallback everywhere — easy to get wrong. The compiler aims to make performance the default.
You still learn hooks and memo rules — they help you read code and work on older projects without the compiler.
Real-life example: Compiler is cruise control on a highway — you still learn to drive (React basics), but long trips need less foot work.
- Write idiomatic React first; measure with Profiler
- Compiler does not replace good keys, state design, or list virtualization
- Enable gradually in new apps; test thoroughly after turning on
Lifting State Up
When two siblings need the same data, move state to their closest common parent and pass props down. Pass callbacks to update state from children.
Real-life example: Lifting state is one thermostat for two rooms — both rooms read the same temperature; one control updates both.
function App() {
const [query, setQuery] = useState("");
return (
<>
<SearchBar query={query} onChange={setQuery} />
<ResultsList query={query} />
</>
);
}Composition over inheritance
React favors composing components with props and children instead of deep class inheritance trees.
Real-life example: Composition is stacking LEGO — you rarely melt pieces together (inheritance); you snap different bricks (components).
- Small focused components
- children prop for flexible wrappers
- Render props or hooks instead of subclassing
Controlled vs uncontrolled inputs
Controlled: React state owns the value. Uncontrolled: the DOM owns it; you read with ref when needed. Prefer controlled for forms in React apps.
Real-life example: Controlled is a typed document you edit live. Uncontrolled is a paper form you only photograph at the end.
React.memo recap
React.memo wraps a component and skips re-render if props are shallow-equal. Pair with stable callbacks when needed.
Real-life example: React.memo is a "no need to repaint" sign on a wall — if nothing changed, skip the work.
import { memo } from "react";
const ExpensiveCard = memo(function ExpensiveCard({ title, onClick }) {
return <article onClick={onClick}>{title}</article>;
});React Server Components (RSC) — what they are
Server Components run on the server only. They can fetch data and access backend resources directly. They do not ship their JavaScript to the browser — smaller bundles.
Client Components (with "use client" in Next.js) still handle interactivity — clicks, useState, useEffect.
Real-life example: Server Components are kitchen prep done before the plate leaves the kitchen — customers get the finished dish faster; they do not need to see every chopping step in the dining room.
Server vs client — simple split
Use Server Components for static content, data fetching, and SEO-heavy pages. Use Client Components for forms, buttons, and browser APIs.
Next.js App Router is the most common place beginners meet RSC. Plain Vite React apps are client-only unless you add a framework.
Real-life example: Server is the warehouse stocking shelves overnight. Client is the shop floor where customers pick items and ask questions live.
- RSC: zero client JS for that component tree
- Client: hooks, events, localStorage, animations
- Learn client React first — then add Next.js for server features
Core concepts interviewers ask
Expect questions on: What is JSX? Virtual DOM? Props vs state? Why keys? Rules of hooks? Controlled inputs? Difference between useEffect and useLayoutEffect?
Real-life example: Interviews are oral exams — they check if you can explain ideas simply, not only copy code from tutorials.
- Virtual DOM — lightweight copy; diff then patch real DOM
- Props down, events up — one-way data flow
- Keys — stable identity for list items
- useEffect cleanup — avoid memory leaks and stale updates
Common coding tasks
Build a counter, todo list, fetch-and-display list, debounced search, or simple form validation. Practice without copy-paste first, then check answers.
Real-life example: Coding tasks are driving tests — examiner watches you park (structure), not just whether the car moves.
- Todo: add, toggle, delete with immutable state
- Fetch: loading, error, empty, success UI states
- Modal: conditional render or portal
Practice on Rishtaara
Use MCQs for quick recall and interview Q&A for speaking practice out loud.
Real-life example: MCQs are flashcards. Interview pages are mock conversations with a friend before the real meeting.
- MCQ practice: /mcq/react-mcq
- Interview prep: /interview/react-interview
- Full guide: /knowledge/react-complete-guide
- Next course: Tailwind CSS — /learn/web-development/tailwind-css-fundamentals
Project overview
Build a Course Progress Tracker for Rishtaara lessons. Mark lessons complete, filter by status, and see progress percentage. Uses useState, useEffect, useMemo, and localStorage.
Copy the full App.jsx below into a Vite React project. Run npm run dev and tick lessons as you finish this course.
Real-life example: This app is your personal report card — each checkbox is a lesson mastered, like stars on a language-learning app.
- Track 28 React lessons with checkboxes
- Filter: All / Done / Todo
- Progress bar with percentage
- Saves to localStorage — survives page refresh
Complete App.jsx — copy and run
import { useState, useEffect, useMemo } from "react";
import "./App.css";
const LESSONS = [
{ id: 1, title: "React Home & Intro", slug: "react-home-intro" },
{ id: 2, title: "Render & createRoot", slug: "react-render-upgrade" },
{ id: 3, title: "ES6 for React", slug: "react-es6" },
{ id: 4, title: "JSX", slug: "react-jsx" },
{ id: 5, title: "Function Components", slug: "react-components" },
{ id: 6, title: "Class Components", slug: "react-class" },
{ id: 7, title: "Props & Children", slug: "react-props" },
{ id: 8, title: "Events & Conditionals", slug: "react-events-conditionals" },
{ id: 9, title: "Lists & Keys", slug: "react-lists" },
{ id: 10, title: "Forms", slug: "react-forms" },
{ id: 11, title: "Styling", slug: "react-styling" },
{ id: 12, title: "React Router", slug: "react-router" },
{ id: 13, title: "Advanced UI", slug: "react-advanced-ui" },
{ id: 14, title: "Practice Path", slug: "react-practice-path" },
{ id: 15, title: "Hooks Intro", slug: "hooks-intro" },
{ id: 16, title: "useState", slug: "hook-usestate" },
{ id: 17, title: "useEffect", slug: "hook-useeffect" },
{ id: 18, title: "useContext", slug: "hook-usecontext" },
{ id: 19, title: "useRef", slug: "hook-useref" },
{ id: 20, title: "useReducer", slug: "hook-usereducer" },
{ id: 21, title: "useCallback", slug: "hook-usecallback" },
{ id: 22, title: "useMemo", slug: "hook-usememo" },
{ id: 23, title: "Custom Hooks", slug: "hook-custom" },
{ id: 24, title: "React Compiler", slug: "react-compiler-note" },
{ id: 25, title: "Patterns Review", slug: "react-patterns-review" },
{ id: 26, title: "Server Components", slug: "react-server-components-note" },
{ id: 27, title: "Interview Prep", slug: "react-interview-prep" },
{ id: 28, title: "This Project!", slug: "react-project" },
];
const STORAGE_KEY = "knowvora-react-progress";
function App() {
const [done, setDone] = useState([]);
const [filter, setFilter] = useState("all");
useEffect(() => {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) setDone(JSON.parse(saved));
}, []);
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(done));
}, [done]);
function toggleLesson(id) {
setDone((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
);
}
const filtered = useMemo(() => {
if (filter === "done") return LESSONS.filter((l) => done.includes(l.id));
if (filter === "todo") return LESSONS.filter((l) => !done.includes(l.id));
return LESSONS;
}, [filter, done]);
const percent = Math.round((done.length / LESSONS.length) * 100);
return (
<div className="app">
<header className="header">
<h1>Rishtaara React Progress</h1>
<p>Track your react-complete-guide lessons</p>
</header>
<section className="stats">
<div className="bar-wrap">
<div className="bar" style={{ width: `${percent}%` }} />
</div>
<p>
{done.length} / {LESSONS.length} complete ({percent}%)
</p>
</section>
<div className="filters">
{["all", "done", "todo"].map((f) => (
<button
key={f}
className={filter === f ? "active" : ""}
onClick={() => setFilter(f)}
>
{f.charAt(0).toUpperCase() + f.slice(1)}
</button>
))}
</div>
<ul className="lesson-list">
{filtered.map((lesson) => {
const isDone = done.includes(lesson.id);
return (
<li key={lesson.id} className={isDone ? "done" : ""}>
<label>
<input
type="checkbox"
checked={isDone}
onChange={() => toggleLesson(lesson.id)}
/>
<span>
{lesson.id}. {lesson.title}
</span>
<code>{lesson.slug}</code>
</label>
</li>
);
})}
</ul>
<footer className="footer">
<p>
Practice more: <a href="/mcq/react-mcq">MCQ</a> ·{" "}
<a href="/interview/react-interview">Interview</a>
</p>
</footer>
</div>
);
}
export default App;.app {
max-width: 640px;
margin: 2rem auto;
padding: 0 1rem;
font-family: system-ui, sans-serif;
}
.header h1 {
color: #0891b2;
margin-bottom: 0.25rem;
}
.stats {
margin: 1.5rem 0;
}
.bar-wrap {
height: 12px;
background: #e0f2fe;
border-radius: 999px;
overflow: hidden;
}
.bar {
height: 100%;
background: #0891b2;
transition: width 0.3s ease;
}
.filters {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.filters button {
padding: 0.4rem 0.8rem;
border: 1px solid #cbd5e1;
border-radius: 8px;
background: white;
cursor: pointer;
}
.filters button.active {
background: #0891b2;
color: white;
border-color: #0891b2;
}
.lesson-list {
list-style: none;
padding: 0;
}
.lesson-list li {
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-bottom: 0.5rem;
}
.lesson-list li.done {
background: #f0fdfa;
opacity: 0.85;
}
.lesson-list label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
}
.lesson-list code {
margin-left: auto;
font-size: 0.75rem;
color: #64748b;
}
.footer {
margin-top: 2rem;
text-align: center;
color: #64748b;
}
.footer a {
color: #0891b2;
}Key Takeaways
- React components are functions that return JSX describing UI.
- Props flow down; state is local unless lifted or shared via Context.
- useState for state, useEffect for side effects after render.
- Keys on list items must be stable and unique.
- Controlled forms tie input values to React state.
- Custom hooks extract reusable stateful logic.
- React Router handles client-side pages with Routes, Link, and useParams.
- React Compiler auto-optimizes many apps — learn hooks first, memo when needed.
- Build the course progress tracker project to practice hooks and localStorage.
Frequently Asked Questions
- React vs Next.js — what's the difference?
- React is the UI library. Next.js is a framework built on React that adds routing, server components, API routes, and deployment tooling.
- Do I need Redux?
- Not for most apps. Start with useState and Context. Add Zustand or Redux when state becomes complex or updates are frequent across many components.
- Class components ya function components?
- Write new code with function components and hooks. Learn class components only to read legacy codebases.
- useEffect kab use karein?
- After render for side effects: fetch data, subscriptions, timers, syncing with non-React APIs. Not for deriving values you can compute during render.
- React Compiler kya karta hai?
- It automatically memoizes components at build time so you need less manual useMemo/useCallback. Still write clear React; measure with Profiler when unsure.