Skip to content
CS/choppari-srinivas
← Back to blog

Optimizing a Real-Time Windmill Monitoring Dashboard: Handling High-Frequency Data Streams in React

7 min read

Introduction

One of the most interesting challenges I worked on involved building a real-time dashboard for monitoring wind turbine power generation.

The system received live telemetry every second from multiple windmills. The challenge wasn't receiving the data—it was displaying it efficiently without causing the React application to become sluggish or unresponsive.

This article explains the architecture, the performance bottleneck we encountered, and the optimization that significantly improved the application's responsiveness.

System Architecture

The data pipeline looked like this:

Windmill Hardware
        │
        ▼
Java Adapter Service
        │
        ▼
Socket.IO Server
        │
        ▼
React Dashboard
        │
        ▼
Power Generation Grid

Components

  • Windmill hardware continuously generated telemetry data.
  • A Java adapter received raw hardware data and transformed it into a standard format.
  • The Java service published processed data through Socket.IO.
  • The React application subscribed to Socket.IO events and displayed the latest power-generation data in a grid.

The Challenge

Each wind turbine sent updated information every second, and the Socket.IO server immediately pushed every update to the React application.

Initially, every incoming message directly updated React state:

socket.on("power-update", (data) => {
  setGridData(data);
});

At first, this seemed like the correct approach. However, as the number of wind turbines increased, the application started experiencing:

  • Frequent React re-renders
  • High CPU usage
  • UI lag
  • Grid flickering
  • Poor scrolling performance
  • Reduced browser responsiveness

The problem became more noticeable when monitoring hundreds of turbines simultaneously.

Root Cause Analysis

Every Socket.IO event triggered:

  • A React state update
  • A component re-render
  • Grid reconciliation
  • DOM updates

When hundreds of updates arrived every second, React spent most of its time rendering instead of responding to user interactions. The issue wasn't Socket.IO—the issue was updating the UI as frequently as data arrived.

The Solution

Instead of updating React state for every incoming Socket.IO event, we decoupled data ingestion from UI rendering.

Step 1: Store Incoming Data in a Ref

We stored the latest telemetry in a useRef:

const latestDataRef = useRef([]);

Whenever Socket.IO received new data:

socket.on("power-update", (data) => {
  latestDataRef.current = data;
});

Updating a ref does not trigger a React re-render. The application could now receive data continuously with minimal rendering overhead.

Step 2: Update the Grid at Fixed Intervals

Instead of rendering on every message, we refreshed the grid once every second:

useEffect(() => {
  const timer = setInterval(() => {
    setGridData(latestDataRef.current);
  }, 1000);

  return () => clearInterval(timer);
}, []);

The new controlled flow became:

Socket.IO
      │
      ▼
Update Ref
      │
      ▼
No React Re-render
      │
      ▼
Every 1 Second
      │
      ▼
Update Grid

The UI updated at a controlled rate while still displaying fresh data.

Why This Works

React state is designed to trigger UI updates. Refs store mutable values without causing re-renders. By separating data reception from UI rendering, the application could process every incoming message while limiting expensive rendering operations.

This pattern is especially useful in:

  • Live dashboards
  • IoT monitoring systems
  • Financial trading platforms
  • Industrial automation
  • Manufacturing control systems
  • Real-time analytics

Performance Improvements

After implementing this optimization, we observed:

  • A significant reduction in unnecessary React renders
  • Lower CPU utilization
  • Smoother grid scrolling
  • Improved browser responsiveness
  • Stable performance with a large number of connected turbines
  • Better scalability for future growth

Most importantly, users still saw near real-time data while the application remained responsive.

Lessons Learned

In real-time applications, receiving data quickly is only half the challenge. Displaying that data efficiently is equally important.

Updating React state for every incoming event may work for low-frequency updates, but high-frequency streams require a different approach. Using useRef as a temporary buffer and updating the UI on a controlled interval achieved a balance between real-time visibility and application performance.

Not every incoming event needs to trigger a UI render. Separating data ingestion from rendering is a simple yet powerful technique for building scalable real-time applications.

Conclusion

Real-time systems often fail not because of network throughput, but because the frontend tries to render faster than users can perceive changes. Introducing a lightweight buffering layer with useRef and rendering on a fixed interval allowed us to handle continuous telemetry efficiently while keeping the dashboard smooth and responsive.

This optimization became a key part of building a scalable monitoring solution capable of handling high-frequency data streams from industrial equipment.