Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
22 minLesson 16 of 40
React Fundamentals

Props, State & Re-rendering

Props, State & Re-rendering

Props and state are the two ways components hold data. Understanding the difference — and when React re-renders — is the foundation of building correct React applications.

Props

Props are data passed from parent to child. They're read-only:

// Parent passes props
function App() {
    return <UserCard name="Alice" role="admin" isOnline={true} />;
}

// Child receives props as an object
function UserCard({ name, role, isOnline }) {
    return (
        <div className={`card ${isOnline ? "online" : ""}`}>
            <h3>{name}</h3>
            <span className="role">{role}</span>
        </div>
    );
}

// Default prop values
function Button({ label, variant = "primary", size = "md", disabled = false }) {
    return (
        <button 
            className={`btn btn-${variant} btn-${size}`}
            disabled={disabled}
        >
            {label}
        </button>
    );
}

children Prop

// Pass JSX content between tags
function Card({ title, children }) {
    return (
        <div className="card">
            <h3 className="card-title">{title}</h3>
            <div className="card-body">{children}</div>
        </div>
    );
}

// Usage
<Card title="User Profile">
    <img src={avatarUrl} alt="avatar" />
    <p>Member since 2024</p>
    <Button label="Edit Profile" />
</Card>

State with useState

State is data that belongs to a component. When state changes, React re-renders the component:

import { useState } from "react";

function Counter() {
    const [count, setCount] = useState(0);  // [value, setter]
    
    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 Rules

// 1. NEVER mutate state directly
const [items, setItems] = useState([1, 2, 3]);

items.push(4);          // ❌ direct mutation — React won't re-render
setItems([...items, 4]); // ✅ new array

// 2. State updates are async (batched)
const [count, setCount] = useState(0);

function handleClick() {
    setCount(count + 1);  // reads stale count if called multiple times
    setCount(count + 1);  // still uses old value!
}

// Use updater function for derived state
function handleClick() {
    setCount(prev => prev + 1);  // ✅ uses latest value
    setCount(prev => prev + 1);  // ✅ correctly increments twice
}

// 3. Object state — spread to preserve other fields
const [user, setUser] = useState({ name: "Alice", age: 30, role: "admin" });

setUser({ ...user, age: 31 });        // ✅ keep name and role
setUser(prev => ({ ...prev, age: 31 })); // ✅ same, safer

State for Forms

function LoginForm() {
    const [form, setForm] = useState({ email: "", password: "" });
    const [error, setError] = useState("");
    const [loading, setLoading] = useState(false);
    
    function handleChange(e) {
        setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));
    }
    
    async function handleSubmit(e) {
        e.preventDefault();
        setLoading(true);
        setError("");
        
        try {
            await login(form.email, form.password);
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    }
    
    return (
        <form onSubmit={handleSubmit}>
            <input name="email" type="email" value={form.email} onChange={handleChange} />
            <input name="password" type="password" value={form.password} onChange={handleChange} />
            {error && <p className="error">{error}</p>}
            <button type="submit" disabled={loading}>
                {loading ? "Signing in..." : "Sign In"}
            </button>
        </form>
    );
}

Lifting State Up

When two components need the same state, move it to their common parent:

function App() {
    const [selectedId, setSelectedId] = useState(null);
    
    return (
        <div className="layout">
            <UserList
                selectedId={selectedId}
                onSelect={setSelectedId}  // pass setter down
            />
            <UserDetail
                userId={selectedId}      // share the same state
            />
        </div>
    );
}

Re-rendering

React re-renders a component when:

  1. Its state changes
  2. Its parent re-renders (and passes new props)
  3. Context it consumes changes
// Avoid unnecessary re-renders
// If a prop is the same value, the component still re-renders by default
// (React.memo solves this — covered later)

function ExpensiveChart({ data }) {
    console.log("Chart rendered");
    return <svg>{/* ... */}</svg>;
}

// Parent re-renders → Chart re-renders even if data didn't change
// Use React.memo to skip if props are equal
const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
    return <svg>{/* ... */}</svg>;
});

Next lesson: Hooks — useState, useEffect, and the rules of hooks.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!