Skip to content

refactor(ContentPreview): change from children to render prop pattern (WEBAPP-49784)#4457

Merged
mergify[bot] merged 2 commits intobox:masterfrom
ahorowitz123:custom-preview-render-prop
Feb 27, 2026
Merged

refactor(ContentPreview): change from children to render prop pattern (WEBAPP-49784)#4457
mergify[bot] merged 2 commits intobox:masterfrom
ahorowitz123:custom-preview-render-prop

Conversation

@ahorowitz123
Copy link
Contributor

@ahorowitz123 ahorowitz123 commented Feb 27, 2026

Summary

  • Refactors ContentPreview custom preview API from children pattern to render prop pattern
  • Provides better ergonomics and clearer developer intent with explicit function signature
  • Improves TypeScript/Flow type inference for custom preview props
  • Maintains full backward compatibility with all existing functionality

Changes

  • Replace children?: React.Node with renderCustomPreview?: (props: ContentPreviewChildProps) => React.Node
  • Update CustomPreviewWrapper to call render function instead of React.cloneElement()
  • Wrap rendered content in fragment (<>) to satisfy ErrorBoundary's React.Element requirement
  • Update all 119 ContentPreview tests to use render prop syntax
  • Update JSDoc comments and examples

API Comparison

Before (children pattern):

<ContentPreview fileId="123" token={token}>
  <MarkdownEditor />
</ContentPreview>

After (render prop pattern):
<ContentPreview
  fileId="123"
  token={token}
  renderCustomPreview={(props) => <MarkdownEditor {...props} />}
/>

Benefits

- More explicit intent - function signature clearly shows available props
- Better TypeScript/Flow type inference for render function parameters
- More idiomatic React pattern (matches patterns in React Router, Formik, etc.)
- Easier conditional rendering and prop transformation

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Refactor**
* Replaced children-based custom preview with a renderCustomPreview render-function prop; custom previews now receive a props object and render via the provided function. Default preview is skipped when renderCustomPreview is supplied.
* **Tests**
* Updated tests to exercise renderCustomPreview behavior, prop injection, early-return loading, and error/wrapping flows.
* **Documentation**
* Updated docs/comments to describe the new render function API and rendering behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Refactors the custom preview API from children pattern to render prop
pattern for better ergonomics and clearer developer intent.

Changes:
- Replace children?: React.Node with renderCustomPreview
- Update CustomPreviewWrapper to call render function
- Wrap rendered content in fragment to satisfy ErrorBoundary
- Update all 119 tests to use render prop syntax
- Update JSDoc comments and examples

Before (children pattern):
  <ContentPreview fileId="123" token={token}>
    <MarkdownEditor />
  </ContentPreview>

After (render prop pattern):
  <ContentPreview
    fileId="123"
    token={token}
    renderCustomPreview={(props) => <MarkdownEditor {...props} />}
  />

Benefits:
- More explicit intent - function signature shows available props
- Better TypeScript/Flow type inference
- More idiomatic React pattern
- Easier conditional rendering and prop transformation

Testing:
- All 119 ContentPreview tests passing
- Flow: 0 errors
- ESLint: clean

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ahorowitz123 ahorowitz123 requested review from a team as code owners February 27, 2026 01:41
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Walkthrough

This PR replaces the children injection API with a renderCustomPreview render-function prop across ContentPreview and CustomPreviewWrapper, updates initialization and keyboard/load logic to skip Box.Preview when renderCustomPreview is provided, and updates tests to the new API.

Changes

Cohort / File(s) Summary
Custom Preview API Refactor
src/elements/content-preview/ContentPreview.js, src/elements/content-preview/CustomPreviewWrapper.js
Replaced public children prop with renderCustomPreview: (props: ContentPreviewChildProps) => React.Node. ContentPreview now early-returns/skips Box.Preview init when renderCustomPreview is present and passes ContentPreviewChildProps into CustomPreviewWrapper, which calls the render function instead of cloning children and wraps result in an ErrorBoundary.
Tests
src/elements/content-preview/__tests__/ContentPreview.test.js
Updated tests to provide and assert renderCustomPreview usage: verify early-return/load behavior when provided, ensure render function receives injected props, and validate rendered output within wrapper/ErrorBoundary flow.

Sequence Diagram(s)

sequenceDiagram
    participant User as Client
    participant CP as ContentPreview
    participant CWrapper as CustomPreviewWrapper
    participant BoxPreview as Box.Preview
    participant ErrorB as ErrorBoundary

    rect rgba(200,200,255,0.5)
    User->>CP: mount with renderCustomPreview
    CP->>CP: detect renderCustomPreview -> skip Box.Preview init
    CP->>CWrapper: render with renderCustomPreview + childProps
    CWrapper->>ErrorB: wrap rendered node
    ErrorB->>User: render custom content
    end

    rect rgba(200,255,200,0.5)
    User->>CP: mount without renderCustomPreview
    CP->>BoxPreview: initialize/load preview
    BoxPreview->>CP: preview ready
    CP->>User: render default preview
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

ready-to-merge

Suggested reviewers

  • mickr
  • shahzadaziz
  • JChan106

Poem

🐰 I swapped a child for a function's song,
Props hop in where clones don't belong,
I render with care, wrapped safe and sound,
Custom previews now leap and bound! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main refactoring: changing ContentPreview from a children pattern to a render prop pattern.
Description check ✅ Passed The description is comprehensive, including a summary, detailed changes, API comparison with before/after examples, and stated benefits. It clearly explains the refactoring and maintains a helpful structure.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (2)
src/elements/content-preview/ContentPreview.js (1)

1275-1283: ⚠️ Potential issue | 🔴 Critical

Bug: children prop reference not updated to renderCustomPreview.

The onKeyDown method still references children from props (line 1276), but this prop was removed and replaced with renderCustomPreview. This means hotkey handling will never be skipped for custom preview content, causing potential conflicts with custom component keyboard shortcuts.

🐛 Proposed fix
     onKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
-        const { useHotkeys, children }: Props = this.props;
+        const { useHotkeys, renderCustomPreview }: Props = this.props;

         // Skip ContentPreview hotkeys when custom content children are provided to prevent conflicts.
         // Custom components must implement their own keyboard shortcuts (arrow navigation, etc)
         // as ContentPreview's default handlers only work with Box.Preview viewer.
-        if (!useHotkeys || children) {
+        if (!useHotkeys || renderCustomPreview) {
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/elements/content-preview/ContentPreview.js` around lines 1275 - 1283, The
onKeyDown handler still checks the removed children prop, so hotkey skipping
never occurs for custom previews; update the conditional in onKeyDown (method
onKeyDown in ContentPreview.js) to check this.props.renderCustomPreview instead
of this.props.children (and update the destructuring where useHotkeys and
children are read to useHotkeys and renderCustomPreview) so that when
renderCustomPreview is provided the handler returns early and hotkeys are
skipped.
src/elements/content-preview/__tests__/ContentPreview.test.js (1)

1753-1767: ⚠️ Potential issue | 🟡 Minor

Test will fail due to bug in ContentPreview.js.

This test expects onKeyDown to return early when renderCustomPreview is provided, but the production code at line 1276 still checks children instead of renderCustomPreview. This test will fail because event.preventDefault will be called (the hotkey logic won't skip).

This test correctly describes the expected behavior - once the bug in ContentPreview.js is fixed (changing children to renderCustomPreview in onKeyDown), this test should pass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/elements/content-preview/__tests__/ContentPreview.test.js` around lines
1753 - 1767, The onKeyDown handler in ContentPreview.js incorrectly checks the
presence of children instead of renderCustomPreview, causing hotkey handling to
run when a custom preview is provided; update the conditional in the onKeyDown
method (ContentPreview.prototype.onKeyDown / function onKeyDown) to check
renderCustomPreview (the prop) and return early when it's present so
event.preventDefault and event.stopPropagation are not called for custom
previews.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/elements/content-preview/__tests__/ContentPreview.test.js`:
- Around line 1753-1767: The onKeyDown handler in ContentPreview.js incorrectly
checks the presence of children instead of renderCustomPreview, causing hotkey
handling to run when a custom preview is provided; update the conditional in the
onKeyDown method (ContentPreview.prototype.onKeyDown / function onKeyDown) to
check renderCustomPreview (the prop) and return early when it's present so
event.preventDefault and event.stopPropagation are not called for custom
previews.

In `@src/elements/content-preview/ContentPreview.js`:
- Around line 1275-1283: The onKeyDown handler still checks the removed children
prop, so hotkey skipping never occurs for custom previews; update the
conditional in onKeyDown (method onKeyDown in ContentPreview.js) to check
this.props.renderCustomPreview instead of this.props.children (and update the
destructuring where useHotkeys and children are read to useHotkeys and
renderCustomPreview) so that when renderCustomPreview is provided the handler
returns early and hotkeys are skipped.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97e633c and f25439c.

📒 Files selected for processing (3)
  • src/elements/content-preview/ContentPreview.js
  • src/elements/content-preview/CustomPreviewWrapper.js
  • src/elements/content-preview/__tests__/ContentPreview.test.js

The onKeyDown handler was still checking the removed children prop, which meant
hotkey skipping never occurred for custom previews. Updated the conditional to
check renderCustomPreview instead, ensuring custom previews can implement their
own keyboard shortcuts without conflicts.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/elements/content-preview/ContentPreview.js`:
- Line 172: The Props change removed support for the old children pattern but
the code and comment are inconsistent—update the ContentPreview component to
either (A) restore a backward-compatibility warning by detecting a passed
children prop (in the ContentPreview function/component) and emitting a dev-only
console.warn or process.env.NODE_ENV check explaining children are deprecated
and suggesting renderCustomPreview, or (B) if you intend this to be a breaking
change, remove the stale comment referencing "Even when children are provided"
and amend the PR description to explicitly state the breaking change; reference
the renderCustomPreview prop and the ContentPreview component when making the
change so the warning or documentation accurately targets consumers of the
previous <ContentPreview><CustomComponent/></ContentPreview> pattern.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f25439c and 18a6b58.

📒 Files selected for processing (1)
  • src/elements/content-preview/ContentPreview.js

Copy link
Collaborator

@shahzadaziz shahzadaziz left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for the follow up.

@mergify mergify bot added the queued label Feb 27, 2026
@mergify mergify bot merged commit 881b71c into box:master Feb 27, 2026
8 of 9 checks passed
@mergify
Copy link
Contributor

mergify bot commented Feb 27, 2026

Merge Queue Status

Rule: Automatic strict merge


  • Entered queue2026-02-27 16:01 UTC
  • Checks passed · in-place
  • Merged2026-02-27 16:01 UTC · at 18a6b58ecd484f15020fe23d636a33af4cca13b2

This pull request spent 6 seconds in the queue, with no time running CI.

Required conditions to merge

@mergify mergify bot removed the queued label Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants