Skip to content

montecassino/high-performance-list

Repository files navigation

High-Performance List Engine (React + Redux Toolkit)

This project demonstrates how to handle large datasets (5,000+ items) in React without compromising user experience. It compares a "Naive Implementation" (Laggy) against an "Architected Solution" (Optimized) to visualize the impact of Rendering Performance, State Normalization, and Virtualization.


Quick Start

  1. Install Dependencies:
    npm install
  2. Run the App:
    npm run dev
  3. Open in Browser: Navigate to http://localhost:5173

Optimized vs Unoptimized

This app contains two distinct engines. You can toggle between them using the buttons in the UI.

The "Laggy" Engine

  • State: Stores data as a giant Array (Task[]). Updates require .map() which replaces the entire array reference.
  • Rendering: Renders 5,000 DOM nodes at once.
  • Optimization: None. No React.memo, no specialized selectors.
  • Result:
    • FPS: Drops to 0-5 FPS while scrolling.
    • Interaction: Clicking a checkbox freezes the browser for 0.5s - 2.0s.
    • Memory: High memory usage (holding thousands of DOM nodes).

The "Optimized" Engine

  • State: Normalized using createEntityAdapter (Hash Map style).
  • Rendering: Uses Virtualization (Windowing) to render only the 20 visible rows.
  • Optimization:
    • O(1) Updates: Updating a task does not touch its neighbors.
    • Memoization: React.memo prevents re-renders if props haven't changed.
    • Reselect: Memoized selectors prevent expensive derived data calculations.
  • Result:
    • FPS: Solid 60 FPS.
    • Interaction: Instantaneous (<2ms) updates.
    • Memory: Low memory usage.

Core Concepts

1. State Normalization (createEntityAdapter)

Problem: In a standard array [{ id: 1 }, { id: 2 }], finding or updating an item is O(n). Worse, in Redux, updating one item in an array changes the reference of the array, causing every component consuming that array to re-render.

Solution: We treat the Redux state like a database.

// Optimized State Shape
{
  ids: ['task-1', 'task-2', ...],
  entities: {
    'task-1': { id: 'task-1', title: 'Pikachu', ... },
    'task-2': { id: 'task-2', title: 'Charmander', ... }
  }
}

Why it wins:

  • Lookups are O(1): state.entities['task-500'] is instant.
  • Stable References: Updating "Task 1" creates a new object for Task 1, but the object for "Task 2" remains the exact same memory reference.

2. List Virtualization (Windowing)

Problem: The browser cannot handle 5,000 <div> elements. It destroys the layout engine and consumes massive RAM.

Solution: We use react-window List.

  • We only render what fits on the screen (20 items).
  • As you scroll, the library recycles the DOM nodes, just swapping the text content.
  • Math: 5,000 items vs 20 items = 99.6% reduction in DOM weight.

3. Referential Integrity & React.memo

In the Laggy version, clicking a checkbox updates the parent Array. This forces React to re-render the Entire List.

In the Optimized version:

  1. The Parent (TaskList) only listens to ids (which don't change on updates).
  2. The Child (TaskRow) is wrapped in React.memo.
  3. The Child selects its own data: const task = useSelector(state => selectById(state, id)).
  4. When Task 1 updates, Task 2's selector returns the same object. React.memo sees no changes and skips the render.

How to Benchmark

The app includes a built-in HUD (Heads Up Display), but here is how to verify deeper.

1. The "Visual Flash" Test

  • Action: Click a checkbox.
  • Observation:
    • Optimized: Only the single row clicked changes background color.
    • Laggy: The entire list flashes a new color.
  • Note: Check the console logs to see the render counts.

2. Chrome Performance Profiler

  1. Open DevTools (F12) -> Profiler tab.
  2. Click Record.
  3. Click a checkbox in the app.
  4. Stop Recording.
  5. Analyze:
    • Laggy: You will see a "Commit" taking 500ms+. You will see LaggyRow rendered 5,000 times.
    • Optimized: You will see a "Commit" taking < 5ms. You will see TaskRow rendered 1 time.

About

a demonstration between a naive and optimized solution for rendering a react app that has a lot of list items

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors