Skip to content

React Conditional and List Rendering

Overview

In React, we often need to display different content based on different conditions or render dynamic list data. This chapter will learn how to perform conditional rendering and list rendering in React, mastering various rendering techniques and best practices.

🔀 Conditional Rendering

Basic Conditional Rendering

jsx
function BasicConditionalRendering() {
  const [isLoggedIn, setIsLoggedIn] = React.useState(false);
  const [user, setUser] = React.useState(null);

  const login = () => {
    setUser({ name: 'John', role: 'admin' });
    setIsLoggedIn(true);
  };

  const logout = () => {
    setUser(null);
    setIsLoggedIn(false);
  };

  // Method 1: Using if statement
  if (!isLoggedIn) {
    return (
      <div>
        <h2>Please login</h2>
        <button onClick={login}>Login</button>
      </div>
    );
  }

  return (
    <div>
      <h2>Welcome, {user.name}!</h2>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Ternary Operator

jsx
function TernaryOperator() {
  const [loading, setLoading] = React.useState(false);
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);

  const fetchData = async () => {
    setLoading(true);
    setError(null);

    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 2000));
      setData({ message: 'Data loaded successfully!' });
    } catch (err) {
      setError('Loading failed');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <h2>Data Fetching Example</h2>
      <button onClick={fetchData} disabled={loading}>
        {loading ? 'Loading...' : 'Fetch Data'}
      </button>

      {/* Nested ternary operator */}
      {loading ? (
        <div>Loading data...</div>
      ) : error ? (
        <div style={{ color: 'red' }}>Error: {error}</div>
      ) : data ? (
        <div style={{ color: 'green' }}>{data.message}</div>
      ) : (
        <div>Click button to fetch data</div>
      )}
    </div>
  );
}

Logical AND Operator

jsx
function LogicalAndOperator() {
  const [showWarning, setShowWarning] = React.useState(false);
  const [notifications, setNotifications] = React.useState([]);
  const [userRole, setUserRole] = React.useState('user');

  const addNotification = () => {
    const newNotification = {
      id: Date.now(),
      message: `Notification ${notifications.length + 1}`,
      timestamp: new Date().toLocaleTimeString()
    };
    setNotifications([...notifications, newNotification]);
  };

  return (
    <div>
      <h2>Conditional Display Component</h2>

      <div>
        <button onClick={() => setShowWarning(!showWarning)}>
          Toggle Warning Display
        </button>
        <button onClick={addNotification}>Add Notification</button>
        <button onClick={() => setUserRole(userRole === 'admin' ? 'user' : 'admin')}>
          Toggle User Role: {userRole}
        </button>
      </div>

      {/* Using && operator */}
      {showWarning && (
        <div style={{ backgroundColor: '#fff3cd', padding: '10px', margin: '10px 0' }}>
          ⚠️ This is a warning message
        </div>
      )}

      {/* Conditionally display notifications */}
      {notifications.length > 0 && (
        <div>
          <h3>Notification List ({notifications.length})</h3>
          {notifications.map(notification => (
            <div key={notification.id} style={{ padding: '5px', border: '1px solid #ddd' }}>
              [{notification.timestamp}] {notification.message}
            </div>
          ))}
        </div>
      )}

      {/* Admin-only feature */}
      {userRole === 'admin' && (
        <div style={{ backgroundColor: '#d4edda', padding: '10px', margin: '10px 0' }}>
          <h3>Admin Panel</h3>
          <button>User Management</button>
          <button>System Settings</button>
        </div>
      )}
    </div>
  );
}

Switch Statement Simulation

jsx
function SwitchLikeRendering() {
  const [status, setStatus] = React.useState('loading');
  const [userType, setUserType] = React.useState('guest');

  // Use object mapping to simulate switch
  const statusComponents = {
    loading: <div>⏳ Loading...</div>,
    success: <div style={{ color: 'green' }}>✅ Operation successful</div>,
    error: <div style={{ color: 'red' }}>❌ Operation failed</div>,
    idle: <div>Waiting for operation</div>
  };

  // Use function to simulate switch
  const renderUserInterface = () => {
    switch (userType) {
      case 'admin':
        return (
          <div style={{ backgroundColor: '#e7f3ff' }}>
            <h3>Admin Interface</h3>
            <button>User Management</button>
            <button>System Settings</button>
            <button>Data Statistics</button>
          </div>
        );
      case 'vip':
        return (
          <div style={{ backgroundColor: '#fff7e6' }}>
            <h3>VIP User Interface</h3>
            <button>Exclusive Service</button>
            <button>Advanced Features</button>
          </div>
        );
      case 'user':
        return (
          <div style={{ backgroundColor: '#f6fff7' }}>
            <h3>Regular User Interface</h3>
            <button>Basic Features</button>
          </div>
        );
      default:
        return (
          <div>
            <h3>Guest Interface</h3>
            <button>Register</button>
            <button>Login</button>
          </div>
        );
    }
  };

  return (
    <div>
      <h2>Complex Conditional Rendering</h2>

      <div>
        <label>Status: </label>
        <select value={status} onChange={(e) => setStatus(e.target.value)}>
          <option value="loading">Loading</option>
          <option value="success">Success</option>
          <option value="error">Error</option>
          <option value="idle">Idle</option>
        </select>
      </div>

      <div>
        <label>User Type: </label>
        <select value={userType} onChange={(e) => setUserType(e.target.value)}>
          <option value="guest">Guest</option>
          <option value="user">User</option>
          <option value="vip">VIP</option>
          <option value="admin">Admin</option>
        </select>
      </div>

      <div style={{ margin: '20px 0' }}>
        <h3>Current Status:</h3>
        {statusComponents[status]}
      </div>

      <div>
        {renderUserInterface()}
      </div>
    </div>
  );
}

📋 List Rendering

Basic List Rendering

jsx
function BasicListRendering() {
  const [items, setItems] = React.useState([
    { id: 1, name: 'Apple', price: 5.99, category: 'Fruits' },
    { id: 2, name: 'Banana', price: 3.99, category: 'Fruits' },
    { id: 3, name: 'Milk', price: 12.99, category: 'Dairy' },
    { id: 4, name: 'Bread', price: 8.99, category: 'Staples' }
  ]);

  const [filter, setFilter] = React.useState('all');

  const filteredItems = items.filter(item =>
    filter === 'all' || item.category === filter
  );

  const categories = ['all', ...new Set(items.map(item => item.category))];

  return (
    <div>
      <h2>Product List</h2>

      {/* Filters */}
      <div style={{ marginBottom: '20px' }}>
        {categories.map(category => (
          <button
            key={category}
            onClick={() => setFilter(category)}
            style={{
              margin: '0 5px',
              backgroundColor: filter === category ? '#007bff' : '#f8f9fa',
              color: filter === category ? 'white' : 'black'
            }}
          >
            {category === 'all' ? 'All' : category}
          </button>
        ))}
      </div>

      {/* Product list */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '10px' }}>
        {filteredItems.map(item => (
          <div
            key={item.id}
            style={{
              border: '1px solid #ddd',
              borderRadius: '5px',
              padding: '15px',
              backgroundColor: '#f8f9fa'
            }}
          >
            <h3>{item.name}</h3>
            <p>Category: {item.category}</p>
            <p style={{ color: '#007bff', fontSize: '18px', fontWeight: 'bold' }}>
              ${item.price}
            </p>
          </div>
        ))}
      </div>

      {filteredItems.length === 0 && (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          No products found matching criteria
        </div>
      )}
    </div>
  );
}

Dynamic List Operations

jsx
function DynamicListOperations() {
  const [todos, setTodos] = React.useState([
    { id: 1, text: 'Learn React', completed: false, priority: 'high' },
    { id: 2, text: 'Complete Project', completed: true, priority: 'medium' },
    { id: 3, text: 'Write Documentation', completed: false, priority: 'low' }
  ]);

  const [newTodo, setNewTodo] = React.useState('');
  const [sortBy, setSortBy] = React.useState('id');

  const addTodo = () => {
    if (newTodo.trim()) {
      const todo = {
        id: Date.now(),
        text: newTodo,
        completed: false,
        priority: 'medium'
      };
      setTodos([...todos, todo]);
      setNewTodo('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  const changePriority = (id, priority) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, priority } : todo
    ));
  };

  // Sorting logic
  const sortedTodos = [...todos].sort((a, b) => {
    switch (sortBy) {
      case 'text':
        return a.text.localeCompare(b.text);
      case 'completed':
        return a.completed - b.completed;
      case 'priority':
        const priorityOrder = { high: 3, medium: 2, low: 1 };
        return priorityOrder[b.priority] - priorityOrder[a.priority];
      default:
        return a.id - b.id;
    }
  });

  const getPriorityColor = (priority) => {
    switch (priority) {
      case 'high': return '#dc3545';
      case 'medium': return '#ffc107';
      case 'low': return '#28a745';
      default: return '#6c757d';
    }
  };

  return (
    <div>
      <h2>Todo Management</h2>

      {/* Add new task */}
      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="Enter new task..."
          style={{ padding: '8px', marginRight: '10px', width: '200px' }}
        />
        <button onClick={addTodo}>Add</button>
      </div>

      {/* Sort options */}
      <div style={{ marginBottom: '20px' }}>
        <label>Sort by: </label>
        <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
          <option value="id">Default</option>
          <option value="text">By Name</option>
          <option value="completed">By Status</option>
          <option value="priority">By Priority</option>
        </select>
      </div>

      {/* Task list */}
      <div>
        {sortedTodos.map(todo => (
          <div
            key={todo.id}
            style={{
              display: 'flex',
              alignItems: 'center',
              padding: '10px',
              margin: '5px 0',
              border: '1px solid #ddd',
              borderRadius: '5px',
              backgroundColor: todo.completed ? '#f8f9fa' : 'white'
            }}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
              style={{ marginRight: '10px' }}
            />

            <span
              style={{
                flex: 1,
                textDecoration: todo.completed ? 'line-through' : 'none',
                color: todo.completed ? '#6c757d' : 'black'
              }}
            >
              {todo.text}
            </span>

            <select
              value={todo.priority}
              onChange={(e) => changePriority(todo.id, e.target.value)}
              style={{
                marginRight: '10px',
                color: getPriorityColor(todo.priority)
              }}
            >
              <option value="low">Low</option>
              <option value="medium">Medium</option>
              <option value="high">High</option>
            </select>

            <button
              onClick={() => deleteTodo(todo.id)}
              style={{ color: 'red', background: 'none', border: 'none' }}
            >
              Delete
            </button>
          </div>
        ))}
      </div>

      {todos.length === 0 && (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          No tasks yet, add one to get started!
        </div>
      )}

      {/* Statistics */}
      <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f8f9fa' }}>
        <p>Total tasks: {todos.length}</p>
        <p>Completed: {todos.filter(t => t.completed).length}</p>
        <p>Pending: {todos.filter(t => !t.completed).length}</p>
      </div>
    </div>
  );
}

Importance of Key Attribute

jsx
function KeyImportance() {
  const [items, setItems] = React.useState([
    { id: 'a', name: 'Item A', count: 0 },
    { id: 'b', name: 'Item B', count: 0 },
    { id: 'c', name: 'Item C', count: 0 }
  ]);

  const addItemAtStart = () => {
    const newItem = {
      id: Math.random().toString(36).substr(2, 9),
      name: `Item ${String.fromCharCode(65 + items.length)}`,
      count: 0
    };
    setItems([newItem, ...items]);
  };

  const updateCount = (id, delta) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, count: item.count + delta } : item
    ));
  };

  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };

  return (
    <div>
      <h2>Key Attribute Demo</h2>
      <button onClick={addItemAtStart} style={{ marginBottom: '20px' }}>
        Add Item at Beginning
      </button>

      <div style={{ display: 'flex', gap: '20px' }}>
        {/* Correct use of key */}
        <div>
          <h3>✅ Correct: Use unique ID as key</h3>
          {items.map(item => (
            <div key={item.id} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px 0' }}>
              <div>{item.name}</div>
              <div>
                <input
                  type="number"
                  value={item.count}
                  onChange={(e) => updateCount(item.id, parseInt(e.target.value) - item.count)}
                  style={{ width: '60px', marginRight: '10px' }}
                />
                <button onClick={() => updateCount(item.id, 1)}>+</button>
                <button onClick={() => updateCount(item.id, -1)}>-</button>
                <button onClick={() => removeItem(item.id)} style={{ marginLeft: '10px', color: 'red' }}>
                  Delete
                </button>
              </div>
            </div>
          ))}
        </div>

        {/* Wrong use of key */}
        <div>
          <h3>❌ Wrong: Use index as key</h3>
          {items.map((item, index) => (
            <div key={index} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px 0' }}>
              <div>{item.name}</div>
              <div>
                <input
                  type="number"
                  value={item.count}
                  onChange={(e) => updateCount(item.id, parseInt(e.target.value) - item.count)}
                  style={{ width: '60px', marginRight: '10px' }}
                />
                <button onClick={() => updateCount(item.id, 1)}>+</button>
                <button onClick={() => updateCount(item.id, -1)}>-</button>
                <button onClick={() => removeItem(item.id)} style={{ marginLeft: '10px', color: 'red' }}>
                  Delete
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>

      <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#fff3cd' }}>
        <strong>Note:</strong> Enter some numbers on the left side, then click "Add Item at Beginning",
        and observe the input value changes in both columns. Using index as key can cause input state confusion.
      </div>
    </div>
  );
}

🎯 Advanced Rendering Techniques

Rendering Optimization

jsx
const MemoizedItem = React.memo(function Item({ item, onUpdate, onDelete }) {
  console.log('Rendering:', item.name);

  return (
    <div style={{ padding: '10px', border: '1px solid #ddd', margin: '5px 0' }}>
      <span>{item.name} - {item.value}</span>
      <button onClick={() => onUpdate(item.id)}>Update</button>
      <button onClick={() => onDelete(item.id)}>Delete</button>
    </div>
  );
});

function OptimizedRendering() {
  const [items, setItems] = React.useState([
    { id: 1, name: 'Item 1', value: 0 },
    { id: 2, name: 'Item 2', value: 0 },
    { id: 3, name: 'Item 3', value: 0 }
  ]);

  const [counter, setCounter] = React.useState(0);

  const updateItem = React.useCallback((id) => {
    setItems(items => items.map(item =>
      item.id === id ? { ...item, value: item.value + 1 } : item
    ));
  }, []);

  const deleteItem = React.useCallback((id) => {
    setItems(items => items.filter(item => item.id !== id));
  }, []);

  return (
    <div>
      <h2>Rendering Optimization Demo</h2>
      <p>Counter: {counter}</p>
      <button onClick={() => setCounter(counter + 1)}>
        Increment Counter (Does not affect list item rendering)
      </button>

      <div>
        {items.map(item => (
          <MemoizedItem
            key={item.id}
            item={item}
            onUpdate={updateItem}
            onDelete={deleteItem}
          />
        ))}
      </div>
    </div>
  );
}

📝 Chapter Summary

Through this chapter, you should have mastered:

Conditional Rendering Techniques

  • ✅ if statement, ternary operator, logical AND operator
  • ✅ Complex condition handling strategies
  • ✅ State-driven UI changes
  • ✅ Error boundaries and loading states

List Rendering Techniques

  • ✅ map() method for array rendering
  • ✅ Correct use of Key attribute
  • ✅ Dynamic list CRUD operations
  • ✅ List sorting and filtering

Best Practices

  1. Importance of Key: Use unique and stable identifiers
  2. Performance optimization: Use React.memo to avoid unnecessary rendering
  3. Condition complexity: Reasonably split complex conditional logic
  4. State management: Keep state structure simple and clear
  5. User experience: Provide loading states and empty state hints

Continue Learning: Next Chapter - React Lifecycle

Content is for learning and research only.