Alwaysgaurav1 commented on PR #69617:
URL: https://github.com/apache/airflow/pull/69617#issuecomment-4916786330
> Very interesting! Could you share how you tested the rendering results to
get to a 90% reduction?
>
> I'm very curious how much memo-izing made an impact. Our newer react
compiler is supposed to do a lot of out of the box and when I removed a lot of
manual memo calls there was no noticeable perfomance impact. I guess that we
had too many props for an efficient compare?
Hi @bbovenzi! That's a great question. Here is how the performance
improvements were measured and why the React Compiler (React Forget) wasn't
able to optimize this behavior out-of-the-box.
### 1. How the 90% Reduction Was Measured
I profiled the Grid view interactions using the **React Developer Tools
Profiler** alongside the Chrome DevTools **Performance** tab:
* **Before the changes:** Hovering over a cell or column header triggered a
hover context change (`hoveredRunId` or `hoveredTaskId`). Since the context was
consumed directly by every single cell (`GridTI`), column
(`TaskInstancesColumn`), and header bar (`Bar`), **every element in the grid
subscribed to the context was forced to re-render**. For a moderate DAG with 40
tasks and 30 runs (~1,200 grid cells), a single hover state toggle forced
1,200+ component re-renders. This took **50ms–100ms+** per hover event, leading
to dropped frames, mouse cursor stutter, and visible interaction lag.
* **After the changes:** When hovering now, the React Profiler shows almost
the entire grid in gray (indicating that rendering was skipped). The only
components that actually execute their inner render logic are the lightweight
wrapper components (which run in <1ms) and the exact `GridTI` / `Bar` elements
that changed their hover state (the previously hovered row/column and the newly
hovered row/column). The number of rendering cells dropped from 1,200+ to
**just a handful**, reducing the rendering work by **over 90%** and keeping
frame render times well under **2ms–5ms** (solid 60fps).
---
### 2. Why the React Compiler Didn't Solve This Out-of-the-Box
The React Compiler is excellent at auto-memoizing component outputs and hook
dependencies to prevent children from re-rendering when their *parent*
re-renders. However, it has a structural limitation when it comes to **React
Context**:
1. **Context Bypass:** Under React's architecture, if a component calls
`useContext` (or a custom hook wrapping it like `useHover()`), it registers as
a direct subscriber. The React Compiler **cannot prevent** a subscribing
component's body from executing when the context value changes.
2. **Reference Invalidation:** Because the hover context value is a combined
object containing multiple states (`hoveredRunId`, `hoveredTaskId`, etc.), any
hover event updates the state, creating a new context object reference. This
invalidates the hook return value, forcing every subscriber to run its
component function.
3. **The Wrapper Pattern "Firewall":** The refactoring uses a lightweight
wrapper to consume the context, compute a stable primitive boolean (`isHovered
= hoveredId === currentId`), and pass it to a memoized inner component (e.g.,
`BarInner`, `GridTI` wrapped in `React.memo`).
* The wrapper *does* still execute on every hover update, but it does
virtually zero work.
* The heavy inner component is protected by `React.memo`. Since its prop
is a primitive boolean (`isHovered`), React sees that `false` remained `false`
(or `true` remained `true`) and skips executing the component and its children
entirely.
The React Compiler cannot automatically split context subscriptions or
insert wrapper component boundaries, which is why the manual wrapper +
`React.memo` pattern is still required here to prevent context-driven render
cascades.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]