Skip to content

React Component Communication

Overview

In React applications, data passing and communication between components is key to building complex interfaces. This chapter will learn various component communication methods, including parent-child communication, sibling component communication, cross-level communication, and best practices.

📤 Parent-Child Component Communication

Parent to Child Data Passing

jsx
// Child component: User card
function UserCard({ user, onEdit, onDelete, showActions = true }) {
  return (
    <div style={{
      border: '1px solid #ddd',
      borderRadius: '8px',
      padding: '16px',
      margin: '8px',
      backgroundColor: '#f9f9f9'
    }}>
      <div style={{ display: 'flex', alignItems: 'center', marginBottom: '12px' }}>
        <img 
          src={user.avatar || '/default-avatar.png'} 
          alt={user.name}
          style={{ width: '50px', height: '50px', borderRadius: '50%', marginRight: '12px' }}
        />
        <div>
          <h3 style={{ margin: 0 }}>{user.name}</h3>
          <p style={{ margin: 0, color: '#666' }}>{user.email}</p>
        </div>
      </div>
      
      <div style={{ marginBottom: '12px' }}>
        <p><strong>Position:</strong> {user.position}</p>
        <p><strong>Department:</strong> {user.department}</p>
        <p><strong>Status:</strong> 
          <span style={{ 
            color: user.isActive ? 'green' : 'red',
            fontWeight: 'bold'
          }}>
            {user.isActive ? 'Online' : 'Offline'}
          </span>
        </p>
      </div>
      
      {showActions && (
        <div style={{ display: 'flex', gap: '8px' }}>
          <button 
            onClick={() => onEdit(user.id)}
            style={{ backgroundColor: '#007bff', color: 'white', border: 'none', padding: '8px 16px', borderRadius: '4px' }}
          >
            Edit
          </button>
          <button 
            onClick={() => onDelete(user.id)}
            style={{ backgroundColor: '#dc3545', color: 'white', border: 'none', padding: '8px 16px', borderRadius: '4px' }}
          >
            Delete
          </button>
        </div>
      )}
    </div>
  );
}

// Parent component: User list
function UserList() {
  const [users, setUsers] = React.useState([
    {
      id: 1,
      name: 'Zhang San',
      email: 'zhangsan@example.com',
      position: 'Frontend Developer',
      department: 'Engineering',
      isActive: true,
      avatar: '/avatars/zhangsan.jpg'
    },
    {
      id: 2,
      name: 'Li Si',
      email: 'lisi@example.com',
      position: 'Backend Developer',
      department: 'Engineering',
      isActive: false,
      avatar: '/avatars/lisi.jpg'
    }
  ]);
  
  const [filter, setFilter] = React.useState('all');
  
  const handleEdit = (userId) => {
    console.log('Edit user:', userId);
    // Implement edit logic
  };
  
  const handleDelete = (userId) => {
    if (window.confirm('Are you sure you want to delete this user?')) {
      setUsers(users.filter(user => user.id !== userId));
    }
  };
  
  const filteredUsers = users.filter(user => {
    if (filter === 'active') return user.isActive;
    if (filter === 'inactive') return !user.isActive;
    return true;
  });
  
  return (
    <div>
      <h2>User Management</h2>
      
      {/* Filter */}
      <div style={{ marginBottom: '20px' }}>
        <label>Filter: </label>
        <select value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">All</option>
          <option value="active">Online</option>
          <option value="inactive">Offline</option>
        </select>
      </div>
      
      {/* User list */}
      <div>
        {filteredUsers.map(user => (
          <UserCard
            key={user.id}
            user={user}
            onEdit={handleEdit}
            onDelete={handleDelete}
            showActions={true}
          />
        ))}
      </div>
      
      {filteredUsers.length === 0 && (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          No users found matching criteria
        </div>
      )}
    </div>
  );
}

Child to Parent Data Passing

jsx
// Child component: Form
function UserForm({ initialUser = null, onSubmit, onCancel }) {
  const [formData, setFormData] = React.useState({
    name: initialUser?.name || '',
    email: initialUser?.email || '',
    position: initialUser?.position || '',
    department: initialUser?.department || ''
  });
  
  const [errors, setErrors] = React.useState({});
  
  const validateForm = () => {
    const newErrors = {};
    
    if (!formData.name.trim()) {
      newErrors.name = 'Name cannot be empty';
    }
    
    if (!formData.email.trim()) {
      newErrors.email = 'Email cannot be empty';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Invalid email format';
    }
    
    if (!formData.position.trim()) {
      newErrors.position = 'Position cannot be empty';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    
    // Clear corresponding field error
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (validateForm()) {
      // Pass data to parent component
      onSubmit({
        ...formData,
        id: initialUser?.id || Date.now()
      });
    }
  };
  
  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '20px 0' }}>
      <h3>{initialUser ? 'Edit User' : 'Add User'}</h3>
      
      <div style={{ marginBottom: '15px' }}>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
          style={{ 
            width: '100%', 
            padding: '8px', 
            borderColor: errors.name ? 'red' : '#ccc'
          }}
        />
        {errors.name && <div style={{ color: 'red', fontSize: '12px' }}>{errors.name}</div>}
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          style={{ 
            width: '100%', 
            padding: '8px',
            borderColor: errors.email ? 'red' : '#ccc'
          }}
        />
        {errors.email && <div style={{ color: 'red', fontSize: '12px' }}>{errors.email}</div>}
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>Position:</label>
        <input
          type="text"
          name="position"
          value={formData.position}
          onChange={handleChange}
          style={{ 
            width: '100%', 
            padding: '8px',
            borderColor: errors.position ? 'red' : '#ccc'
          }}
        />
        {errors.position && <div style={{ color: 'red', fontSize: '12px' }}>{errors.position}</div>}
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>Department:</label>
        <select
          name="department"
          value={formData.department}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        >
          <option value="">Select Department</option>
          <option value="Engineering">Engineering</option>
          <option value="Product">Product</option>
          <option value="Design">Design</option>
          <option value="Marketing">Marketing</option>
        </select>
      </div>
      
      <div style={{ display: 'flex', gap: '10px' }}>
        <button type="submit" style={{ flex: 1, padding: '10px', backgroundColor: '#007bff', color: 'white', border: 'none' }}>
          {initialUser ? 'Update' : 'Add'}
        </button>
        <button type="button" onClick={onCancel} style={{ flex: 1, padding: '10px', backgroundColor: '#6c757d', color: 'white', border: 'none' }}>
          Cancel
        </button>
      </div>
    </form>
  );
}

// Parent component: User management
function UserManager() {
  const [users, setUsers] = React.useState([]);
  const [showForm, setShowForm] = React.useState(false);
  const [editingUser, setEditingUser] = React.useState(null);
  
  const handleAddUser = (userData) => {
    setUsers(prev => [...prev, { ...userData, isActive: true }]);
    setShowForm(false);
  };
  
  const handleEditUser = (userData) => {
    setUsers(prev => prev.map(user => 
      user.id === userData.id ? { ...user, ...userData } : user
    ));
    setEditingUser(null);
    setShowForm(false);
  };
  
  const handleDeleteUser = (userId) => {
    setUsers(prev => prev.filter(user => user.id !== userId));
  };
  
  const startEdit = (userId) => {
    const user = users.find(u => u.id === userId);
    setEditingUser(user);
    setShowForm(true);
  };
  
  const cancelForm = () => {
    setShowForm(false);
    setEditingUser(null);
  };
  
  return (
    <div>
      <h2>User Management System</h2>
      
      <button 
        onClick={() => setShowForm(true)}
        disabled={showForm}
        style={{ 
          padding: '10px 20px', 
          backgroundColor: '#28a745', 
          color: 'white', 
          border: 'none', 
          borderRadius: '4px',
          marginBottom: '20px'
        }}
      >
        Add User
      </button>
      
      {showForm && (
        <UserForm
          initialUser={editingUser}
          onSubmit={editingUser ? handleEditUser : handleAddUser}
          onCancel={cancelForm}
        />
      )}
      
      <div>
        {users.map(user => (
          <UserCard
            key={user.id}
            user={user}
            onEdit={startEdit}
            onDelete={handleDeleteUser}
          />
        ))}
      </div>
    </div>
  );
}

🔄 Sibling Component Communication

Communicating Through Common Parent

jsx
// Product component
function ProductList({ products, onSelectProduct }) {
  return (
    <div style={{ width: '300px', border: '1px solid #ddd', padding: '20px' }}>
      <h3>Product List</h3>
      {products.map(product => (
        <div 
          key={product.id}
          onClick={() => onSelectProduct(product)}
          style={{
            padding: '10px',
            margin: '5px 0',
            border: '1px solid #ccc',
            borderRadius: '4px',
            cursor: 'pointer',
            backgroundColor: '#f8f9fa'
          }}
        >
          <div style={{ fontWeight: 'bold' }}>{product.name}</div>
          <div style={{ color: '#666' }}>${product.price}</div>
        </div>
      ))}
    </div>
  );
}

// Product detail component
function ProductDetail({ product }) {
  if (!product) {
    return (
      <div style={{ width: '400px', border: '1px solid #ddd', padding: '20px' }}>
        <h3>Product Detail</h3>
        <p>Please select a product to view details</p>
      </div>
    );
  }
  
  return (
    <div style={{ width: '400px', border: '1px solid #ddd', padding: '20px' }}>
      <h3>Product Detail</h3>
      <img 
        src={product.image || '/placeholder.jpg'} 
        alt={product.name}
        style={{ width: '100%', height: '200px', objectFit: 'cover', marginBottom: '15px' }}
      />
      <h4>{product.name}</h4>
      <p style={{ fontSize: '24px', color: '#007bff', fontWeight: 'bold' }}>
        ${product.price}
      </p>
      <p><strong>Category:</strong> {product.category}</p>
      <p><strong>Description:</strong> {product.description}</p>
      <p><strong>Stock:</strong> {product.stock} units</p>
      
      <button 
        style={{ 
          padding: '10px 20px', 
          backgroundColor: '#007bff', 
          color: 'white', 
          border: 'none', 
          borderRadius: '4px',
          width: '100%'
        }}
        disabled={product.stock === 0}
      >
        {product.stock > 0 ? 'Add to Cart' : 'Out of Stock'}
      </button>
    </div>
  );
}

// Parent component: Manage sibling component communication
function ProductCatalog() {
  const [products] = React.useState([
    {
      id: 1,
      name: 'iPhone 15',
      price: 5999,
      category: 'Smartphones',
      description: 'Latest iPhone with A17 chip',
      stock: 10,
      image: '/products/iphone15.jpg'
    },
    {
      id: 2,
      name: 'MacBook Pro',
      price: 12999,
      category: 'Laptops',
      description: 'Powerful professional laptop',
      stock: 5,
      image: '/products/macbook.jpg'
    },
    {
      id: 3,
      name: 'AirPods Pro',
      price: 1899,
      category: 'Headphones',
      description: 'Active noise cancelling wireless earbuds',
      stock: 0,
      image: '/products/airpods.jpg'
    }
  ]);
  
  const [selectedProduct, setSelectedProduct] = React.useState(null);
  
  return (
    <div>
      <h2>Product Catalog</h2>
      <div style={{ display: 'flex', gap: '20px' }}>
        <ProductList 
          products={products} 
          onSelectProduct={setSelectedProduct}
        />
        <ProductDetail product={selectedProduct} />
      </div>
    </div>
  );
}

🌐 Context Cross-Level Communication

Creating and Using Context

jsx
// Create theme context
const ThemeContext = React.createContext();

// Create user context
const UserContext = React.createContext();

// Theme provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = React.useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  const themeStyles = {
    light: {
      backgroundColor: '#ffffff',
      color: '#000000',
      borderColor: '#dddddd'
    },
    dark: {
      backgroundColor: '#2d3748',
      color: '#ffffff',
      borderColor: '#4a5568'
    }
  };
  
  return (
    <ThemeContext.Provider value={{
      theme,
      toggleTheme,
      styles: themeStyles[theme]
    }}>
      {children}
    </ThemeContext.Provider>
  );
}

// User provider component
function UserProvider({ children }) {
  const [user, setUser] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(false);
  
  const login = async (credentials) => {
    setIsLoading(true);
    try {
      // Simulate login API
      await new Promise(resolve => setTimeout(resolve, 1000));
      setUser({
        id: 1,
        name: credentials.username,
        email: `${credentials.username}@example.com`,
        role: 'user'
      });
    } catch (error) {
      console.error('Login failed:', error);
    } finally {
      setIsLoading(false);
    }
  };
  
  const logout = () => {
    setUser(null);
  };
  
  return (
    <UserContext.Provider value={{
      user,
      isLoading,
      login,
      logout,
      isAuthenticated: !!user
    }}>
      {children}
    </UserContext.Provider>
  );
}

// Custom hooks
function useTheme() {
  const context = React.useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

function useUser() {
  const context = React.useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
}

// Header component
function Header() {
  const { theme, toggleTheme, styles } = useTheme();
  const { user, logout, isAuthenticated } = useUser();
  
  return (
    <header style={{
      ...styles,
      padding: '20px',
      borderBottom: `1px solid ${styles.borderColor}`,
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center'
    }}>
      <h1>My App</h1>
      
      <div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
        <button onClick={toggleTheme} style={{
          backgroundColor: styles.backgroundColor,
          color: styles.color,
          border: `1px solid ${styles.borderColor}`,
          padding: '8px 16px',
          borderRadius: '4px'
        }}>
          {theme === 'light' ? '🌙' : '☀️'} {theme === 'light' ? 'Dark' : 'Light'}
        </button>
        
        {isAuthenticated ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
            <span>Welcome, {user.name}</span>
            <button onClick={logout} style={{
              backgroundColor: '#dc3545',
              color: 'white',
              border: 'none',
              padding: '8px 16px',
              borderRadius: '4px'
            }}>
              Logout
            </button>
          </div>
        ) : (
          <LoginForm />
        )}
      </div>
    </header>
  );
}

// Login form component
function LoginForm() {
  const [credentials, setCredentials] = React.useState({
    username: '',
    password: ''
  });
  const { login, isLoading } = useUser();
  const { styles } = useTheme();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    login(credentials);
  };
  
  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', gap: '10px' }}>
      <input
        type="text"
        placeholder="Username"
        value={credentials.username}
        onChange={(e) => setCredentials({ ...credentials, username: e.target.value })}
        style={{
          padding: '8px',
          backgroundColor: styles.backgroundColor,
          color: styles.color,
          border: `1px solid ${styles.borderColor}`,
          borderRadius: '4px'
        }}
      />
      <input
        type="password"
        placeholder="Password"
        value={credentials.password}
        onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
        style={{
          padding: '8px',
          backgroundColor: styles.backgroundColor,
          color: styles.color,
          border: `1px solid ${styles.borderColor}`,
          borderRadius: '4px'
        }}
      />
      <button 
        type="submit" 
        disabled={isLoading}
        style={{
          backgroundColor: '#007bff',
          color: 'white',
          border: 'none',
          padding: '8px 16px',
          borderRadius: '4px'
        }}
      >
        {isLoading ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

// Main content component
function MainContent() {
  const { styles } = useTheme();
  const { isAuthenticated, user } = useUser();
  
  return (
    <main style={{
      ...styles,
      padding: '20px',
      minHeight: '400px'
    }}>
      {isAuthenticated ? (
        <div>
          <h2>Welcome back, {user.name}!</h2>
          <p>This is protected content area.</p>
          <UserProfile />
        </div>
      ) : (
        <div>
          <h2>Please login first</h2>
          <p>Login to view more content.</p>
        </div>
      )}
    </main>
  );
}

// User profile component
function UserProfile() {
  const { user } = useUser();
  const { styles } = useTheme();
  
  return (
    <div style={{
      ...styles,
      border: `1px solid ${styles.borderColor}`,
      borderRadius: '8px',
      padding: '20px',
      marginTop: '20px'
    }}>
      <h3>User Profile</h3>
      <p><strong>Name:</strong> {user.name}</p>
      <p><strong>Email:</strong> {user.email}</p>
      <p><strong>Role:</strong> {user.role}</p>
    </div>
  );
}

// App root component
function ContextApp() {
  return (
    <ThemeProvider>
      <UserProvider>
        <div>
          <Header />
          <MainContent />
        </div>
      </UserProvider>
    </ThemeProvider>
  );
}

📝 Chapter Summary

Through this chapter, you should have mastered:

Communication Methods

  • ✅ Parent-child component communication: Props and callbacks
  • ✅ Sibling component communication: Through common parent
  • ✅ Cross-level communication: Context API
  • ✅ Complex state management: State lifting pattern

Best Practices

  1. Unidirectional Data Flow: Keep data flow clear
  2. State Lifting: Lift shared state to the nearest common parent
  3. Context Usage: Suitable for cross-level global state
  4. Component Decoupling: Communicate through interfaces rather than implementation
  5. Performance Optimization: Use memo and useCallback to optimize rendering

Continue Learning: Next Chapter - React Conditional Rendering

Content is for learning and research only.