React

React JS Tutorial

Build modern user interfaces with the most popular JavaScript library

1 What is React?

React is a JavaScript library for building user interfaces, created by Facebook. It uses a component-based architecture and a virtual DOM for efficient updates.

React Core Concepts
Components
Reusable UI pieces
+
State
Dynamic data
+
Props
Data passing
=
UI
User Interface

🔑 Why React?

Component-Based: Build encapsulated components that manage their own state
Declarative: Describe what UI should look like, React handles updates
Virtual DOM: Efficient updates by comparing virtual and real DOM
Large Ecosystem: Rich tooling, libraries, and community support

React vs Vanilla JavaScript

Vanilla JS
// Vanilla JavaScript - Manual DOM manipulation
const button = document.getElementById('counter-btn');
const display = document.getElementById('count');
let count = 0;

button.addEventListener('click', () => {
    count++;
    display.textContent = count;  // Manual update
});
React
// React - Declarative approach
function Counter() {
    const [count, setCount] = useState(0);
    
    return (
        <button onClick={() => setCount(count + 1)}>
            Count: {count}
        </button>
    );
}

2 Environment Setup

The easiest way to start a React project is using Create React App or the newer Vite.

Terminal Commands
# Option 1: Create React App (traditional)
npx create-react-app my-app
cd my-app
npm start

# Option 2: Vite (faster, recommended)
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev

# Project structure created:
my-app/
├── node_modules/
├── public/
├── src/
│   ├── App.jsx       # Main component
│   ├── main.jsx      # Entry point
│   └── index.css
├── package.json
└── vite.config.js

💡 Vite vs Create React App

Vite - Faster dev server, instant HMR, smaller builds (recommended for new projects)
CRA - More mature, wider compatibility, extensive documentation

3 JSX Syntax

JSX is a syntax extension that lets you write HTML-like code in JavaScript. It gets compiled to regular JavaScript function calls.

JSX - Basic Syntax
// JSX looks like HTML but it's JavaScript!
const element = <h1>Hello, World!</h1>;

// Embedding expressions with {}
const name = "Alice";
const greeting = <h1>Hello, {name}!</h1>;

// Any JavaScript expression works
const math = <p>2 + 2 = {2 + 2}</p>;

// Calling functions
const upper = <p>{name.toUpperCase()}</p>;

// JSX attributes use camelCase
const link = <a href="https://react.dev" className="link">React Docs</a>;
// Note: className instead of class!

// Inline styles use object syntax
const styled = <div style={{ color: 'blue', fontSize: '20px' }}>
    Styled text
</div>;

// Self-closing tags must have /
const image = <img src="photo.jpg" alt="Photo" />;
const input = <input type="text" />;

// Multiple elements need a wrapper (or Fragment)
const multiple = (
    <>
        <h1>Title</h1>
        <p>Paragraph</p>
    </>
);

⚠️ JSX Rules

• Use className not class
• Use htmlFor not for
• All tags must be closed: <br />, <img />
• Return one parent element (use <></> Fragment if needed)

4 Functional Components

Components are the building blocks of React apps. They're JavaScript functions that return JSX.

React - Components
// Simple component (function that returns JSX)
function Welcome() {
    return <h1>Welcome to React!</h1>;
}

// Arrow function syntax
const Greeting = () => {
    return <p>Hello there!</p>;
};

// Component with multiple elements
function UserCard() {
    return (
        <div className="card">
            <img src="avatar.jpg" alt="Avatar" />
            <h2>John Doe</h2>
            <p>Frontend Developer</p>
        </div>
    );
}

// Using components (like HTML tags)
function App() {
    return (
        <div>
            <Welcome />
            <Greeting />
            <UserCard />
            <UserCard />  {/* Reusable! */}
        </div>
    );
}
▶ RENDERED OUTPUT

Welcome to React!

Hello there!

John Doe

Frontend Developer

John Doe

Frontend Developer

5 Props (Properties)

Props are how you pass data from parent to child components. They're read-only!

Props Flow (One-Way Data)
→ props →
Child
Receives data
React - Props
// Component receiving props
function Greeting(props) {
    return <h1>Hello, {props.name}!</h1>;
}

// Destructuring props (cleaner)
function UserCard({ name, role, avatar }) {
    return (
        <div className="card">
            <img src={avatar} alt={name} />
            <h2>{name}</h2>
            <p>{role}</p>
        </div>
    );
}

// Default props
function Button({ text = "Click Me", color = "blue" }) {
    return (
        <button style={{ backgroundColor: color }}>
            {text}
        </button>
    );
}

// Using components with props
function App() {
    return (
        <div>
            <Greeting name="Alice" />
            <Greeting name="Bob" />
            
            <UserCard 
                name="Alice Johnson"
                role="Developer"
                avatar="alice.jpg"
            />
            
            <Button />                           {/* Uses defaults */}
            <Button text="Submit" color="green" />
        </div>
    );
}
▶ RENDERED OUTPUT

Hello, Alice!

Hello, Bob!

6 Children Props

The special children prop contains whatever you put between opening and closing tags of a component.

React - Children
// Wrapper component using children
function Card({ children, title }) {
    return (
        <div className="card">
            <h3>{title}</h3>
            <div className="card-body">
                {children}  {/* Content goes here */}
            </div>
        </div>
    );
}

// Layout component
function Layout({ children }) {
    return (
        <div className="layout">
            <header>My App</header>
            <main>{children}</main>
            <footer>© 2024</footer>
        </div>
    );
}

// Usage
function App() {
    return (
        <Layout>
            <Card title="Welcome">
                <p>This is the card content.</p>
                <button>Click Me</button>
            </Card>
            
            <Card title="Another Card">
                <ul>
                    <li>Item 1</li>
                    <li>Item 2</li>
                </ul>
            </Card>
        </Layout>
    );
}

7 useState Hook

useState lets components "remember" information. When state changes, React re-renders the component.

State Update Flow
User Action
Click, Type
setState()
Update state
Re-render
New UI
React - useState
import { useState } from 'react';

// Counter example
function Counter() {
    // [currentValue, setterFunction] = useState(initialValue)
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setCount(count - 1)}>-1</button>
            <button onClick={() => setCount(0)}>Reset</button>
        </div>
    );
}

// Multiple state variables
function Form() {
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const [agreed, setAgreed] = useState(false);
    
    return (
        <form>
            <input 
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="Name"
            />
            <input 
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="Email"
            />
            <label>
                <input 
                    type="checkbox"
                    checked={agreed}
                    onChange={(e) => setAgreed(e.target.checked)}
                />
                I agree to terms
            </label>
        </form>
    );
}

// State with objects
function UserProfile() {
    const [user, setUser] = useState({
        name: "Alice",
        age: 25,
        city: "NYC"
    });
    
    // Updating one property (spread operator!)
    const updateAge = () => {
        setUser({ ...user, age: user.age + 1 });
    };
    
    return (
        <div>
            <p>{user.name}, {user.age}</p>
            <button onClick={updateAge}>Birthday!</button>
        </div>
    );
}

⚠️ State Rules

• Don't modify state directly: count++ ❌ → setCount(count + 1)
• State updates are async (batched for performance)
• For objects/arrays, always create new copies with spread

8 useEffect Hook

useEffect handles side effects - operations like fetching data, subscriptions, or DOM manipulation.

React - useEffect
import { useState, useEffect } from 'react';

function DataFetcher() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    
    // Runs after every render
    useEffect(() => {
        console.log("Component rendered!");
    });
    
    // Runs ONCE on mount (empty dependency array)
    useEffect(() => {
        fetch("https://api.example.com/data")
            .then(res => res.json())
            .then(data => {
                setData(data);
                setLoading(false);
            });
    }, []);  // Empty array = run once
    
    if (loading) return <p>Loading...</p>;
    return <div>{JSON.stringify(data)}</div>;
}

// Runs when specific values change
function SearchResults({ query }) {
    const [results, setResults] = useState([]);
    
    useEffect(() => {
        console.log(`Searching for: ${query}`);
        // Fetch new results when query changes
        searchAPI(query).then(setResults);
    }, [query]);  // Re-run when query changes
    
    return (
        <ul>
            {results.map(r => <li key={r.id}>{r.name}</li>)}
        </ul>
    );
}

// Cleanup function (for subscriptions, timers)
function Timer() {
    const [seconds, setSeconds] = useState(0);
    
    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(s => s + 1);
        }, 1000);
        
        // Cleanup: runs when component unmounts
        return () => clearInterval(interval);
    }, []);
    
    return <p>Timer: {seconds}s</p>;
}

💡 useEffect Dependency Array

useEffect(() => {}, []) - Run once on mount
useEffect(() => {}) - Run after every render
useEffect(() => {}, [x, y]) - Run when x or y changes

9 useRef Hook

useRef creates a mutable reference that persists across renders without causing re-renders.

React - useRef
import { useRef } from 'react';

// Access DOM elements
function FocusInput() {
    const inputRef = useRef(null);
    
    const handleClick = () => {
        inputRef.current.focus();  // Focus the input
    };
    
    return (
        <div>
            <input ref={inputRef} placeholder="Click button to focus" />
            <button onClick={handleClick}>Focus Input</button>
        </div>
    );
}

// Store mutable values without re-render
function Stopwatch() {
    const [time, setTime] = useState(0);
    const intervalRef = useRef(null);
    
    const start = () => {
        intervalRef.current = setInterval(() => {
            setTime(t => t + 1);
        }, 1000);
    };
    
    const stop = () => {
        clearInterval(intervalRef.current);
    };
    
    return (
        <div>
            <p>Time: {time}s</p>
            <button onClick={start}>Start</button>
            <button onClick={stop}>Stop</button>
        </div>
    );
}

10 useContext Hook

Context provides a way to pass data through the component tree without prop drilling.

React - Context
import { createContext, useContext, useState } from 'react';

// 1. Create context
const ThemeContext = createContext("light");

// 2. Provider component
function App() {
    const [theme, setTheme] = useState("light");
    
    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            <Header />
            <Main />
        </ThemeContext.Provider>
    );
}

// 3. Consume context with useContext
function Header() {
    const { theme, setTheme } = useContext(ThemeContext);
    
    return (
        <header className={theme}>
            <h1>My App ({theme} mode)</h1>
            <button onClick={() => 
                setTheme(theme === "light" ? "dark" : "light")
            }>
                Toggle Theme
            </button>
        </header>
    );
}

// Deep nested component can still access context!
function DeepChild() {
    const { theme } = useContext(ThemeContext);
    return <div className={`card ${theme}`}>Styled by context</div>;
}

11 Lists & Keys

When rendering lists, each item needs a unique key prop for React to track changes efficiently.

React - Lists
function TodoList() {
    const [todos, setTodos] = useState([
        { id: 1, text: "Learn React", done: false },
        { id: 2, text: "Build project", done: false },
        { id: 3, text: "Deploy app", done: true }
    ]);
    
    return (
        <ul>
            {todos.map(todo => (
                {/* key must be unique and stable */}
                <li key={todo.id}>
                    <input 
                        type="checkbox" 
                        checked={todo.done}
                        onChange={() => toggleTodo(todo.id)}
                    />
                    {todo.text}
                </li>
            ))}
        </ul>
    );
}

// Filtering lists
function FilteredList({ items, filter }) {
    const filtered = items
        .filter(item => item.category === filter)
        .map(item => (
            <div key={item.id}>{item.name}</div>
        ));
    
    return <div>{filtered}</div>;
}

⚠️ Key Rules

• Keys must be unique among siblings
• Don't use array index as key (causes bugs on reorder)
• Use stable IDs from your data

12 Form Handling

React forms use "controlled components" where form data is handled by React state.

React - Forms
function SignupForm() {
    const [formData, setFormData] = useState({
        username: "",
        email: "",
        password: ""
    });
    const [errors, setErrors] = useState({});
    
    // Generic handler for all inputs
    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData(prev => ({
            ...prev,
            [name]: value
        }));
    };
    
    const handleSubmit = (e) => {
        e.preventDefault();
        
        // Validation
        const newErrors = {};
        if (!formData.username) newErrors.username = "Required";
        if (!formData.email) newErrors.email = "Required";
        if (formData.password.length < 6) {
            newErrors.password = "Min 6 characters";
        }
        
        if (Object.keys(newErrors).length > 0) {
            setErrors(newErrors);
            return;
        }
        
        console.log("Submitting:", formData);
    };
    
    return (
        <form onSubmit={handleSubmit}>
            <div>
                <input
                    name="username"
                    value={formData.username}
                    onChange={handleChange}
                    placeholder="Username"
                />
                {errors.username && <span className="error">{errors.username}</span>}
            </div>
            
            <div>
                <input
                    name="email"
                    type="email"
                    value={formData.email}
                    onChange={handleChange}
                    placeholder="Email"
                />
                {errors.email && <span className="error">{errors.email}</span>}
            </div>
            
            <button type="submit">Sign Up</button>
        </form>
    );
}

13 Conditional Rendering

Show different content based on conditions using JavaScript operators in JSX.

React - Conditionals
function Dashboard({ user, loading, error }) {
    // Early return pattern
    if (loading) return <p>Loading...</p>;
    if (error) return <p>Error: {error}</p>;
    
    return (
        <div>
            {/* Ternary operator */}
            {user ? (
                <h1>Welcome, {user.name}!</h1>
            ) : (
                <h1>Please log in</h1>
            )}
            
            {/* && operator (show if truthy) */}
            {user && user.isAdmin && (
                <button>Admin Panel</button>
            )}
            
            {/* Multiple conditions */}
            {user?.notifications?.length > 0 && (
                <span className="badge">
                    {user.notifications.length}
                </span>
            )}
        </div>
    );
}

// Conditional CSS classes
function Button({ disabled, primary }) {
    return (
        <button 
            className={`btn ${primary ? 'btn-primary' : ''} ${disabled ? 'disabled' : ''}`}
            disabled={disabled}
        >
            Click Me
        </button>
    );
}

14 Data Fetching

Fetch data from APIs using useEffect and useState.

React - Data Fetching
function UserList() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        const fetchUsers = async () => {
            try {
                const response = await fetch(
                    "https://jsonplaceholder.typicode.com/users"
                );
                
                if (!response.ok) {
                    throw new Error("Failed to fetch");
                }
                
                const data = await response.json();
                setUsers(data);
            } catch (err) {
                setError(err.message);
            } finally {
                setLoading(false);
            }
        };
        
        fetchUsers();
    }, []);
    
    if (loading) return <div className="spinner">Loading...</div>;
    if (error) return <div className="error">Error: {error}</div>;
    
    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>
                    <strong>{user.name}</strong>
                    <p>{user.email}</p>
                </li>
            ))}
        </ul>
    );
}

15 Custom Hooks

Extract reusable logic into custom hooks. They always start with "use".

React - Custom Hooks
// Custom hook for data fetching
function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        fetch(url)
            .then(res => res.json())
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, [url]);
    
    return { data, loading, error };
}

// Usage
function Posts() {
    const { data, loading, error } = useFetch("/api/posts");
    
    if (loading) return <p>Loading...</p>;
    return <div>{data.map(p => <Post key={p.id} {...p} />)}</div>;
}

// Custom hook for localStorage
function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(() => {
        const stored = localStorage.getItem(key);
        return stored ? JSON.parse(stored) : initialValue;
    });
    
    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);
    
    return [value, setValue];
}

// Usage
function Settings() {
    const [theme, setTheme] = useLocalStorage("theme", "light");
    // theme persists across page refreshes!
}

16 React Router

React Router enables navigation between pages in single-page applications.

React Router
// npm install react-router-dom
import { 
    BrowserRouter, 
    Routes, 
    Route, 
    Link,
    useParams,
    useNavigate
} from 'react-router-dom';

// App with routing
function App() {
    return (
        <BrowserRouter>
            {/* Navigation */}
            <nav>
                <Link to="/">Home</Link>
                <Link to="/about">About</Link>
                <Link to="/users">Users</Link>
            </nav>
            
            {/* Route definitions */}
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
                <Route path="/users" element={<Users />} />
                <Route path="/users/:id" element={<UserDetail />} />
                <Route path="*" element={<NotFound />} />
            </Routes>
        </BrowserRouter>
    );
}

// Dynamic route with useParams
function UserDetail() {
    const { id } = useParams();
    return <h1>User ID: {id}</h1>;
}

// Programmatic navigation
function LoginForm() {
    const navigate = useNavigate();
    
    const handleSubmit = () => {
        // After login success
        navigate("/dashboard");
    };
    
    return <button onClick={handleSubmit}>Login</button>;
}

17 Performance Optimization

React provides tools to optimize rendering and prevent unnecessary re-renders.

React - Performance
import { useState, useMemo, useCallback, memo } from 'react';

// useMemo - memoize expensive calculations
function ExpensiveList({ items, filter }) {
    const filteredItems = useMemo(() => {
        console.log("Filtering...");
        return items.filter(item => 
            item.name.includes(filter)
        );
    }, [items, filter]); // Only recalculate when these change
    
    return (
        <ul>
            {filteredItems.map(item => (
                <li key={item.id}>{item.name}</li>
            ))}
        </ul>
    );
}

// useCallback - memoize functions
function Parent() {
    const [count, setCount] = useState(0);
    
    // Without useCallback, new function created every render
    const handleClick = useCallback(() => {
        console.log("Clicked!");
    }, []); // Empty deps = same function reference
    
    return <Child onClick={handleClick} />;
}

// memo - prevent re-render if props unchanged
const Child = memo(function Child({ onClick }) {
    console.log("Child rendered");
    return <button onClick={onClick}>Click</button>;
});

// Lazy loading components
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
    return (
        <Suspense fallback={<p>Loading...</p>}>
            <HeavyComponent />
        </Suspense>
    );
}

💡 When to Optimize

useMemo - Expensive calculations (sorting, filtering large arrays)
useCallback - Functions passed to memoized children
memo - Components that render often with same props
Don't optimize prematurely! Measure first.