Skip to content

Conversation

@divyanshu-patil
Copy link

@divyanshu-patil divyanshu-patil commented Nov 7, 2025

Proposed changes

This PR resolves the React warning Each child in a list should have a unique key prop.

  • Added unique key props to list-rendered components that were missing them.
  • added the _id to each parsed token in app/containers/markdown/index.tsx
  • the key is generated by taking random values + current date/ time
  • the numeric values are converted into base-36 to make generated keys container 0-9 and a-z letters

Closes #4943

files changed

  • app/containers/markdown/components/code/Code.tsx

  • app/containers/markdown/components/emoji/BigEmoji.tsx‎

  • app/containers/markdown/components/inline/Bold.tsx

  • app/containers/markdown/components/inline/Italic.tsx

  • app/containers/markdown/components/inline/Strike.tsx

  • app/containers/markdown/components/list/UnorderedList.tsx

  • app/containers/markdown/components/list/TaskList.tsx

  • app/containers/markdown/components/Inline.tsx

  • app/containers/markdown/index.tsx

  • app/containers/UIKit/MessageBlock.tsx

  • app/containers/markdown/index.tsx : generate unique id and assigns them to each parsed token so now every block contains ._id field

How to test or reproduce

Screenshots

image image

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • Bug Fixes
    • Improved rendering stability for messages, lists, and markdown content with enhanced item tracking during updates.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

Walkthrough

This PR adds stable React keys to markdown and message block rendering components by implementing client-side ID generation at the block and child element level. A recursive assignIds function decorates each block in the token tree with unique _id properties, which are then used as React keys during component rendering across inline elements, lists, code blocks, quotes, emojis, and message blocks.

Changes

Cohort / File(s) Change Summary
Core markdown ID generation
app/containers/markdown/index.tsx
Adds generateId utility and recursive assignIds function to attach unique _id to each block and nested children. Applies IDs to tokens prior to rendering and supplies key={block._id} for all block types (BIG_EMOJI, UNORDERED_LIST, ORDERED_LIST, TASKS, QUOTE, PARAGRAPH, CODE, HEADING, LINE_BREAK, KATEX).
Inline text formatting elements
app/containers/markdown/components/inline/Bold.tsx, app/containers/markdown/components/inline/Italic.tsx, app/containers/markdown/components/inline/Strike.tsx
Each file introduces a local type extending the base type with _id: string. During value mapping, items are cast to this type and rendered with stable keys derived from block._id. Applies to LINK, PLAIN_TEXT, BOLD, ITALIC, STRIKE, and MENTION_CHANNEL child elements.
Root inline rendering
app/containers/markdown/components/Inline.tsx
Adds type alias TInlineWithID and casts inline blocks during iteration. All rendered elements (Image, Plain, Bold, Strike, Italic, Link, AtMention, Emoji, Hashtag, InlineCode, Text) now receive key={block._id}.
List components
app/containers/markdown/components/list/UnorderedList.tsx, app/containers/markdown/components/list/TaskList.tsx
Both introduce local types extending their respective base types with _id: string. List items and task rows are keyed with item._id while preserving existing UI structure and content rendering.
Code and special content blocks
app/containers/markdown/components/code/Code.tsx, app/containers/markdown/components/Quote.tsx, app/containers/markdown/components/emoji/BigEmoji.tsx
Each file adds a local type with _id: string extension. Code lines, quote paragraphs, and emoji elements are now rendered with key={block._id} instead of unkeyed rendering.
UIKit message block rendering
app/containers/UIKit/MessageBlock.tsx
Updates modalBlockWithContext to merge context with data in provider value as {{ ...context, ...data }}. Generalizes MessageBlock rendering to wrap arrays of rendered elements in React.Fragment with stable keys derived from block.props.blockId or index fallback.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Repetitive pattern application: The same ID-typing-and-keying strategy is applied consistently across 8+ markdown components, reducing cognitive load per file.
  • Core logic verification needed: The assignIds recursive function in markdown/index.tsx requires careful review to ensure IDs are correctly propagated to all nested blocks and children.
  • Unique changes warrant attention:
    • MessageBlock.tsx context merging logic differs from the pattern
    • Type casting consistency across files with TXXXWithID types
  • Potential edge cases: Verify ID stability across re-renders and that fallback index-based keys in MessageBlock.tsx don't cause reconciliation issues.

Possibly related PRs

  • fix: File rendering as URL #6725: Modifies app/containers/markdown/index.tsx and adjusts Markdown props and preview paths; shares the same core markdown rendering file with this PR's ID generation logic.

Suggested reviewers

  • OtavioStasiak

Poem

🐰 Hop! Hop! Each block now has a name,
IDs flowing through the markdown frame,
React keys dance without a care,
No more warnings in the render air!
Stable rendering, fast and free,
That's the way it's meant to be! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main objective: adding unique key props to components that were missing them to resolve React warnings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3532447 and 3629d17.

📒 Files selected for processing (6)
  • app/containers/UIKit/MessageBlock.tsx (1 hunks)
  • app/containers/markdown/components/Inline.tsx (2 hunks)
  • app/containers/markdown/components/emoji/BigEmoji.tsx (1 hunks)
  • app/containers/markdown/components/inline/Bold.tsx (1 hunks)
  • app/containers/markdown/components/list/UnorderedList.tsx (1 hunks)
  • app/containers/markdown/index.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/containers/markdown/components/Inline.tsx (2)
app/containers/markdown/Markdown.stories.tsx (4)
  • Image (121-125)
  • Emoji (94-101)
  • Hashtag (88-92)
  • Text (51-59)
app/containers/EmojiPicker/Emoji.tsx (1)
  • Emoji (9-17)
app/containers/markdown/index.tsx (2)
app/containers/markdown/components/LineBreak.tsx (1)
  • LineBreak (4-6)
app/containers/markdown/components/Katex.tsx (1)
  • KaTeX (16-28)
app/containers/markdown/components/list/UnorderedList.tsx (1)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/UIKit/MessageBlock.tsx (1)
app/containers/UIKit/index.tsx (1)
  • UiKitMessage (211-211)
🔇 Additional comments (7)
app/containers/markdown/components/emoji/BigEmoji.tsx (1)

19-20: LGTM! Good key generation strategy.

The key generation appropriately prioritizes meaningful identifiers (shortCode or unicode) before falling back to the index, ensuring both uniqueness and stability.

app/containers/markdown/index.tsx (1)

78-100: Keys applied consistently across all block types.

All rendered block components now receive the generated key prop, which resolves the React warning. The switch statement covers all block types appropriately.

app/containers/markdown/components/list/UnorderedList.tsx (1)

18-28: LGTM! Key generation ensures uniqueness.

The indexed map with a composite key (${value}-${index}) ensures uniqueness. The optional chaining on toString?.() is good defensive coding.

app/containers/markdown/components/inline/Bold.tsx (1)

28-45: Keys applied consistently to all block types.

All cases in the switch statement now receive the generated key prop, properly resolving React key warnings.

app/containers/markdown/components/Inline.tsx (1)

52-87: Keys applied comprehensively across all inline block types.

All block types, including AtMention, now receive unique keys. The key generation strategy ensures stability and uniqueness.

app/containers/UIKit/MessageBlock.tsx (2)

13-19: LGTM! Proper handling of array rendering with stable keys.

The implementation correctly:

  1. Guards against null/undefined blocks
  2. Checks if the result is an array before mapping
  3. Uses blockId when available (stable identifier) before falling back to index
  4. Wraps each element in a Fragment with a unique key

21-26: Context merging looks appropriate.

The spread of both context and data into the KitContext.Provider value ensures all necessary properties are available to the ModalBlock component.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3629d17 and 589512e.

📒 Files selected for processing (4)
  • app/containers/markdown/components/Inline.tsx (3 hunks)
  • app/containers/markdown/components/inline/Bold.tsx (2 hunks)
  • app/containers/markdown/index.tsx (2 hunks)
  • app/lib/methods/getBlockValueString.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/containers/markdown/components/Inline.tsx
  • app/containers/markdown/index.tsx
  • app/containers/markdown/components/inline/Bold.tsx

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: 0

🧹 Nitpick comments (1)
app/lib/methods/getBlockValueString.ts (1)

1-19: Excellent implementation—all past feedback addressed!

The function now properly handles distinct falsy values, uses a separator for array joins, includes clear JSDoc, and has a reasonable truncation limit. This will generate stable, debuggable keys for React rendering.

One minor consideration: JSON.stringify on line 17 can throw on circular references or BigInt values. However, given the context (Markdown/UIKit blocks), these edge cases are unlikely. If you want to be defensive, you could wrap it in a try-catch and return a fallback like '[object]':

-	return JSON.stringify(v).slice(0, 50);
+	try {
+		return JSON.stringify(v).slice(0, 50);
+	} catch {
+		return '[object]';
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 589512e and c015085.

📒 Files selected for processing (4)
  • app/containers/markdown/components/Inline.tsx (3 hunks)
  • app/containers/markdown/components/inline/Bold.tsx (2 hunks)
  • app/containers/markdown/index.tsx (2 hunks)
  • app/lib/methods/getBlockValueString.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/containers/markdown/components/Inline.tsx
  • app/containers/markdown/components/inline/Bold.tsx
  • app/containers/markdown/index.tsx

Copy link
Member

@diegolmello diegolmello left a comment

Choose a reason for hiding this comment

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

Using index as key is never a good idea https://www.epicreact.dev/why-react-needs-a-key-prop

@divyanshu-patil
Copy link
Author

Updated the PR
All index-based keys have been replaced with well formed keys
also added keys for components that i missed before

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

♻️ Duplicate comments (5)
app/containers/markdown/components/list/UnorderedList.tsx (1)

20-30: LGTM: Stable keys for list items.

Using item._id as the key for each list item View is the correct approach.

Note: The same type assertion concern mentioned in Code.tsx applies here - ensure _id is populated upstream.

app/containers/markdown/components/Quote.tsx (1)

22-25: LGTM: Stable keys for quote paragraphs.

Each Paragraph component now has a stable key via item._id.

Note: The same type assertion concern mentioned in Code.tsx applies here.

app/containers/markdown/components/inline/Strike.tsx (1)

22-38: LGTM: All inline elements properly keyed.

All rendered components (Link, Plain, Bold, Italic) now have stable keys via block._id.

Note: The same type assertion concern mentioned in Code.tsx applies here.

app/containers/markdown/components/inline/Italic.tsx (2)

22-38: LGTM: All inline elements properly keyed.

All rendered components (Link, Plain, Strike, Bold) now have stable keys via block._id.

Note: The same type assertion concern mentioned in Code.tsx applies here.


18-18: Consider simplifying the generic type.

Similar to Strike.tsx, the generic parameter in TItalicWithID<T> is unnecessary. A simpler type would be:

type TItalicBlockWithID = ItalicProps['value'][number] & { _id: string };
🧹 Nitpick comments (1)
app/containers/markdown/components/inline/Strike.tsx (1)

18-18: Consider simplifying the generic type.

TStrikeWithID<T> uses a generic parameter, but T is always typeof b from the map callback. A simpler non-generic type would suffice:

type TStrikeBlockWithID = StrikeProps['value'][number] & { _id: string };

Then use: const block = b as TStrikeBlockWithID;

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 70f99ac and 810b56d.

📒 Files selected for processing (11)
  • app/containers/UIKit/MessageBlock.tsx (1 hunks)
  • app/containers/markdown/components/Inline.tsx (4 hunks)
  • app/containers/markdown/components/Quote.tsx (2 hunks)
  • app/containers/markdown/components/code/Code.tsx (3 hunks)
  • app/containers/markdown/components/emoji/BigEmoji.tsx (2 hunks)
  • app/containers/markdown/components/inline/Bold.tsx (1 hunks)
  • app/containers/markdown/components/inline/Italic.tsx (1 hunks)
  • app/containers/markdown/components/inline/Strike.tsx (1 hunks)
  • app/containers/markdown/components/list/TaskList.tsx (2 hunks)
  • app/containers/markdown/components/list/UnorderedList.tsx (2 hunks)
  • app/containers/markdown/index.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/containers/markdown/components/emoji/BigEmoji.tsx
  • app/containers/markdown/index.tsx
  • app/containers/markdown/components/Inline.tsx
  • app/containers/markdown/components/inline/Bold.tsx
  • app/containers/markdown/components/list/TaskList.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
app/containers/markdown/components/Quote.tsx (2)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/markdown/components/code/Code.tsx (1)
app/containers/markdown/components/code/index.ts (1)
  • CodeLine (4-4)
app/containers/markdown/components/list/UnorderedList.tsx (3)
app/containers/markdown/components/list/index.ts (1)
  • UnorderedList (5-5)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/UIKit/MessageBlock.tsx (1)
app/containers/UIKit/index.tsx (1)
  • UiKitMessage (211-211)
🔇 Additional comments (3)
app/containers/markdown/components/code/Code.tsx (2)

27-31: Good: Stable key usage with _id.

The switch from index-based keys to block._id resolves the React warning and provides stable keys across renders.


13-13: I'm unable to complete the verification as the repository clone operation failed. Without direct access to the codebase, I cannot:

  1. Examine the Code component implementation at lines 13-28
  2. Verify how and where assignIds is called
  3. Trace data flow to confirm _id assignment before rendering
  4. Identify all code paths leading to the Code component

To proceed, I would need either:

  • Resolution of the repository access issue, or
  • You to provide the relevant code sections (Code.tsx, usage locations, assignIds implementation)

Could you provide the code or confirm the repository access can be restored?

app/containers/UIKit/MessageBlock.tsx (1)

28-28: I'm unable to verify the review comment due to repository access limitations and insufficient public documentation about the specific return types of these UIKit rendering functions. Let me mark this for manual verification:

Unable to verify if UiKitModal returns arrays that would require the suggested defensive handling pattern.

The review comment's suggestion depends on whether UiKitModal(blocks) can return an array of elements (similar to UiKitMessage). Without access to:

  • The actual implementation of UiKitModal and UiKitMessage
  • The current implementation of MessageBlock to confirm it uses the suggested pattern
  • The repository code to trace these function behaviors

I cannot confirm whether this suggestion is necessary or accurate.

To resolve this review comment, manually verify:

  1. Check if UiKitModal(blocks) can return an array of React elements
  2. Check if UiKitMessage(blocks) returns arrays and whether MessageBlock implements the suggested defensive handling
  3. If both can return arrays, apply the same pattern to ModalBlock for consistency

Comment on lines +16 to +17
return Array.isArray(renderedBlocks)
? renderedBlocks.map(element => <React.Fragment key={element.props.blockId}>{element}</React.Fragment>)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

❓ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Search for UiKitMessage implementation and blockId assignment
rg -n "blockId" --type ts --type tsx -C 3 | head -100

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 212


🏁 Script executed:

#!/bin/bash
# Find UiKitMessage definition
rg -n "UiKitMessage" --type ts --type tsx -C 3 | head -80

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 212


🏁 Script executed:

#!/bin/bash
# Look at the actual MessageBlock file to see full context
cat -n app/containers/UIKit/MessageBlock.tsx

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 212


The repository cannot be cloned in the sandbox environment. However, I can still provide a rewritten review comment based on the context provided and common React/TypeScript patterns.

Let me analyze the original review comment:

Key observations from the provided context:

  1. The code uses element.props.blockId as the React key without any fallback
  2. The AI summary mentioned a fallback mechanism, but the actual code doesn't show one
  3. The concern is valid: if blockId is undefined/null, multiple fragments could share the same key

Analysis:

  • The original review correctly identifies a potential issue: React will warn if multiple elements share undefined as a key
  • Whether this is actually a problem depends on whether UiKitMessage always provides blockId on rendered elements
  • Without access to the implementation, the safest approach is to flag this as requiring verification by the developer

Since I cannot verify the codebase directly, the review comment should be rewritten to be more precise about what needs verification while preserving the valid concern:


Consider adding a fallback for blockId if it can be undefined.

The current implementation sets the key to element.props.blockId without a fallback. If any rendered element lacks a blockId property, React will warn about duplicate keys. Verify that UiKitMessage guarantees blockId on all rendered elements; if not, add an index fallback:

-	return Array.isArray(renderedBlocks)
-		? renderedBlocks.map(element => <React.Fragment key={element.props.blockId}>{element}</React.Fragment>)
+	return Array.isArray(renderedBlocks)
+		? renderedBlocks.map((element, index) => (
+				<React.Fragment key={element.props.blockId ?? `block-${index}`}>{element}</React.Fragment>
+			))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return Array.isArray(renderedBlocks)
? renderedBlocks.map(element => <React.Fragment key={element.props.blockId}>{element}</React.Fragment>)
return Array.isArray(renderedBlocks)
? renderedBlocks.map((element, index) => (
<React.Fragment key={element.props.blockId ?? `block-${index}`}>{element}</React.Fragment>
))
🤖 Prompt for AI Agents
In app/containers/UIKit/MessageBlock.tsx around lines 16 to 17, the mapped
Fragment uses element.props.blockId as the React key with no fallback; ensure
keys are always stable by verifying UiKitMessage guarantees a non-empty blockId
or change the mapping to derive a unique key when blockId is missing (for
example use the map index or a deterministic fallback like
`${element.type}-${index}`), and update the code to use that fallback so React
never receives duplicate/undefined keys.

@divyanshu-patil
Copy link
Author

updated the PR,
now none of the keys relies on the index

every direct or nested block now contains _id field which we can use as key
keys are generated using helper function generateID()

switch (block.type) {
case 'CODE_LINE':
return <CodeLine value={block.value} />;
return <CodeLine key={block._id} value={block.value} />;
Copy link
Member

Choose a reason for hiding this comment

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

where is _id coming from?

Copy link
Author

Choose a reason for hiding this comment

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

in app/containers/markdown/index.tsx
after the tokens are parsed we add _id field in it

assigning ids here

generating ids

i also tested in my local _id field always contains unique 16 char value

Copy link
Author

@divyanshu-patil divyanshu-patil Nov 27, 2025

Choose a reason for hiding this comment

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

image

ids are assigned to nested blocks also, so even if the block internally maps the tokens again like BigEmoji works perfectly with this keys

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.

Error: Each child in a list should have a unique "key" prop

2 participants