Skip to content

fix: Stabilize block document renderer props to reduce rerenders - #761

Merged
ilyabo merged 2 commits into
mainfrom
fix-dashboard-block-rerenders
Jun 22, 2026
Merged

ilyabo merged 2 commits into
mainfrom
fix-dashboard-block-rerenders

Conversation

@ilyabo

@ilyabo ilyabo commented Jun 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • memoize chart renderer context values to keep provider identity stable
  • wrap chart and stateful block renderers in memoized boundaries
  • route NodeView change handlers through refs to avoid stale closures while preserving read-only checks
  • memoize stateful block wrapper styles when height is resolved

Testing

  • Not run (not requested)

Summary by CodeRabbit

  • Refactor
    • Optimized rendering for chart and stateful block components using memoized boundaries and stable render props.
    • Improved context stability by keeping the provided value reference consistent when the renderer doesn’t change.
    • Fixed potential stale-closure behavior in title/caption/table-name/config update handlers by synchronizing read-only and update logic with refs.
    • Standardized default chart config handling and reduced unnecessary recomputation (e.g., wrapper styles).

@coderabbitai

coderabbitai Bot commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70b1ffe2-37bf-4c42-9f03-19b9dcad3360

📥 Commits

Reviewing files that changed from the base of the PR and between 7eec707 and 1ed4f71.

📒 Files selected for processing (3)
  • packages/documents/src/BlockDocumentChartRendererContext.tsx
  • packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx
  • packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx
  • packages/documents/src/BlockDocumentChartRendererContext.tsx
  • packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx

📝 Walkthrough

Walkthrough

Three files in the documents package are updated to improve React rendering stability. BlockDocumentChartRendererContext memoizes its provider value. BlockDocumentChartNodeView and BlockDocumentStatefulBlockNodeView each introduce a new memo-wrapped boundary component and replace direct updateAttributes/readOnly closure captures with useRef variables synchronized via useEffect.

Changes

Render stability: memoized boundaries and stable callbacks

Layer / File(s) Summary
Chart renderer context value stabilization
packages/documents/src/BlockDocumentChartRendererContext.tsx
BlockDocumentChartRendererProvider now wraps its context value in useMemo keyed on renderer, preventing a new object reference from being created on every render.
ChartNodeView: memoized boundary and ref-based callbacks
packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx
Introduces ChartRendererBoundary as a memo-wrapped component with a typed props shape. Callback handlers for table name, config, and caption now access readOnly and updateAttributes through useRef variables kept current by useEffect, removing stale closure captures. The render path replaces inline createElement with ChartRendererBoundary.
StatefulBlockNodeView: memoized boundary and ref-based callbacks
packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx
Adds StatefulBlockRendererBoundary as a memo-wrapped wrapper. Introduces updateAttributesRef kept current via useEffect. wrapperStyle is computed with useMemo. handleTitleChange and handleCaptionChange delegate through updateAttributesRef.current. Renderer creation switches to StatefulBlockRendererBoundary.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hopping through renders, no closures astray,
A memo'd boundary keeps stale bugs at bay.
The ref holds the truth while the callbacks stay still,
No phantom old values to cause a re-thrill.
Fresh context, stable props — the warren is bright,
Every chart and stateful block renders just right! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Stabilize block document renderer props to reduce rerenders' directly and accurately describes the main objective of the PR - addressing performance issues by stabilizing component props and context values to prevent unnecessary rerenders.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-dashboard-block-rerenders

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ilyabo ilyabo changed the title Stabilize block document renderer props to reduce rerenders fix: Stabilize block document renderer props to reduce rerenders Jun 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx (1)

130-134: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the read-only guard in the new stateful callbacks.

The chart callbacks block writes when readOnly is true, but these new title/caption handlers always call updateAttributesRef.current. A renderer that invokes the callback despite readOnly can still mutate the document.

🛡️ Proposed read-only guard
   const wrapperRef = useRef<HTMLDivElement | null>(null);
   const updateAttributesRef = useRef(updateAttributes);
   const hideScrollHintTimeoutRef = useRef<number | undefined>(undefined);
   const scrollHintTargetRef = useRef<HTMLElement | null>(null);
   const resizeCleanupRef = useRef<(() => void) | null>(null);
   const {documentId, readOnly} = useBlockDocumentEditorContext();
+  const readOnlyRef = useRef(readOnly);
   useEffect(() => {
     updateAttributesRef.current = updateAttributes;
-  }, [updateAttributes]);
+    readOnlyRef.current = readOnly;
+  }, [readOnly, updateAttributes]);
   const handleTitleChange = useCallback((nextTitle: string | undefined) => {
+    if (readOnlyRef.current) return;
     updateAttributesRef.current({title: nextTitle || undefined});
   }, []);
 
   const handleCaptionChange = useCallback((nextCaption: string | undefined) => {
+    if (readOnlyRef.current) return;
     updateAttributesRef.current({caption: nextCaption});
   }, []);

Also applies to: 168-170, 329-335

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx`
around lines 130 - 134, The new title and caption callbacks are missing a
read-only guard that prevents mutations when the document is read-only. Add a
check at the beginning of these callbacks (which call
updateAttributesRef.current) to ensure they only execute when readOnly is false,
using the readOnly variable already destructured from
useBlockDocumentEditorContext. Apply this same guard pattern to all callback
handlers mentioned in the comment at lines 168-170 and 329-335 to ensure
consistent protection across all mutation operations.
🧹 Nitpick comments (4)
packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx (2)

36-38: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add TSDoc for the exported node view component.

This exported component is part of the touched TSX surface and should have a short API-level description.

♻️ Proposed documentation addition
+/** Renders a chart block as a Tiptap node view inside the block document editor. */
 export const BlockDocumentChartNodeView: FC<
   BlockDocumentChartNodeViewProps
 > = ({node, selected, updateAttributes}) => {

As per coding guidelines, **/*.{ts,tsx}: “Add tsdoc to exported types and functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx`
around lines 36 - 38, Add a TSDoc comment block above the
BlockDocumentChartNodeView component definition that provides a short, API-level
description of what this node view component does. The TSDoc comment should
start with /** and include a brief summary of the component's purpose and its
role in the document editing context. This aligns with the coding guidelines
requiring TSDoc for all exported types and functions in TypeScript files.

Source: Coding guidelines


25-32: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Hoist the empty config fallback so the memo boundary can stay effective.

Line 48 creates a new {} whenever attrs.config is absent, so Line 86 passes a new config identity into ChartRendererBoundary on every parent render. That defeats the new memoized boundary for unconfigured chart blocks.

♻️ Proposed stable fallback
 type ChartRendererBoundaryProps = BlockDocumentChartRendererProps & {
   Renderer: BlockDocumentChartRenderer;
 };
 
+const EMPTY_CHART_CONFIG: Record<string, never> = {};
+
 const ChartRendererBoundary = memo(
   ({Renderer, ...props}: ChartRendererBoundaryProps) =>
     createElement(Renderer, props),
 );
-  const config = attrs.config ?? {};
+  const config = attrs.config ?? EMPTY_CHART_CONFIG;

Verify with a render-count/profiler case for a chart block without stored config; unrelated parent rerenders should not rerender the boundary after this change.

Also applies to: 48-48, 81-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx`
around lines 25 - 32, The memo optimization of ChartRendererBoundary is
ineffective because a new empty object is created on line 48 whenever
attrs.config is absent, causing the config prop to have a new identity on each
parent render. Extract the empty object fallback to a module-level constant so
that the same reference is reused across renders when config is missing. This
stable reference will allow the memo boundary to properly prevent unnecessary
rerenders of ChartRendererBoundary when the parent rerenders but the config
value hasn't actually changed.
packages/documents/src/BlockDocumentChartRendererContext.tsx (1)

31-38: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add TSDoc for the touched exported provider surface.

BlockDocumentChartRendererProviderProps and BlockDocumentChartRendererProvider are exported, and this change updates provider behavior.

♻️ Proposed documentation addition
+/** Props for {`@link` BlockDocumentChartRendererProvider}. */
 export type BlockDocumentChartRendererProviderProps = PropsWithChildren<{
   renderer?: BlockDocumentChartRenderer;
 }>;
 
+/** Provides the chart renderer used by block document chart node views. */
 export const BlockDocumentChartRendererProvider: FC<
   BlockDocumentChartRendererProviderProps
 > = ({renderer, children}) => {

As per coding guidelines, **/*.{ts,tsx}: “Add tsdoc to exported types and functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/documents/src/BlockDocumentChartRendererContext.tsx` around lines 31
- 38, Add TSDoc documentation comments to the exported type and function in this
file. Add a TSDoc comment block above the
BlockDocumentChartRendererProviderProps type definition to describe its purpose
and properties, and add another TSDoc comment block above the
BlockDocumentChartRendererProvider function to document its purpose, parameters,
and what it provides via context. Follow the coding guidelines that require
tsdoc to be added to exported types and functions in TypeScript files.

Source: Coding guidelines

packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx (1)

126-128: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add TSDoc for the exported stateful node view component.

This exported component is part of the touched TSX surface and should have a short API-level description.

♻️ Proposed documentation addition
+/** Renders a stateful block as a Tiptap node view inside the block document editor. */
 export const BlockDocumentStatefulBlockNodeView: FC<
   BlockDocumentStatefulBlockNodeViewProps
 > = ({node, selected, updateAttributes}) => {

As per coding guidelines, **/*.{ts,tsx}: “Add tsdoc to exported types and functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx`
around lines 126 - 128, The exported component
BlockDocumentStatefulBlockNodeView is missing TSDoc documentation at the
component declaration level. Add a TSDoc comment block directly above the
component declaration that provides a brief, API-level description of what this
stateful block node view component does and its purpose. The documentation
should follow the coding guideline that requires tsdoc for all exported types
and functions in typescript files.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx`:
- Around line 130-134: The new title and caption callbacks are missing a
read-only guard that prevents mutations when the document is read-only. Add a
check at the beginning of these callbacks (which call
updateAttributesRef.current) to ensure they only execute when readOnly is false,
using the readOnly variable already destructured from
useBlockDocumentEditorContext. Apply this same guard pattern to all callback
handlers mentioned in the comment at lines 168-170 and 329-335 to ensure
consistent protection across all mutation operations.

---

Nitpick comments:
In `@packages/documents/src/BlockDocumentChartRendererContext.tsx`:
- Around line 31-38: Add TSDoc documentation comments to the exported type and
function in this file. Add a TSDoc comment block above the
BlockDocumentChartRendererProviderProps type definition to describe its purpose
and properties, and add another TSDoc comment block above the
BlockDocumentChartRendererProvider function to document its purpose, parameters,
and what it provides via context. Follow the coding guidelines that require
tsdoc to be added to exported types and functions in TypeScript files.

In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx`:
- Around line 36-38: Add a TSDoc comment block above the
BlockDocumentChartNodeView component definition that provides a short, API-level
description of what this node view component does. The TSDoc comment should
start with /** and include a brief summary of the component's purpose and its
role in the document editing context. This aligns with the coding guidelines
requiring TSDoc for all exported types and functions in TypeScript files.
- Around line 25-32: The memo optimization of ChartRendererBoundary is
ineffective because a new empty object is created on line 48 whenever
attrs.config is absent, causing the config prop to have a new identity on each
parent render. Extract the empty object fallback to a module-level constant so
that the same reference is reused across renders when config is missing. This
stable reference will allow the memo boundary to properly prevent unnecessary
rerenders of ChartRendererBoundary when the parent rerenders but the config
value hasn't actually changed.

In
`@packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx`:
- Around line 126-128: The exported component BlockDocumentStatefulBlockNodeView
is missing TSDoc documentation at the component declaration level. Add a TSDoc
comment block directly above the component declaration that provides a brief,
API-level description of what this stateful block node view component does and
its purpose. The documentation should follow the coding guideline that requires
tsdoc for all exported types and functions in typescript files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 625d3165-8a5c-446e-aa40-f023c86ccda4

📥 Commits

Reviewing files that changed from the base of the PR and between c540c0b and 7eec707.

📒 Files selected for processing (3)
  • packages/documents/src/BlockDocumentChartRendererContext.tsx
  • packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentChartNodeView.tsx
  • packages/documents/src/BlockDocumentEditor/node-views/BlockDocumentStatefulBlockNodeView.tsx

@ilyabo
ilyabo merged commit 613bd9f into main Jun 22, 2026
4 checks passed
@ilyabo
ilyabo deleted the fix-dashboard-block-rerenders branch June 22, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant