ReactFeatured

React Fundamentals: Complete Guide to Components, Props, and State

Master React basics with this comprehensive guide covering components, props, state management, and event handling. Perfect for beginners starting their React journey.

By
reactcomponentspropsstatefundamentalsbeginnerjsxevents

React Fundamentals: Complete Guide to Components, Props, and State

React is a powerful JavaScript library for building user interfaces, particularly single-page applications where you need a fast, interactive user experience. This comprehensive guide will take you through the fundamental concepts that form the foundation of React development.

What is React?

React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It was created by Facebook (now Meta) and has become one of the most popular frontend libraries in the web development ecosystem.

Key Benefits of React

  • Component-Based Architecture: Build encapsulated components that manage their own state
  • Virtual DOM: Efficient updates through virtual DOM diffing algorithm
  • Declarative Programming: Describe what the UI should look like for any given state
  • Large Ecosystem: Extensive community and third-party library support
  • Developer Experience: Excellent debugging tools and development workflow

Setting Up Your First React Application

Before diving into React concepts, let's set up a new React application using Create React App:

# Create a new React app
npx create-react-app my-react-app

# Navigate to the project directory
cd my-react-app

# Start the development server
npm start

This will create a new React application with all the necessary dependencies and configuration.

Understanding JSX

JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code in your JavaScript files. It's the primary way to describe the UI in React.

Basic JSX Syntax

// Simple JSX element
const element = <h1>Hello, React!</h1>;

// JSX with variables
const name = 'World';
const greeting = <h1>Hello, {name}!</h1>;

// JSX with expressions
const user = { firstName: 'John', lastName: 'Doe' };
const welcome = (
  <h1>
    Welcome, {user.firstName} {user.lastName}!
  </h1>
);

JSX Rules and Best Practices

// 1. JSX must return a single parent element
const ValidComponent = () => {
  return (
    <div>
      <h1>Title</h1>
      <p>Content</p>
    </div>
  );
};

// 2. Use React.Fragment or shorthand <> for multiple elements
const FragmentExample = () => {
  return (
    <>
      <h1>Title</h1>
      <p>Content</p>
    </>
  );
};

// 3. Use camelCase for HTML attributes
const AttributeExample = () => {
  return (
    <div className="container" onClick={handleClick}>
      <label htmlFor="email">Email:</label>
      <input type="email" id="email" />
    </div>
  );
};

// 4. Close all tags
const SelfClosingTags = () => {
  return (
    <div>
      <img src="image.jpg" alt="Description" />
      <input type="text" />
      <br />
    </div>
  );
};

React Components

Components are the building blocks of React applications. They let you split the UI into independent, reusable pieces.

Function Components

Function components are the modern way to create React components:

// Simple function component
function Welcome() {
  return <h1>Welcome to React!</h1>;
}

// Arrow function component
const Greeting = () => {
  return <h1>Hello from Arrow Function!</h1>;
};

// Component with parameters
const PersonalGreeting = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

// Using the components
const App = () => {
  return (
    <div>
      <Welcome />
      <Greeting />
      <PersonalGreeting name="Alice" />
    </div>
  );
};

Component Composition

// Header component
const Header = () => {
  return (
    <header className="app-header">
      <h1>My React App</h1>
      <nav>
        <a href="#home">Home</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
      </nav>
    </header>
  );
};

// Main content component
const MainContent = () => {
  return (
    <main className="main-content">
      <h2>Welcome to Our Website</h2>
      <p>This is the main content area.</p>
    </main>
  );
};

// Footer component
const Footer = () => {
  return (
    <footer className="app-footer">
      <p>&copy; 2024 My React App. All rights reserved.</p>
    </footer>
  );
};

// App component using composition
const App = () => {
  return (
    <div className="app">
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
};

Understanding Props

Props (short for properties) are how components receive data from their parent components. They make components reusable and dynamic.

Basic Props Usage

// Component that receives props
const UserCard = (props) => {
  return (
    <div className="user-card">
      <h3>{props.name}</h3>
      <p>Age: {props.age}</p>
      <p>Email: {props.email}</p>
    </div>
  );
};

// Using the component with props
const App = () => {
  return (
    <div>
      <UserCard name="John Doe" age={30} email="john@example.com" />
      <UserCard name="Jane Smith" age={25} email="jane@example.com" />
    </div>
  );
};

Props Destructuring

// Destructuring props in function parameters
const UserCard = ({ name, age, email, avatar }) => {
  return (
    <div className="user-card">
      <img src={avatar} alt={`${name} avatar`} />
      <h3>{name}</h3>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
    </div>
  );
};

// Default props
const Button = ({ text = 'Click me', type = 'button', onClick }) => {
  return (
    <button type={type} onClick={onClick} className="btn">
      {text}
    </button>
  );
};

Props Validation with PropTypes

import PropTypes from 'prop-types';

const UserCard = ({ name, age, email, isActive }) => {
  return (
    <div className={`user-card ${isActive ? 'active' : 'inactive'}`}>
      <h3>{name}</h3>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
      <span className="status">{isActive ? 'Active' : 'Inactive'}</span>
    </div>
  );
};

// PropTypes validation
UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  email: PropTypes.string.isRequired,
  isActive: PropTypes.bool,
};

// Default props
UserCard.defaultProps = {
  isActive: true,
};

Children Props

// Component that uses children prop
const Card = ({ title, children }) => {
  return (
    <div className="card">
      <div className="card-header">
        <h3>{title}</h3>
      </div>
      <div className="card-body">{children}</div>
    </div>
  );
};

// Using children prop
const App = () => {
  return (
    <div>
      <Card title="User Information">
        <p>This content is passed as children</p>
        <button>Edit Profile</button>
      </Card>

      <Card title="Settings">
        <form>
          <input type="text" placeholder="Username" />
          <input type="email" placeholder="Email" />
          <button type="submit">Save</button>
        </form>
      </Card>
    </div>
  );
};

State Management with useState

State allows components to create and manage their own data. React provides the useState hook for functional components.

Basic useState Usage

import React, { useState } from 'react';

const Counter = () => {
  // Declare state variable with initial value
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  const reset = () => {
    setCount(0);
  };

  return (
    <div className="counter">
      <h2>Count: {count}</h2>
      <div className="counter-buttons">
        <button onClick={increment}>+</button>
        <button onClick={decrement}>-</button>
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  );
};

State with Objects and Arrays

import React, { useState } from 'react';

const UserProfile = () => {
  // State with object
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0,
  });

  // State with array
  const [hobbies, setHobbies] = useState([]);
  const [newHobby, setNewHobby] = useState('');

  const updateUser = (field, value) => {
    setUser((prevUser) => ({
      ...prevUser,
      [field]: value,
    }));
  };

  const addHobby = () => {
    if (newHobby.trim() !== '') {
      setHobbies((prevHobbies) => [...prevHobbies, newHobby]);
      setNewHobby('');
    }
  };

  const removeHobby = (index) => {
    setHobbies((prevHobbies) => prevHobbies.filter((_, i) => i !== index));
  };

  return (
    <div className="user-profile">
      <h2>User Profile</h2>

      <div className="form-group">
        <input
          type="text"
          placeholder="Name"
          value={user.name}
          onChange={(e) => updateUser('name', e.target.value)}
        />
        <input
          type="email"
          placeholder="Email"
          value={user.email}
          onChange={(e) => updateUser('email', e.target.value)}
        />
        <input
          type="number"
          placeholder="Age"
          value={user.age}
          onChange={(e) => updateUser('age', parseInt(e.target.value))}
        />
      </div>

      <div className="hobbies-section">
        <h3>Hobbies</h3>
        <div className="add-hobby">
          <input
            type="text"
            placeholder="Add a hobby"
            value={newHobby}
            onChange={(e) => setNewHobby(e.target.value)}
          />
          <button onClick={addHobby}>Add</button>
        </div>

        <ul className="hobbies-list">
          {hobbies.map((hobby, index) => (
            <li key={index}>
              {hobby}
              <button onClick={() => removeHobby(index)}>Remove</button>
            </li>
          ))}
        </ul>
      </div>

      <div className="user-preview">
        <h3>Preview</h3>
        <p>Name: {user.name}</p>
        <p>Email: {user.email}</p>
        <p>Age: {user.age}</p>
        <p>Hobbies: {hobbies.join(', ')}</p>
      </div>
    </div>
  );
};

Conditional State Updates

import React, { useState } from 'react';

const LoginForm = () => {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
    rememberMe: false,
  });
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState('');

  const handleInputChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: type === 'checkbox' ? checked : value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    setError('');

    try {
      // Simulate API call
      await new Promise((resolve) => setTimeout(resolve, 2000));

      if (formData.username === 'admin' && formData.password === 'password') {
        alert('Login successful!');
      } else {
        setError('Invalid username or password');
      }
    } catch (err) {
      setError('An error occurred during login');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="login-form">
      <h2>Login</h2>

      {error && <div className="error-message">{error}</div>}

      <div className="form-group">
        <input
          type="text"
          name="username"
          placeholder="Username"
          value={formData.username}
          onChange={handleInputChange}
          required
        />
      </div>

      <div className="form-group">
        <input
          type="password"
          name="password"
          placeholder="Password"
          value={formData.password}
          onChange={handleInputChange}
          required
        />
      </div>

      <div className="form-group">
        <label>
          <input
            type="checkbox"
            name="rememberMe"
            checked={formData.rememberMe}
            onChange={handleInputChange}
          />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
};

Event Handling

React uses SyntheticEvents to handle events in a consistent way across different browsers.

Basic Event Handling

import React, { useState } from 'react';

const EventExamples = () => {
  const [message, setMessage] = useState('');
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  // Click event handler
  const handleClick = (e) => {
    console.log('Button clicked!', e);
    setMessage('Button was clicked!');
  };

  // Input change handler
  const handleInputChange = (e) => {
    setMessage(e.target.value);
  };

  // Mouse move handler
  const handleMouseMove = (e) => {
    setMousePosition({
      x: e.clientX,
      y: e.clientY,
    });
  };

  // Form submit handler
  const handleSubmit = (e) => {
    e.preventDefault(); // Prevent page reload
    alert(`Form submitted with message: ${message}`);
  };

  // Key press handler
  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      alert('Enter key pressed!');
    }
  };

  return (
    <div className="event-examples" onMouseMove={handleMouseMove}>
      <h2>Event Handling Examples</h2>

      <p>
        Mouse Position: X: {mousePosition.x}, Y: {mousePosition.y}
      </p>

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={message}
          onChange={handleInputChange}
          onKeyPress={handleKeyPress}
          placeholder="Type something..."
        />
        <button type="submit">Submit</button>
      </form>

      <button onClick={handleClick}>Click me</button>

      <p>Current message: {message}</p>
    </div>
  );
};

Event Handler Patterns

import React, { useState } from 'react';

const AdvancedEventHandling = () => {
  const [items, setItems] = useState([
    { id: 1, name: 'Item 1', completed: false },
    { id: 2, name: 'Item 2', completed: true },
    { id: 3, name: 'Item 3', completed: false },
  ]);

  // Handler with parameters
  const toggleItem = (id) => {
    setItems((prevItems) =>
      prevItems.map((item) =>
        item.id === id ? { ...item, completed: !item.completed } : item
      )
    );
  };

  // Handler for dynamic events
  const handleItemAction = (id, action) => {
    switch (action) {
      case 'toggle':
        toggleItem(id);
        break;
      case 'delete':
        setItems((prevItems) => prevItems.filter((item) => item.id !== id));
        break;
      default:
        console.log('Unknown action:', action);
    }
  };

  // Inline handler with arrow function
  const addItem = () => {
    const newItem = {
      id: Date.now(),
      name: `Item ${items.length + 1}`,
      completed: false,
    };
    setItems((prevItems) => [...prevItems, newItem]);
  };

  return (
    <div className="advanced-events">
      <h2>Advanced Event Handling</h2>

      <button onClick={addItem}>Add Item</button>

      <ul className="items-list">
        {items.map((item) => (
          <li key={item.id} className={item.completed ? 'completed' : ''}>
            <span>{item.name}</span>
            <div className="item-actions">
              <button onClick={() => handleItemAction(item.id, 'toggle')}>
                {item.completed ? 'Undo' : 'Complete'}
              </button>
              <button
                onClick={() => handleItemAction(item.id, 'delete')}
                className="delete-btn"
              >
                Delete
              </button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
};

Component Communication

Parent to Child Communication

// Parent component
const ParentComponent = () => {
  const [userTheme, setUserTheme] = useState('light');
  const [userData, setUserData] = useState({
    name: 'John Doe',
    email: 'john@example.com',
  });

  return (
    <div className={`app ${userTheme}`}>
      <ChildComponent
        theme={userTheme}
        user={userData}
        onThemeChange={setUserTheme}
      />
    </div>
  );
};

// Child component
const ChildComponent = ({ theme, user, onThemeChange }) => {
  const toggleTheme = () => {
    onThemeChange(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <div className="child-component">
      <h2>Hello, {user.name}!</h2>
      <p>Current theme: {theme}</p>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'dark' : 'light'} theme
      </button>
    </div>
  );
};

Child to Parent Communication

import React, { useState } from 'react';

// Child component that sends data to parent
const ContactForm = ({ onSubmit }) => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    // Send data to parent
    onSubmit(formData);
    // Reset form
    setFormData({ name: '', email: '', message: '' });
  };

  const handleChange = (e) => {
    setFormData((prev) => ({
      ...prev,
      [e.target.name]: e.target.value,
    }));
  };

  return (
    <form onSubmit={handleSubmit} className="contact-form">
      <h3>Contact Form</h3>
      <input
        type="text"
        name="name"
        placeholder="Your Name"
        value={formData.name}
        onChange={handleChange}
        required
      />
      <input
        type="email"
        name="email"
        placeholder="Your Email"
        value={formData.email}
        onChange={handleChange}
        required
      />
      <textarea
        name="message"
        placeholder="Your Message"
        value={formData.message}
        onChange={handleChange}
        required
      />
      <button type="submit">Send Message</button>
    </form>
  );
};

// Parent component that receives data from child
const ContactPage = () => {
  const [submissions, setSubmissions] = useState([]);

  const handleFormSubmit = (formData) => {
    const newSubmission = {
      ...formData,
      id: Date.now(),
      timestamp: new Date().toLocaleString(),
    };
    setSubmissions((prev) => [...prev, newSubmission]);
    alert('Message sent successfully!');
  };

  return (
    <div className="contact-page">
      <h2>Contact Us</h2>
      <ContactForm onSubmit={handleFormSubmit} />

      <div className="submissions">
        <h3>Recent Submissions</h3>
        {submissions.map((submission) => (
          <div key={submission.id} className="submission">
            <p>
              <strong>From:</strong> {submission.name} ({submission.email})
            </p>
            <p>
              <strong>Message:</strong> {submission.message}
            </p>
            <p>
              <small>Sent: {submission.timestamp}</small>
            </p>
          </div>
        ))}
      </div>
    </div>
  );
};

Complete Example: Todo Application

Let's build a complete todo application that demonstrates all the concepts we've learned:

import React, { useState } from 'react';

// Individual Todo Item Component
const TodoItem = ({ todo, onToggle, onDelete, onEdit }) => {
  const [isEditing, setIsEditing] = useState(false);
  const [editText, setEditText] = useState(todo.text);

  const handleSave = () => {
    if (editText.trim()) {
      onEdit(todo.id, editText.trim());
      setIsEditing(false);
    }
  };

  const handleCancel = () => {
    setEditText(todo.text);
    setIsEditing(false);
  };

  return (
    <li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />

      {isEditing ? (
        <div className="edit-mode">
          <input
            type="text"
            value={editText}
            onChange={(e) => setEditText(e.target.value)}
            onKeyPress={(e) => e.key === 'Enter' && handleSave()}
          />
          <button onClick={handleSave}>Save</button>
          <button onClick={handleCancel}>Cancel</button>
        </div>
      ) : (
        <div className="view-mode">
          <span className="todo-text">{todo.text}</span>
          <div className="todo-actions">
            <button onClick={() => setIsEditing(true)}>Edit</button>
            <button onClick={() => onDelete(todo.id)}>Delete</button>
          </div>
        </div>
      )}
    </li>
  );
};

// Add Todo Form Component
const AddTodoForm = ({ onAdd }) => {
  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      onAdd(text.trim());
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="add-todo-form">
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="What needs to be done?"
        autoFocus
      />
      <button type="submit">Add Todo</button>
    </form>
  );
};

// Filter Buttons Component
const FilterButtons = ({ filter, onFilterChange }) => {
  const filters = [
    { key: 'all', label: 'All' },
    { key: 'active', label: 'Active' },
    { key: 'completed', label: 'Completed' },
  ];

  return (
    <div className="filter-buttons">
      {filters.map((f) => (
        <button
          key={f.key}
          className={filter === f.key ? 'active' : ''}
          onClick={() => onFilterChange(f.key)}
        >
          {f.label}
        </button>
      ))}
    </div>
  );
};

// Main Todo Application Component
const TodoApp = () => {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React fundamentals', completed: false },
    { id: 2, text: 'Build a todo app', completed: false },
    { id: 3, text: 'Master React hooks', completed: false },
  ]);
  const [filter, setFilter] = useState('all');

  // Add new todo
  const addTodo = (text) => {
    const newTodo = {
      id: Date.now(),
      text,
      completed: false,
    };
    setTodos((prevTodos) => [...prevTodos, newTodo]);
  };

  // Toggle todo completion
  const toggleTodo = (id) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  // Delete todo
  const deleteTodo = (id) => {
    setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
  };

  // Edit todo
  const editTodo = (id, newText) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo) =>
        todo.id === id ? { ...todo, text: newText } : todo
      )
    );
  };

  // Filter todos based on current filter
  const filteredTodos = todos.filter((todo) => {
    switch (filter) {
      case 'active':
        return !todo.completed;
      case 'completed':
        return todo.completed;
      default:
        return true;
    }
  });

  // Calculate statistics
  const totalTodos = todos.length;
  const completedTodos = todos.filter((todo) => todo.completed).length;
  const activeTodos = totalTodos - completedTodos;

  return (
    <div className="todo-app">
      <header className="todo-header">
        <h1>Todo Application</h1>
        <div className="todo-stats">
          <span>Total: {totalTodos}</span>
          <span>Active: {activeTodos}</span>
          <span>Completed: {completedTodos}</span>
        </div>
      </header>

      <AddTodoForm onAdd={addTodo} />

      <FilterButtons filter={filter} onFilterChange={setFilter} />

      <ul className="todo-list">
        {filteredTodos.map((todo) => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={toggleTodo}
            onDelete={deleteTodo}
            onEdit={editTodo}
          />
        ))}
      </ul>

      {filteredTodos.length === 0 && (
        <div className="empty-state">
          <p>No todos found for the current filter.</p>
        </div>
      )}
    </div>
  );
};

export default TodoApp;

Best Practices and Common Patterns

Component Organization

// 1. Keep components small and focused
const Button = ({ children, variant = 'primary', onClick, ...props }) => {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick} {...props}>
      {children}
    </button>
  );
};

// 2. Use composition over inheritance
const Modal = ({ isOpen, onClose, title, children }) => {
  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{title}</h2>
          <button onClick={onClose}>×</button>
        </div>
        <div className="modal-body">{children}</div>
      </div>
    </div>
  );
};

// 3. Extract reusable logic into custom hooks
const useToggle = (initialValue = false) => {
  const [value, setValue] = useState(initialValue);

  const toggle = () => setValue((prev) => !prev);
  const setTrue = () => setValue(true);
  const setFalse = () => setValue(false);

  return [value, toggle, setTrue, setFalse];
};

// Using the custom hook
const Example = () => {
  const [isVisible, toggleVisible, show, hide] = useToggle(false);

  return (
    <div>
      <button onClick={toggleVisible}>{isVisible ? 'Hide' : 'Show'}</button>
      {isVisible && <p>This content is toggleable!</p>}
    </div>
  );
};

Performance Considerations

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

const OptimizedComponent = ({ items, category }) => {
  const [search, setSearch] = useState('');

  // Memoize expensive calculations
  const filteredItems = useMemo(() => {
    return items.filter(
      (item) =>
        item.category === category &&
        item.name.toLowerCase().includes(search.toLowerCase())
    );
  }, [items, category, search]);

  // Memoize event handlers
  const handleSearchChange = useCallback((e) => {
    setSearch(e.target.value);
  }, []);

  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={handleSearchChange}
        placeholder="Search items..."
      />
      <ul>
        {filteredItems.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

Conclusion

React fundamentals form the foundation of modern web development with React. Understanding components, props, state, and event handling is essential for building interactive user interfaces.

Key Takeaways

  1. Components: Build reusable, modular UI pieces
  2. Props: Pass data from parent to child components
  3. State: Manage dynamic data within components
  4. Events: Handle user interactions and system events
  5. JSX: Write HTML-like syntax in JavaScript
  6. Composition: Combine components to build complex UIs

Next Steps

Now that you understand React fundamentals, you're ready to explore:

  • React Hooks: Deep dive into useEffect, useContext, and custom hooks
  • State Management: Learn about Redux, Context API, and other state management solutions
  • React Router: Build single-page applications with client-side routing
  • Performance Optimization: Learn about React.memo, useMemo, and useCallback
  • Testing: Write tests for your React components

React's declarative nature and component-based architecture make it an excellent choice for building modern web applications. Practice these fundamentals, and you'll be well on your way to mastering React development!