You are viewing a preview of this lesson. Sign in to start learning
Back to React

Core React Concepts

Master fundamental React concepts using functional components and Hooks

Core React Concepts

Master React fundamentals with free flashcards and spaced repetition practice. This lesson covers components, JSX, props, state, and the virtual DOMβ€”essential concepts for building modern web applications with React.

Welcome to React πŸ’»

React has revolutionized how we build user interfaces. Created by Facebook in 2013, React introduced a component-based architecture that makes building complex UIs manageable and maintainable. Whether you're building a small personal project or a large-scale application, understanding React's core concepts is your foundation for success.

πŸ€” Did you know? React was first deployed on Facebook's newsfeed in 2011 and Instagram.com in 2012 before being open-sourced. Today, it powers thousands of production applications including Netflix, Airbnb, and Discord.

Core Concepts Explained

1. Components 🧩

Components are the building blocks of React applications. Think of them as custom, reusable HTML elements that encapsulate their own structure, styling, and behavior.

🌍 Real-world analogy: Components are like LEGO blocks. Each block (component) has a specific shape and purpose, but you can combine them in countless ways to build complex structures (applications).

There are two types of components:

Functional Components (modern approach):

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Class Components (legacy approach):

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

πŸ’‘ Tip: Since React 16.8 (2019), functional components with Hooks are the preferred approach. They're simpler, more concise, and easier to test.

2. JSX (JavaScript XML) πŸ“

JSX is a syntax extension that lets you write HTML-like code directly in JavaScript. It looks like HTML but gets transformed into JavaScript function calls.

// JSX syntax
const element = <h1 className="greeting">Hello, world!</h1>;

// What it compiles to
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

Key JSX Rules:

RuleExampleWhy?
Use className not class<div className="box">class is a JavaScript keyword
Close all tags<img />, <br />JSX requires valid XML syntax
Use camelCase for attributesonClick, onChangeJavaScript naming convention
One parent element<>...</> or <div>...</div>JSX expressions must have one root

🧠 Memory device: "CamelCase for Clicks" - remember to use onClick, not onclick.

3. Props (Properties) πŸ“¦

Props are how components talk to each other. They're read-only data passed from parent to child components, like function parameters.

function UserCard(props) {
  return (
    <div className="card">
      <h2>{props.name}</h2>
      <p>Age: {props.age}</p>
      <p>Role: {props.role}</p>
    </div>
  );
}

// Usage
<UserCard name="Alice" age={28} role="Developer" />

Props Flow Visualization:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      App Component (Parent)         β”‚
β”‚                                     β”‚
β”‚  const user = {                     β”‚
β”‚    name: "Alice",                   β”‚
β”‚    age: 28                          β”‚
β”‚  }                                  β”‚
β”‚                                     β”‚
β”‚  Passes data ↓                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ props={user}
               ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    UserCard Component (Child)       β”‚
β”‚                                     β”‚
β”‚  Receives: props.name, props.age    β”‚
β”‚  ⚠️  Cannot modify props!           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’‘ Tip: Use destructuring for cleaner code:

function UserCard({ name, age, role }) {
  return <div>{name} is {age}</div>;
}

4. State πŸ”„

State is private data that belongs to a component and can change over time. When state changes, React automatically re-renders the component.

import { useState } from 'react';

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

State vs Props Comparison:

AspectPropsState
Controlled byParent componentComponent itself
Can change?❌ No (read-only)βœ… Yes (via setState)
Triggers re-render?βœ… When parent changesβœ… When state updates
Use caseConfiguration, data flowInteractive data, UI changes

🧠 Memory device: "Props Pass down, State Stays local"

5. Virtual DOM 🎭

The Virtual DOM is React's secret weapon for performance. It's a lightweight JavaScript representation of the actual DOM.

How it works:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         REACT UPDATE PROCESS                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  Step 1: State/Props Change
     β”‚
     ↓
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   Create new        β”‚
  β”‚   Virtual DOM       β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             ↓
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   Diff Algorithm    β”‚
  β”‚   (Reconciliation)  β”‚
  β”‚   Compare old vs    β”‚
  β”‚   new Virtual DOM   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             ↓
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   Calculate         β”‚
  β”‚   Minimum Changes   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             ↓
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   Update Real DOM   β”‚
  β”‚   (Batch Updates)   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🌍 Real-world analogy: Imagine you're editing a document. Instead of reprinting the entire document after each change, you mark only the changes and update just those sections. That's what the Virtual DOM does for web pages.

6. Component Lifecycle & Effects ⏰

Components go through different phases: mounting (creation), updating (changes), and unmounting (removal).

With Hooks, we use useEffect to handle side effects:

import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // Runs after component mounts
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
    
    // Cleanup function (runs before unmount)
    return () => {
      console.log('Component unmounting');
    };
  }, []); // Empty array = run once on mount
  
  return <div>{data ? data.title : 'Loading...'}</div>;
}

useEffect Dependency Array:

SyntaxWhen Effect RunsUse Case
useEffect(() => {...})After every render⚠️ Rarely needed, can cause issues
useEffect(() => {...}, [])Once on mountAPI calls, subscriptions
useEffect(() => {...}, [dep])When dep changesReact to specific changes

Detailed Examples with Explanations

Example 1: Building a Todo List πŸ“

Let's build a complete todo application demonstrating multiple concepts:

import { useState } from 'react';

function TodoApp() {
  // State management
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');
  
  // Event handler
  const handleAddTodo = () => {
    if (inputValue.trim()) {
      setTodos([...todos, {
        id: Date.now(),
        text: inputValue,
        completed: false
      }]);
      setInputValue(''); // Clear input
    }
  };
  
  // Toggle completion
  const handleToggle = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id 
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };
  
  // Delete todo
  const handleDelete = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  return (
    <div className="todo-app">
      <h1>My Todo List</h1>
      
      {/* Input section */}
      <div>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleAddTodo()}
          placeholder="Add a new task..."
        />
        <button onClick={handleAddTodo}>Add</button>
      </div>
      
      {/* Todo list */}
      <ul>
        {todos.map(todo => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={handleToggle}
            onDelete={handleDelete}
          />
        ))}
      </ul>
      
      {/* Stats */}
      <p>Total: {todos.length} | 
         Completed: {todos.filter(t => t.completed).length}
      </p>
    </div>
  );
}

// Separate component for each todo item
function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li style={{ 
      textDecoration: todo.completed ? 'line-through' : 'none' 
    }}>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span>{todo.text}</span>
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
}

Key concepts demonstrated:

  • βœ… Multiple state variables (todos, inputValue)
  • βœ… Event handlers (onChange, onClick, onKeyPress)
  • βœ… Conditional rendering (line-through style)
  • βœ… List rendering with map()
  • βœ… Props passing to child component
  • βœ… Immutable state updates (spread operator, filter, map)

Example 2: Fetching and Displaying Data 🌐

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    // Reset states when userId changes
    setLoading(true);
    setError(null);
    
    // Fetch user data
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => {
        if (!response.ok) throw new Error('User not found');
        return response.json();
      })
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, [userId]); // Re-run when userId changes
  
  // Loading state
  if (loading) return <div>Loading user profile...</div>;
  
  // Error state
  if (error) return <div>Error: {error}</div>;
  
  // Success state
  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
      <p>Joined: {new Date(user.joinDate).toLocaleDateString()}</p>
    </div>
  );
}

Key concepts demonstrated:

  • βœ… useEffect with dependency array
  • βœ… Handling loading, error, and success states
  • βœ… Conditional rendering (multiple return statements)
  • βœ… Props (userId) triggering effects
  • βœ… Async operations in React

Example 3: Form Handling with Controlled Components πŸ“‹

import { useState } from 'react';

function RegistrationForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
    agreeToTerms: false
  });
  
  const [errors, setErrors] = useState({});
  
  // Generic handler for all inputs
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };
  
  // Validation
  const validate = () => {
    const newErrors = {};
    
    if (formData.username.length < 3) {
      newErrors.username = 'Username must be at least 3 characters';
    }
    
    if (!formData.email.includes('@')) {
      newErrors.email = 'Invalid email address';
    }
    
    if (formData.password.length < 8) {
      newErrors.password = 'Password must be at least 8 characters';
    }
    
    if (!formData.agreeToTerms) {
      newErrors.agreeToTerms = 'You must agree to terms';
    }
    
    return newErrors;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const validationErrors = validate();
    
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }
    
    // Submit form
    console.log('Form submitted:', formData);
    // API call would go here
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Username:</label>
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
        {errors.username && <span className="error">{errors.username}</span>}
      </div>
      
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <label>Password:</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      
      <div>
        <label>
          <input
            type="checkbox"
            name="agreeToTerms"
            checked={formData.agreeToTerms}
            onChange={handleChange}
          />
          I agree to the terms and conditions
        </label>
        {errors.agreeToTerms && <span className="error">{errors.agreeToTerms}</span>}
      </div>
      
      <button type="submit">Register</button>
    </form>
  );
}

Key concepts demonstrated:

  • βœ… Controlled components (React controls form state)
  • βœ… Single handler for multiple inputs
  • βœ… Form validation
  • βœ… Preventing default form submission
  • βœ… Dynamic object property names [name]
  • βœ… Conditional rendering of errors

Example 4: Component Composition and Props.children 🎁

// Reusable Card component
function Card({ title, children, footer }) {
  return (
    <div className="card">
      {title && <div className="card-header">{title}</div>}
      <div className="card-body">
        {children}
      </div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// Usage examples
function App() {
  return (
    <div>
      {/* Simple card */}
      <Card title="Welcome">
        <p>This is card content</p>
      </Card>
      
      {/* Card with footer */}
      <Card 
        title="User Stats"
        footer={<button>View Details</button>}
      >
        <p>Posts: 42</p>
        <p>Followers: 1,234</p>
      </Card>
      
      {/* Card with complex content */}
      <Card title="Settings">
        <form>
          <input type="text" placeholder="Name" />
          <input type="email" placeholder="Email" />
          <button>Save</button>
        </form>
      </Card>
    </div>
  );
}

Key concepts demonstrated:

  • βœ… props.children for flexible composition
  • βœ… Optional props with conditional rendering
  • βœ… Reusable wrapper components
  • βœ… Component composition over inheritance

⚠️ Common Mistakes

1. Mutating State Directly ❌

// ❌ WRONG - Mutating state
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // This won't trigger re-render!
setItems(items); // Still wrong!

// βœ… CORRECT - Create new array
setItems([...items, 4]);
// or
setItems(items.concat(4));

2. Forgetting Keys in Lists ❌

// ❌ WRONG - No key or using index as key
{todos.map((todo, index) => (
  <li key={index}>{todo.text}</li>
))}

// βœ… CORRECT - Use unique, stable identifier
{todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))}

πŸ’‘ Why? React uses keys to track which items changed. Using index can cause bugs when items are reordered or deleted.

3. Missing Dependencies in useEffect ❌

// ❌ WRONG - Missing dependency
const [count, setCount] = useState(0);
useEffect(() => {
  console.log(count); // Uses 'count'
}, []); // But doesn't list it!

// βœ… CORRECT - Include all dependencies
useEffect(() => {
  console.log(count);
}, [count]); // Now it re-runs when count changes

4. Using State Immediately After Setting It ❌

// ❌ WRONG - State updates are asynchronous
const [count, setCount] = useState(0);
const handleClick = () => {
  setCount(count + 1);
  console.log(count); // Still shows old value!
};

// βœ… CORRECT - Use useEffect to react to changes
useEffect(() => {
  console.log(count); // Shows updated value
}, [count]);

5. Not Using Functional Updates ❌

// ❌ WRONG - Can cause bugs with multiple updates
const handleClick = () => {
  setCount(count + 1);
  setCount(count + 1); // Both use same 'count' value!
};

// βœ… CORRECT - Use functional form
const handleClick = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1); // Each gets latest value
};

πŸ”§ Try This: Mini-Exercise

Challenge: Modify the Counter example to include a "Reset" button and prevent the count from going below zero.

Starter code:

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      {/* Add your code here */}
    </div>
  );
}

Solution:

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(prev => Math.max(0, prev - 1))}>-</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

Key Takeaways 🎯

βœ… Components are reusable building blocks that return JSX

βœ… JSX looks like HTML but is actually JavaScript with special rules

βœ… Props flow down (parent to child) and are read-only

βœ… State is local, mutable data that triggers re-renders when changed

βœ… Virtual DOM makes React fast by minimizing actual DOM updates

βœ… useEffect handles side effects like data fetching and subscriptions

βœ… Never mutate state directly - always create new objects/arrays

βœ… Keys in lists should be unique and stable identifiers

βœ… State updates are asynchronous - don't rely on the value immediately

πŸ“š Further Study

πŸ“‹ Quick Reference Card

ConceptKey PointsExample
ComponentFunction returning JSXfunction App() { return <div/>; }
PropsRead-only, passed from parent<User name="Alice" />
StateMutable, triggers re-renderconst [x, setX] = useState(0)
JSXHTML-like syntax in JS<div className="box">Hi</div>
useEffectSide effects, runs after renderuseEffect(() => {...}, [deps])
Event HandlerFunction responding to eventsonClick={() => setX(x + 1)}
List RenderingMap array to components{items.map(i => <Li key={i.id}/>)}
Conditional RenderShow/hide based on condition{isLogged && <Dashboard/>}