Skip to content

perf: memoize @json model fields#7451

Closed
diegolmello wants to merge 1 commit into
developfrom
first-iceberg
Closed

perf: memoize @json model fields#7451
diegolmello wants to merge 1 commit into
developfrom
first-iceberg

Conversation

@diegolmello

@diegolmello diegolmello commented Jun 26, 2026

Copy link
Copy Markdown
Member

Proposed changes

Switch every @json-decorated WatermelonDB model field to the memoized form, @json('col', sanitizer, { memo: true }), across all 11 models (67 fields total). With memoization the decorator caches the [rawValue, sanitized] pair and returns the same object reference on every read while the underlying raw column string is unchanged. Today this is render-neutral; it sets up per-field re-render bailout for the consumers that adopt reference equality in later work.

Because the memo cache is invalidated only when the raw column string changes, mutating a memoized value in place would silently corrupt the cache (the mutated object is returned again with a stale identity). This PR converts the two remaining in-place mutations of these fields to replacement:

  • ReactionsList sorts a copy ([...reactions].sort(...)) instead of sorting the reactions field in place.
  • The encryption quote-attachment decryption reassigns message.attachments = [...] instead of push()-ing into the existing array.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1346

How to test or reproduce

No behaviour change is expected — this is a reference-stability refactor. To confirm there is no re-render regression:

  1. Open a room with reactions and an encrypted quoted reply; verify reactions render and sort correctly and the quoted attachment decrypts.
  2. Profile a representative room before and after the change (React DevTools). Measured here on device: target MessageContainer had 0 re-renders (first-mount only) and the RoomView control had 0 re-renders in all trials of both the baseline and the memoized bundle. Commit totals stayed in the same band. The swap is render-neutral; the benefit lands when consumers start relying on the stable references.

pnpm lint (0 errors) and TZ=UTC pnpm test (1770 passing) are green locally.

Screenshots

N/A — no UI changes.

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

The two mutation fixes are required for correctness once { memo: true } is in place — without them a sort or an attachment append would return a stale cached reference. They are intentionally bundled with the memo swap rather than split out, since in isolation they are no-ops.

Summary by CodeRabbit

  • Bug Fixes
    • Improved data handling across messages, threads, rooms, users, permissions, and custom emojis to avoid unnecessary reprocessing and side effects.
    • Fixed a reactions list update so it no longer changes the original input while sorting.
    • Made quote attachment handling safer by appending data immutably, helping prevent attachment-related inconsistencies.

Switch every @json-decorated WatermelonDB model field to the memoized form
({ memo: true }) so an unchanged nested JSON value keeps its reference identity
across reads, enabling per-field re-render bailout once consumers adopt it.

The memo cache is invalidated only when the raw column string changes, so
in-place mutation of a memoized value would silently corrupt it. Convert the
two remaining in-place mutations to replacement:
- ReactionsList sorts a copy ([...reactions].sort) instead of the field.
- encryption quote-attachment decryption reassigns message.attachments instead
  of pushing into it.

Ref: NATIVE-1346

Claude-Session: https://claude.ai/code/session_01Cj1jHhEbiC9mWGe8dpqQeS
@diegolmello diegolmello temporarily deployed to approve_e2e_testing June 26, 2026 21:13 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jun 26, 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 50f9d507-8bf5-4c9d-93b7-20df7a71fdb5

📥 Commits

Reviewing files that changed from the base of the PR and between a8c4899 and 6a8682c.

📒 Files selected for processing (13)
  • app/containers/ReactionsList/index.tsx
  • app/lib/database/model/CustomEmoji.js
  • app/lib/database/model/Message.js
  • app/lib/database/model/Permission.js
  • app/lib/database/model/Room.js
  • app/lib/database/model/Setting.js
  • app/lib/database/model/Subscription.js
  • app/lib/database/model/Thread.js
  • app/lib/database/model/ThreadMessage.js
  • app/lib/database/model/User.js
  • app/lib/database/model/servers/Server.js
  • app/lib/database/model/servers/User.js
  • app/lib/encryption/room.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/database/model/User.js
  • app/lib/database/model/Thread.js
  • app/lib/database/model/servers/User.js
  • app/lib/database/model/Permission.js
  • app/lib/database/model/servers/Server.js
  • app/lib/database/model/Setting.js
  • app/lib/database/model/CustomEmoji.js
  • app/lib/encryption/room.ts
  • app/containers/ReactionsList/index.tsx
  • app/lib/database/model/Room.js
  • app/lib/database/model/Subscription.js
  • app/lib/database/model/Message.js
  • app/lib/database/model/ThreadMessage.js
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/lib/database/model/User.js
  • app/lib/database/model/Thread.js
  • app/lib/database/model/servers/User.js
  • app/lib/database/model/Permission.js
  • app/lib/database/model/servers/Server.js
  • app/lib/database/model/Setting.js
  • app/lib/database/model/CustomEmoji.js
  • app/lib/encryption/room.ts
  • app/containers/ReactionsList/index.tsx
  • app/lib/database/model/Room.js
  • app/lib/database/model/Subscription.js
  • app/lib/database/model/Message.js
  • app/lib/database/model/ThreadMessage.js
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/database/model/User.js
  • app/lib/database/model/Thread.js
  • app/lib/database/model/servers/User.js
  • app/lib/database/model/Permission.js
  • app/lib/database/model/servers/Server.js
  • app/lib/database/model/Setting.js
  • app/lib/database/model/CustomEmoji.js
  • app/lib/encryption/room.ts
  • app/containers/ReactionsList/index.tsx
  • app/lib/database/model/Room.js
  • app/lib/database/model/Subscription.js
  • app/lib/database/model/Message.js
  • app/lib/database/model/ThreadMessage.js
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/lib/encryption/room.ts
  • app/containers/ReactionsList/index.tsx
app/lib/encryption/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place end-to-end encryption implementation in 'app/lib/encryption/' directory using @rocket.chat/mobile-crypto

Files:

  • app/lib/encryption/room.ts
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components in 'app/containers/' directory

Files:

  • app/containers/ReactionsList/index.tsx
🧠 Learnings (2)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/encryption/room.ts
  • app/containers/ReactionsList/index.tsx
📚 Learning: 2026-06-25T18:37:44.793Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.tsx:101-141
Timestamp: 2026-06-25T18:37:44.793Z
Learning: In the Rocket.Chat React Native codebase, do not treat passing an `async` function directly to an event prop in React/React Native UI components (e.g., `onPress={async () => ...}` in TSX) as a “floating promises” CI-blocking lint issue—this repo does not enable the ESLint `no-floating-promises` rule (while `no-void` is enforced). Only raise robustness follow-ups when there are genuinely unhandled promise paths (e.g., fire-and-forget calls like `save()` that return a Promise that is neither awaited nor handled), and prefer making sure failure paths are explicitly handled/reported rather than blocking on lint-style floating-promise concerns.

Applied to files:

  • app/containers/ReactionsList/index.tsx
🔇 Additional comments (14)
app/containers/ReactionsList/index.tsx (1)

33-33: LGTM!

app/lib/encryption/room.ts (1)

731-732: LGTM!

Also applies to: 742-742

app/lib/database/model/Thread.js (1)

21-77: Same memoization semantics apply here (incl. attachments and reactions); the in-place mutation verification flagged on Message.js covers this model too.

app/lib/database/model/ThreadMessage.js (1)

21-81: Same memoized-field mutation concern as Message.js; covered by that verification.

app/lib/database/model/User.js (1)

19-19: LGTM!

app/lib/database/model/servers/Server.js (1)

38-38: LGTM!

app/lib/database/model/servers/User.js (1)

23-23: LGTM!

app/lib/database/model/Message.js (1)

21-87: 🩺 Stability & Availability

Consumers replace these memoized fields rather than mutating them in place.

app/lib/database/model/Subscription.js (2)

40-162: Same memoization/in-place-mutation concern as raised on Room.js applies to these Subscription fields (roles, tunread*, muted/unmuted/ignored, uids, usernames, tags, etc.). See that comment; the verification script already covers them.


164-236: LGTM!

app/lib/database/model/CustomEmoji.js (1)

13-13: LGTM!

app/lib/database/model/Permission.js (1)

11-11: LGTM!

app/lib/database/model/Setting.js (1)

17-17: LGTM!

app/lib/database/model/Room.js (1)

11-29: 🩺 Stability & Availability

No issue here The Room JSON fields are only read or copied into new payload objects; this change does not introduce an in-place mutation risk.

			> Likely an incorrect or invalid review comment.

Walkthrough

The PR adds { memo: true } to many JSON-backed WatermelonDB decorators across several models. It also changes reactions sorting to use a copied array and updates quote attachment handling to append with a new array.

Changes

Memoized JSON decorators

Layer / File(s) Summary
Message and thread fields
app/lib/database/model/Message.js, app/lib/database/model/Thread.js, app/lib/database/model/ThreadMessage.js
@json sanitizer calls for message-family fields now pass { memo: true }.
Room and subscription fields
app/lib/database/model/Room.js, app/lib/database/model/Subscription.js
@json decorators in room and subscription models now pass { memo: true } across the updated fields.
Other model fields
app/lib/database/model/CustomEmoji.js, app/lib/database/model/Permission.js, app/lib/database/model/Setting.js, app/lib/database/model/User.js, app/lib/database/model/servers/Server.js, app/lib/database/model/servers/User.js
Single-field @json decorators in these models now pass { memo: true }.

Reaction sorting

Layer / File(s) Summary
Copy reactions before sorting
app/containers/ReactionsList/index.tsx
sortedReactions now sorts a shallow copy of reactions instead of mutating the input array.

Quote attachment append

Layer / File(s) Summary
Append quote attachments immutably
app/lib/encryption/room.ts
decryptQuoteAttachment now builds a new attachments array before returning from the local-db and API quote paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: chore

Suggested reviewers

  • OtavioStasiak
🚥 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 clearly and concisely describes the main change: memoizing WatermelonDB @json model fields for performance.
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.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • NATIVE-1346: Request failed with status code 401

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.

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.74.0.109231

@diegolmello diegolmello mentioned this pull request Jun 30, 2026
10 tasks
@diegolmello

Copy link
Copy Markdown
Member Author

Closing in favor of #7455, which carries these commits as part of the NATIVE-22 message-hooks work (the useMessage snapshot hook builds directly on this @JSON memoization).

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.

1 participant