search
React star Featured

How to Fix React Each child in a list should have a unique key prop: Error

Learn how to resolve the 'Each child in a list should have a unique key prop' error in React. Complete guide with solutions for list rendering and performance optimization.

person By Gautam Sharma
calendar_today January 2, 2026
schedule 16 min read
React JavaScript Error Handling JSX Performance Lists

The ‘Each child in a list should have a unique key prop’ error is a common issue developers encounter when rendering lists in React applications. This error occurs when React cannot efficiently track changes in a list of elements, leading to potential performance issues and unexpected behavior.

This comprehensive guide provides essential solutions to resolve the key prop error with practical examples and performance optimization techniques.


Understanding the Key Prop Error

React uses keys to identify which items have changed, been added, or been removed in a list. Keys help React optimize rendering by determining which elements need to be re-rendered. When keys are missing or not unique, React warns about potential performance issues.

Common Error Scenarios:

  • Warning: Each child in a list should have a unique "key" prop
  • Warning: Each child in an array or iterator should have a unique "key" prop
  • Warning: Encountered two children with the same key

Common Causes and Solutions

1. Missing Keys in List Rendering

The most common cause is rendering a list without providing keys.

❌ Problem Scenario:

// This will cause the key prop error
function BadUserList({ users }) {
  return (
    <div>
      {/* ❌ Missing key prop */}
      {users.map(user => (
        <div>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );
}

✅ Solution: Add Unique Keys

// Correct approach - add unique keys
function GoodUserList({ users }) {
  return (
    <div>
      {/* ✅ Using unique user ID as key */}
      {users.map(user => (
        <div key={user.id}>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );
}

2. Using Index as Key (Problematic)

Using array index as a key can cause issues when the list order changes.

❌ Problem Scenario:

// This can cause problems with list reordering
function BadList({ items }) {
  return (
    <ul>
      {/* ❌ Using index as key - problematic for dynamic lists */}
      {items.map((item, index) => (
        <li key={index}>{item.name}</li>
      ))}
    </ul>
  );
}

✅ Solution: Use Stable Unique IDs

// Better approach - use stable unique IDs
function GoodList({ items }) {
  return (
    <ul>
      {/* ✅ Using stable unique ID as key */}
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Solution 1: Proper Key Selection Strategies

Choose the right key based on your data structure and use case.

// Different key selection strategies
function KeySelectionExamples() {
  // Strategy 1: Use database IDs (best practice)
  const users = [
    { id: 1, name: 'John', email: 'john@example.com' },
    { id: 2, name: 'Jane', email: 'jane@example.com' }
  ];

  const UserList = () => (
    <div>
      {users.map(user => (
        <div key={user.id}> {/* ✅ Database ID is unique and stable */}
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );

  // Strategy 2: Use generated unique IDs
  const [items, setItems] = useState([
    { id: 'uuid-1', name: 'Item 1' },
    { id: 'uuid-2', name: 'Item 2' }
  ]);

  const ItemList = () => (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );

  // Strategy 3: Use composite keys when needed
  const transactions = [
    { userId: 1, transactionId: 'tx-1', amount: 100 },
    { userId: 1, transactionId: 'tx-2', amount: 200 }
  ];

  const TransactionList = () => (
    <div>
      {transactions.map(tx => (
        <div key={`${tx.userId}-${tx.transactionId}`}> {/* ✅ Composite key */}
          <p>Amount: ${tx.amount}</p>
        </div>
      ))}
    </div>
  );

  return (
    <div>
      <UserList />
      <ItemList />
      <TransactionList />
    </div>
  );
}

Solution 2: Dynamic List Management with Proper Keys

Handle dynamic lists with proper key management.

// Dynamic list with proper key management
function DynamicListManager() {
  const [items, setItems] = useState([
    { id: 1, name: 'Item 1', createdAt: Date.now() },
    { id: 2, name: 'Item 2', createdAt: Date.now() + 1000 }
  ]);

  const addItem = () => {
    const newItem = {
      id: Date.now(), // Generate unique ID
      name: `Item ${items.length + 1}`,
      createdAt: Date.now()
    };
    setItems(prev => [...prev, newItem]);
  };

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

  const moveItem = (id, direction) => {
    setItems(prev => {
      const index = prev.findIndex(item => item.id === id);
      if (index === -1) return prev;
      
      const newItems = [...prev];
      if (direction === 'up' && index > 0) {
        [newItems[index], newItems[index - 1]] = [newItems[index - 1], newItems[index]];
      } else if (direction === 'down' && index < newItems.length - 1) {
        [newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
      }
      return newItems;
    });
  };

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <ul>
        {items.map((item, index) => (
          <li key={item.id} style={{ margin: '10px 0' }}>
            <span>{item.name}</span>
            <button onClick={() => removeItem(item.id)}>Remove</button>
            <button onClick={() => moveItem(item.id, 'up')}>Move Up</button>
            <button onClick={() => moveItem(item.id, 'down')}>Move Down</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Solution 3: Nested List Key Management

Handle nested lists with proper key hierarchy.

// Nested list with proper key management
function NestedListExample() {
  const [categories, setCategories] = useState([
    {
      id: 1,
      name: 'Electronics',
      items: [
        { id: 'e1', name: 'Laptop', price: 1000 },
        { id: 'e2', name: 'Phone', price: 500 }
      ]
    },
    {
      id: 2,
      name: 'Books',
      items: [
        { id: 'b1', name: 'JavaScript Guide', price: 30 },
        { id: 'b2', name: 'React Handbook', price: 40 }
      ]
    }
  ]);

  return (
    <div>
      {categories.map(category => (
        <div key={category.id} style={{ margin: '20px 0' }}>
          <h3>{category.name}</h3>
          <ul>
            {category.items.map(item => (
              <li key={item.id} style={{ margin: '5px 0' }}>
                <span>{item.name} - ${item.price}</span>
              </li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

// More complex nested structure
function ComplexNestedList() {
  const [data, setData] = useState([
    {
      id: 'section-1',
      title: 'Section 1',
      groups: [
        {
          id: 'group-1a',
          name: 'Group A',
          items: [
            { id: 'item-1a1', content: 'Item 1A1' },
            { id: 'item-1a2', content: 'Item 1A2' }
          ]
        },
        {
          id: 'group-1b',
          name: 'Group B',
          items: [
            { id: 'item-1b1', content: 'Item 1B1' }
          ]
        }
      ]
    }
  ]);

  return (
    <div>
      {data.map(section => (
        <div key={section.id}>
          <h2>{section.title}</h2>
          {section.groups.map(group => (
            <div key={group.id} style={{ marginLeft: '20px' }}>
              <h3>{group.name}</h3>
              <ul>
                {group.items.map(item => (
                  <li key={item.id}>{item.content}</li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

Solution 4: Key Management with Filtering and Sorting

Handle dynamic lists that are filtered or sorted.

// List with filtering and sorting
function FilterableList() {
  const [items, setItems] = useState([
    { id: 1, name: 'Apple', category: 'fruit', price: 1.5 },
    { id: 2, name: 'Banana', category: 'fruit', price: 0.8 },
    { id: 3, name: 'Carrot', category: 'vegetable', price: 0.5 },
    { id: 4, name: 'Broccoli', category: 'vegetable', price: 2.0 }
  ]);

  const [filter, setFilter] = useState('');
  const [sortBy, setSortBy] = useState('name');

  // Filter and sort items
  const filteredAndSortedItems = useMemo(() => {
    let result = [...items];
    
    // Apply filter
    if (filter) {
      result = result.filter(item => 
        item.name.toLowerCase().includes(filter.toLowerCase()) ||
        item.category.toLowerCase().includes(filter.toLowerCase())
      );
    }
    
    // Apply sort
    result.sort((a, b) => {
      if (sortBy === 'name') {
        return a.name.localeCompare(b.name);
      } else if (sortBy === 'price') {
        return a.price - b.price;
      } else if (sortBy === 'category') {
        return a.category.localeCompare(b.category);
      }
      return 0;
    });
    
    return result;
  }, [items, filter, sortBy]);

  return (
    <div>
      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          placeholder="Filter items..."
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
        />
        <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
          <option value="name">Sort by Name</option>
          <option value="price">Sort by Price</option>
          <option value="category">Sort by Category</option>
        </select>
      </div>
      
      <ul>
        {filteredAndSortedItems.map(item => (
          <li key={item.id}> {/* ✅ Stable key regardless of filter/sort */}
            <strong>{item.name}</strong> - {item.category} - ${item.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

Solution 5: Custom Components with Keys

Use keys properly with custom components.

// Custom component with proper key usage
function CustomListItem({ item, onRemove }) {
  return (
    <div style={{ 
      border: '1px solid #ccc', 
      margin: '5px', 
      padding: '10px',
      borderRadius: '4px'
    }}>
      <h4>{item.name}</h4>
      <p>{item.description}</p>
      <button onClick={() => onRemove(item.id)}>Remove</button>
    </div>
  );
}

function CustomListWithKeys() {
  const [items, setItems] = useState([
    { id: 1, name: 'Task 1', description: 'Complete the project' },
    { id: 2, name: 'Task 2', description: 'Review code' },
    { id: 3, name: 'Task 3', description: 'Deploy to production' }
  ]);

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

  return (
    <div>
      {items.map(item => (
        <CustomListItem 
          key={item.id} // ✅ Key on the custom component
          item={item}
          onRemove={removeItem}
        />
      ))}
    </div>
  );
}

// Component with multiple child elements
function MultiElementItem({ item }) {
  return (
    <div key={item.id} className="multi-element-item"> {/* ❌ Don't put key here */}
      <h3>{item.title}</h3>
      <p>{item.content}</p>
      <button>Click me</button>
    </div>
  );
}

// ✅ Correct approach - key on the outermost element that's mapped
function CorrectMultiElementList() {
  const items = [
    { id: 1, title: 'Title 1', content: 'Content 1' },
    { id: 2, title: 'Title 2', content: 'Content 2' }
  ];

  return (
    <div>
      {items.map(item => (
        <div key={item.id} className="multi-element-item"> {/* ✅ Key on outermost mapped element */}
          <h3>{item.title}</h3>
          <p>{item.content}</p>
          <button>Click me</button>
        </div>
      ))}
    </div>
  );
}

Solution 6: React.memo with Keys

Combine React.memo with proper key usage for performance optimization.

// Memoized component with proper key usage
const MemoizedListItem = memo(({ item, onUpdate }) => {
  console.log(`Rendering item: ${item.id}`); // This will only log when item actually changes
  
  return (
    <div style={{ margin: '10px', padding: '10px', border: '1px solid #ddd' }}>
      <h4>{item.name}</h4>
      <p>{item.description}</p>
      <button onClick={() => onUpdate(item.id)}>
        Update {item.name}
      </button>
    </div>
  );
});

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

  const updateItem = (id) => {
    setItems(prev => 
      prev.map(item => 
        item.id === id 
          ? { ...item, value: item.value + 1, description: `Updated ${item.name}` }
          : item
      )
    );
  };

  return (
    <div>
      {items.map(item => (
        <MemoizedListItem 
          key={item.id} // ✅ Proper key usage with memoized component
          item={item}
          onUpdate={updateItem}
        />
      ))}
    </div>
  );
}

Solution 7: Key Generation Utilities

Create utilities for generating and managing keys.

// Key generation utilities
const KeyUtils = {
  // Generate unique ID
  generateId: () => `id_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
  
  // Generate key from object properties
  generateKey: (obj, properties = ['id']) => {
    if (properties.length === 1) {
      return obj[properties[0]];
    }
    return properties.map(prop => obj[prop]).join('_');
  },
  
  // Validate key uniqueness
  validateKeys: (items, keyProp = 'id') => {
    const keys = items.map(item => item[keyProp]);
    const uniqueKeys = new Set(keys);
    return keys.length === uniqueKeys.size;
  }
};

// Component using key utilities
function UtilityBasedList() {
  const [items, setItems] = useState([
    { id: 'item-1', name: 'First Item', timestamp: Date.now() },
    { id: 'item-2', name: 'Second Item', timestamp: Date.now() + 1000 }
  ]);

  const addItem = () => {
    const newItem = {
      id: KeyUtils.generateId(),
      name: `Item ${items.length + 1}`,
      timestamp: Date.now()
    };
    setItems(prev => [...prev, newItem]);
  };

  // Validate keys in development
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      const isValid = KeyUtils.validateKeys(items);
      if (!isValid) {
        console.warn('Duplicate keys detected in list!');
      }
    }
  }, [items]);

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <span>{item.name}</span>
            <small> (ID: {item.id})</small>
          </li>
        ))}
      </ul>
    </div>
  );
}

Solution 8: TypeScript for Key Safety

Use TypeScript to ensure key safety and proper typing.

// TypeScript interfaces for key-safe components
interface ListItem {
  id: string | number;
  name: string;
  description?: string;
}

interface ListProps {
  items: ListItem[];
  onRemove?: (id: string | number) => void;
  onUpdate?: (id: string | number, updates: Partial<ListItem>) => void;
}

// Type-safe list component
function TypeSafeList({ items, onRemove, onUpdate }: ListProps) {
  return (
    <div>
      {items.map(item => (
        <div key={item.id} className="list-item">
          <h3>{item.name}</h3>
          {item.description && <p>{item.description}</p>}
          {onRemove && (
            <button onClick={() => onRemove(item.id)}>Remove</button>
          )}
          {onUpdate && (
            <button onClick={() => onUpdate(item.id, { name: `${item.name} (updated)` })}>
              Update
            </button>
          )}
        </div>
      ))}
    </div>
  );
}

// More complex type-safe example
interface Category {
  id: string;
  name: string;
  items: ListItem[];
}

interface CategorizedListProps {
  categories: Category[];
}

function CategorizedList({ categories }: CategorizedListProps) {
  return (
    <div>
      {categories.map(category => (
        <div key={category.id} className="category">
          <h2>{category.name}</h2>
          <ul>
            {category.items.map(item => (
              <li key={item.id}>{item.name}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

Solution 9: Error Boundaries for Key Issues

Use error boundaries to catch and handle key-related errors gracefully.

// Error boundary for key-related issues
class KeyErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    // Check if this is a key-related error
    if (error.message && error.message.includes('key')) {
      return { hasError: true, error };
    }
    return { hasError: false };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Key error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Key Error Detected</h2>
          <p>There may be issues with list keys in this component.</p>
          <p>Error: {this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try Again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Component that might have key issues
function PotentiallyProblematicList({ items }) {
  // This component might have key issues
  return (
    <div>
      {items.map((item, index) => (
        // ❌ This could cause issues if items change order
        <div key={index}>{item.name}</div>
      ))}
    </div>
  );
}

// Safe wrapper with error boundary
function SafeListWrapper({ items }) {
  return (
    <KeyErrorBoundary>
      <PotentiallyProblematicList items={items} />
    </KeyErrorBoundary>
  );
}

Solution 10: Performance Monitoring for Key Issues

Implement performance monitoring to detect key-related performance issues.

// Performance monitoring for key issues
import { useEffect, useRef } from 'react';

function useKeyPerformanceMonitor(componentName = 'Component') {
  const renderCountRef = useRef(0);
  const startTimeRef = useRef(0);
  
  useEffect(() => {
    const startTime = performance.now();
    startTimeRef.current = startTime;
    renderCountRef.current += 1;
    
    // Log if render count is excessive (might indicate key issues)
    if (renderCountRef.current > 10) {
      console.warn(`${componentName} has rendered ${renderCountRef.current} times - check key props`);
    }
    
    // Log render time
    const renderTime = performance.now() - startTime;
    if (renderTime > 16) { // More than one frame at 60fps
      console.warn(`${componentName} render took ${renderTime.toFixed(2)}ms - possible key inefficiency`);
    }
  });
  
  return {
    renderCount: renderCountRef.current,
    renderTime: performance.now() - startTimeRef.current
  };
}

// Component with performance monitoring
function MonitoredList({ items }) {
  const performance = useKeyPerformanceMonitor('MonitoredList');
  
  return (
    <div>
      <div className="performance-info">
        Renders: {performance.renderCount}, 
        Time: {performance.renderTime.toFixed(2)}ms
      </div>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

Performance Considerations

Efficient Key Usage:

// Optimized list rendering with proper keys
function OptimizedListRendering() {
  const [items, setItems] = useState([]);
  const [filter, setFilter] = useState('');

  // Use useMemo for expensive list operations
  const filteredItems = useMemo(() => {
    if (!filter) return items;
    return items.filter(item => 
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  // Stable function for adding items
  const addItem = useCallback((name) => {
    const newItem = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, // Stable unique ID
      name,
      createdAt: Date.now()
    };
    setItems(prev => [...prev, newItem]);
  }, []);

  return (
    <div>
      <input 
        type="text" 
        placeholder="Filter items..." 
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      />
      <button onClick={() => addItem(`Item ${items.length + 1}`)}>
        Add Item
      </button>
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}> {/* ✅ Stable, unique key */}
            {item.name}
          </li>
        ))}
      </ul>
    </div>
  );
}

Security Considerations

Safe Key Generation:

// Secure key generation
function SecureKeyGeneration() {
  const [items, setItems] = useState([]);

  // Generate secure, unpredictable keys
  const generateSecureKey = () => {
    // Use crypto API for secure random generation
    if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
      const array = new Uint8Array(16);
      crypto.getRandomValues(array);
      return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
    }
    // Fallback to less secure method
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  };

  const addItem = () => {
    const newItem = {
      id: generateSecureKey(), // Secure, unpredictable key
      name: `Secure Item ${items.length + 1}`,
      timestamp: Date.now()
    };
    setItems(prev => [...prev, newItem]);
  };

  return (
    <div>
      <button onClick={addItem}>Add Secure Item</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <strong>{item.name}</strong>
            <small> (Key: {item.id.substring(0, 8)}...)</small>
          </li>
        ))}
      </ul>
    </div>
  );
}

Common Mistakes to Avoid

1. Using Array Index as Key:

// ❌ Don't do this for dynamic lists
function BadIndexKey({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item.name}</li> // Problematic for dynamic lists
      ))}
    </ul>
  );
}

2. Duplicate Keys:

// ❌ Don't do this
function BadDuplicateKeys({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key="static-key">{item.name}</li> // All items have same key
      ))}
    </ul>
  );
}

3. Keys from Non-Unique Properties:

// ❌ Don't do this
function BadNonUniqueKey({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.name}>{user.email}</li> // Name might not be unique
      ))}
    </ul>
  );
}

Alternative Solutions

Using React DevTools:

// Component optimized for React DevTools
function DevToolsOptimizedList({ items }) {
  return (
    <div data-testid="key-optimized-list">
      <ul>
        {items.map(item => (
          <li 
            key={item.id} 
            data-item-id={item.id}
          >
            {item.name}
          </li>
        ))}
      </ul>
    </div>
  );
}

Feature Detection:

// Check for key-related issues
function KeyValidationComponent({ items }) {
  useEffect(() => {
    // Validate key uniqueness in development
    if (process.env.NODE_ENV === 'development') {
      const ids = items.map(item => item.id);
      const uniqueIds = new Set(ids);
      if (ids.length !== uniqueIds.size) {
        console.error('Duplicate IDs detected in list:', items);
      }
    }
  }, [items]);

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Troubleshooting Checklist

When encountering the key prop error:

  1. Check List Rendering: Ensure every mapped element has a key prop
  2. Verify Key Uniqueness: Confirm keys are unique within the list
  3. Avoid Array Index: Don’t use array index for dynamic lists
  4. Check Nested Lists: Ensure keys are unique in nested structures
  5. Validate Data: Verify your data doesn’t have duplicate IDs
  6. Use React DevTools: Inspect component tree for key issues
  7. Test Dynamic Operations: Verify adding/removing items maintains key integrity

Conclusion

The ‘Each child in a list should have a unique key prop’ error occurs when React cannot efficiently track list item changes. By understanding React’s reconciliation process and implementing proper key strategies, you can ensure your React applications render lists efficiently and maintain optimal performance.

The key to resolving this error is always providing stable, unique, and predictable keys for list items, especially when lists are dynamic or can change order. Whether you’re working with simple lists or complex nested structures, the solutions provided in this guide will help you handle key props appropriately in your React applications.

Remember to always use stable unique identifiers as keys, avoid array indices for dynamic lists, and implement proper validation to catch key-related issues early in development.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

React

[SOLVED] Too many re-renders. React limits the number of renders Error Tutorial

Learn how to fix the 'Too many re-renders. React limits the number of renders' error in React. Complete guide with solutions for infinite render loops and performance optimization.

January 2, 2026
React

Resolve React useEffect Dependency Warning: Complete Guide

Learn how to resolve React useEffect dependency warnings and missing dependencies. Complete guide with solutions for useEffect hooks and best practices.

January 2, 2026
React

How to Fix Objects are not valid as a React child Error: Complete Guide

Learn how to solve the common 'Objects are not valid as a React child' error. Complete guide with solutions for React rendering issues and best practices.

January 2, 2026