You open a webpage.
You click a button.
either,
- A message appears,
- A cat picture loads or
- The button turns green.
That’s not magic, it’s JavaScript talking to the browser ✨
When your browser loads an HTML file,
It doesn't just display it.
It builds a living, breathing structure behind the scenes:
🌳 The DOM : Document Object Model
Every <div>, <h1>, <button> becomes a room or object inside that house
And the best part? JavaScript can walk through this house and rearrange furniture anytime!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<h1>Hello, World!</h1>
<h2>Welcome to my website</h2>
</body>
</html>
Your HTML = blueprint
The DOM = actual house the browser builds from that blueprint
When a user clicks a button on a classic webpage,
here's what actually happens:
<button id="toggleBtn">Show/Hide Secret</button>
<p id="secret">Shhh… I’m a secret! 🤫</p>
const btn = document.getElementById("toggleBtn");
const msg = document.getElementById("secret");
btn.addEventListener("click", () => {
if (msg.style.display === "none") {
msg.style.display = "block";
} else {
msg.style.display = "none";
}
});
Let’s say we want to hide a secret message when someone clicks a button. Here’s how we’d do it with plain JavaScript:
But what if we have 10 secrets? 100 buttons?
Suddenly we're:
- Writing the same code over and over…
- Forgetting which element has which ID…
- Praying nothing breaks when we add a new feature
The DOM is a tree of objects.
Every change can trigger expensive operations.
<!-- DOM tree (simplified) -->
<body>
<header> → cart badge updates here
<nav>, <logo>, <links>...
<main>
<ProductList> ← re-renders
<ProductCard × 48>
<sidebar> → filter state
<footer>
</body>
As your app grows, DOM manipulation becomes fragile and hard to track. Why?
Too many moving parts: You update one element, but forget it affects another.
No single source of truth: Is the data in a variable? In the DOM? In localStorage?
Performance issues: Changing the DOM is slow, browsers weren’t built for constant updates
Readability nightmare: Your code looks like a bowl of callback spaghetti 🍝
You start spending more time fixing bugs than building features !
Spoiler: there is.
What if you could just describe what your UI should look like,
and let someone else figure out what changed and how to update it?
Facebook built React in 2013 to solve exactly this problem.
And it changed frontend development forever.
❌ Without React :
Manual DOM manipulation
✅ With React
Declarative & automatic
1. State changes
2. React compares Virtual DOMs
3. React finds the diff
4. React updates only what changed
5. ✨ That's it. You're done.
Imperative (vanilla JS) → "Find element #counter, set its text to 5, then find #badge…"
Declarative (React) → "The UI looks like this when count is 5."
Let's clear up the confusion first.
Each component is a LEGO brick, self-contained, reusable.
You snap them together to build complex UIs.
Change one brick? Only that brick updates.
The rest? Untouched.
> React is the most used frontend library in the world.
> Not the newest. Not the flashiest. The most used.
> There's a reason for that.
we need a workspace where:
But browsers? They’re… kinda old-school.
They don’t understand import React from 'react'
So we need a secret translator + organizer.
A bundler does 3 things:
Without a bundler, you'd ship 200 separate JS files to the browser.
With a bundler? One clean, optimized bundle.
Powers Create React App (legacy), which is the traditional way to create single-page React applications. It sets up a new React project with a pre-configured build setup.
Vite is a next-generation build tool for frontend development. It focuses on speed and efficiency by leveraging ES Module imports and server-side rendering (SSR) capabilities.
SWC excels in performance and modern JavaScript/TypeScript support, making it ideal for projects that require fast compilation times and support for the latest ECMAScript features.
Babel, while slightly slower in compilation speed compared to SWC, offers unparalleled flexibility, extensive plugin support, and broad compatibility across different JavaScript environments and syntax versions.
1. Open your terminal and type:
npm create vite@latest my-app
2. Select your project settings
3. run your app
cd my-app
npm install
npm run dev
Your app is live at http://localhost:5173 (or similar), and it hot-reloads (changes appear instantly as you code)!
3 commands. That's all it takes.
✔ Select a framework: React
✔ Select a variant: JavaScript + SWC
✓ Project created!
main.jsx: the entry point.
It grabs `<div id="root">` from `index.html` and renders your entire React app inside it.
That one `<div>`. That's it. React builds everything from there.
In React, a component is a reusable, self-contained piece of code that encapsulates the structure (HTML), behavior (JavaScript), and styling (CSS) of a part of the user interface.
There are two types:
React.Component.
JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code directly in your JavaScript files.
{}.<div>
<h1>Hello, {fullName}!</h1>
<p>Welcome to my React app.</p>
</div>const fullName="Naruto UZUMAKI"Class components are ES6 classes that extend the React.Component class. They can have state and lifecycle methods, making them more suitable for complex logic and interactions.
import React from 'react';
class Header extends React.Component {
render() {
return <header>Welcome to My React App ! 🎉</header>;
}
}The Header component is a class component that returns a <header> element using JSX.
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleIncrement}>Increment</button>
</div>
);
}
}
Functional components are JavaScript functions that return JSX (JavaScript XML) to describe what should be rendered on the screen. They are simpler and modern approach to building components. They are stateless, meaning they don't have internal state or lifecycle methods.
function Header() {
return <header>Welcome to My React App ! 🎉</header>;
}import React from 'react';
const MyComponent = (props) => {
return (
<div>
<h2>Hello, {props.name}!</h2>
<p>{props.message}</p>
</div>
);
};In React, data is typically passed from a parent component to a child component through props. Props (short for "properties") are read-only and allow data to flow in one direction, from parent to child components.
// ParentComponent.js
import React from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const name = 'John Doe';
return <ChildComponent name={name} />;
};// ChildComponent.js
import React from 'react';
const ChildComponent = (props) => {
return <p>Hello, {props.name}!</p>;
};
// ParentComponent.js
import React from 'react';
function UserCard({ name, age }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
}// ChildComponent.js
import React from 'react';
function Greeting(pops) {
return <h1>Hello, {props.name}!</h1>;
}
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
Destructuring is a JavaScript feature that allows you to extract values from objects or arrays into variables. It makes your code cleaner and easier to read.
Exo: Color palette Gallery
// Parent Component
function App() {
const handleClick = () => {
alert('Button clicked in the parent component!');
};
return <Button onClick={handleClick} />;
}
// Child Component
function Button({ onClick }) {
return <button onClick={onClick}>Click Me</button>;
}Sometimes, you need to pass a function from a parent component to a child component to handle events or update the parent’s state. This is a common pattern in React.
npm install prop-typesPropTypes are a way to validate the types of props passed to a component. They help catch bugs by ensuring that the correct data types are used.
import PropTypes from 'prop-types';
function UserCard({ name, age }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
}
UserCard.propTypes = { // Define PropTypes
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};Common PropTypes
PropTypes.string: The prop must be a string.
PropTypes.number: The prop must be a number.
PropTypes.bool: The prop must be a boolean.
PropTypes.func: The prop must be a function.
PropTypes.node: The prop must be a renderable value (e.g., string, number, element).
PropTypes.element: The prop must be a React element.
The children prop is a special prop that allows you to pass content between the opening and closing tags of a component. It’s like a placeholder for nested content.
function App() {
return (
<Card>
<h2>Hello, World!</h2>
<p>This is a card component.</p>
</Card>
);
}
// Child Component
function Card({ children }) {
return <div style={styles.card}>{children}</div>;
}The Component's Memory
Imagine a YouTube video player.
Without state, your UI is a static screenshot.
With state, it becomes a living, breathing app.
> State = data that changes over time and that the UI needs to react to.
You click. `count` becomes 1, 2, 3...
let count = 0;
function Counter() {
return (
<p>{count}</p>
<button onClick={() => count++}>Click me</button>
);
}
But the screen never changes.🧊
> React has no idea `count` changed.
> It's just a JavaScript variable. React isn't watching it.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 }; // Initialize state
}
increment = () => { // Method to increment count
this.setState({ count: this.state.count + 1 });
};
decrement = () => { // Method to decrement count
this.setState({ count: this.state.count - 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
);
}
}
export default Counter;In class components, state is built-in, it is initialized in the constructor and updated using the setState method.
`this.setState()` → React sees the change → re-renders. ✅
> State is native to classes. It just works.
increment = () => {
this.setState(
{ count: this.state.count + 1 },
() => {
console.log('State updated! New count:', this.state.count);
}
);
};
Sometimes, you need to perform an action after the state has been updated. You can pass a callback function as the second argument to setState.
function Counter() {
// 🚫 Where do we put state?
// There's no constructor.
// There's no `this`.
// There's no setState.
return (
???
+
);
}Functional components are simpler, cleaner, more readable.
Everyone loves them. But there's a catch.
Before 2019, functional components were stateless by design.
They could only receive props and render UI. That's it.
> So developers were forced to use class components
> just to get state. Even for simple things.
React 16.8 introduced Hooks, special functions that let
functional components do everything class components could.
Superpowers for Functions
React Hooks are special functions that were introduced to address some of the limitations of functional components and to provide a more cohesive way to handle state and side effects in React applications.
Hooks let you "hook into" React features from functional components:
"Why write 50 lines in a class when 10 lines in a function do the same?"
The useState hook is the simplest and most commonly used hook. It lets you manage data that can change in your component.
const [state, setState] = useState(initialValue);
// ↑ ↑ ↑
// current setter starting value
`useState` returns exactly two things, always destructured as an array.
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [isOpen, setIsOpen] = useState(false);
Rules:
> The setter is the only door into state, Don't try to sneak in through the window.
import { useState } from 'react';
function ThemeButton() {
const [isDark, setIsDark] = useState(false);
const toggle = () => {
setIsDark(prev => !prev)
};
return (
<div>
<p>{isDark ? '🌙 Dark Mode' : '☀️ Light Mode'}</p>
<button onClick={toggle}>
Switch to {isDark ? 'Light' : 'Dark'}
</button>
</div>
);
}One state. One setter. One toggle.
That's 80% of UI interactions right there.
Note !
`prev => !prev` => always use the functional form when new value depends on old value.
import React, { useState } from 'react';
function InputField() {
const [inputValue, setInputValue] = useState('');
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Type something..."
/>
<p>You typed: {inputValue}</p>
</div>
);
}import React, { useState } from 'react';
function TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
const [fahrenheit, setFahrenheit] = useState(32);
const handleCelsiusChange = (event) => {
const newCelsius = event.target.value;
const newFahrenheit = (newCelsius * 9) / 5 + 32;
setCelsius(newCelsius);
setFahrenheit(newFahrenheit);
};
const handleFahrenheitChange = (event) => {
const newFahrenheit = event.target.value;
const newCelsius = ((newFahrenheit - 32) * 5) / 9;
setFahrenheit(newFahrenheit);
setCelsius(newCelsius);
};
// return part
}return (
<div>
<label>
Celsius:
<input type="number"
value={celsius}
onChange={handleCelsiusChange}/>
</label>
<br />
<label>
Fahrenheit:
<input type="number"
value={fahrenheit}
onChange={handleFahrenheitChange}/>
</label>
</div>
);import React, { useState } from 'react';
function UserProfile() {
const [user, setUser] = useState({
firstName: '',
lastName: '',
email: '',
});
const handleInputChange = (event) => {
const { name, value } = event.target;
setUser({ ...user, [name]: value });
};
// return part
}
return (
<div>
<input type="text" name="firstName" value={user.firstName}
onChange={handleInputChange} placeholder="First Name" />
<input type="text" name="lastName" value={user.lastName}
onChange={handleInputChange} placeholder="Last Name"/>
<input type="email" name="email" value={user.email}
onChange={handleInputChange} placeholder="Email" />
<p>{`Hello, ${user.firstName} ${user.lastName}`}</p>
<p>Email: {user.email}</p>
</div>
);But what about everything outside the component?
Fetching data from an API
Setting a timer
Listening to window resize events
Changing the page title
These are called side effects, things that reach outside the React world.
The useEffect Hook in React is used to manage side effects in functional components. It allows you to perform tasks that can't or shouldn't happen within the regular rendering process.
useEffect tells React: "Do something after the component renders or when certain data changes."useEffect(() => {
// Code to run after rendering or
// when dependencies change
return () => {
// Cleanup code (optional)
};
}, [dependencies]); // Dependency array (optional)useEffect(() => {
// Your side effect code here
}, [dependencies]);import { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Dependency: `count`
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default App;const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Infinite loop!
}, [count]); // `count` changes on every renderForgetting Cleanup: If you subscribe to an event or timer, don’t forget to clean it up to avoid memory leaks.
1. Only call hooks at the top level
// ❌ Never inside conditions or loops
if (isLoggedIn) {
const [user, setUser] = useState(null); // 💥
}
2. Only call hooks from React functions