Skip to content

fix(MessageComposer): prevent draft creation/clearing on disconnected channel#3249

Open
arnautov-anton wants to merge 1 commit into
masterfrom
composer-provider-adjustments
Open

fix(MessageComposer): prevent draft creation/clearing on disconnected channel#3249
arnautov-anton wants to merge 1 commit into
masterfrom
composer-provider-adjustments

Conversation

@arnautov-anton

@arnautov-anton arnautov-anton commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🎯 Goal

Fixes: #3248
Closes: REACT-1046

As a side feauture, adds preventClearingOnUnmount prop.

Summary by CodeRabbit

  • New Features

    • Added an option to preserve message composer state when the composer is removed from the screen.
    • Improved draft handling during composer cleanup, including support for configurations where drafts are disabled.
  • Bug Fixes

    • Prevented unnecessary cleanup when the messaging channel is disconnected.

@arnautov-anton
arnautov-anton force-pushed the composer-provider-adjustments branch from 58afcb4 to def2530 Compare July 23, 2026 09:56
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

πŸ“ Walkthrough

Walkthrough

The message composer adds an option to preserve controller state on unmount. Its cleanup now handles disconnected channels, conditionally creates drafts, and includes the new option in effect dependencies.

Changes

Message composer cleanup

Layer / File(s) Summary
Guard and configure unmount cleanup
src/components/MessageComposer/MessageComposer.tsx
Adds the preventClearingOnUnmount prop and updates provider cleanup to guard disconnected channels, create drafts only when enabled, optionally skip clearing, and track the new prop.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: oliverlaz, martincupela

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes the goal and issue links, but omits the required implementation details and UI Changes sections. Add a brief implementation summary and either screenshots or an explicit note that there are no UI changes.
βœ… Passed checks (4 passed)
Check name Status Explanation
Linked Issues check βœ… Passed The cleanup now guards disconnected channels and avoids clear/draft work that would require an active channel, matching #3248.
Out of Scope Changes check βœ… Passed The new prop and cleanup-condition changes are directly related to the disconnected-channel fix; no unrelated changes are evident.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check βœ… Passed The title accurately summarizes the main change: preventing draft creation/clearing on disconnected channels in MessageComposer.
✨ 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 composer-provider-adjustments

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

Size Change: +126 B (+0.01%)

Total Size: 881 kB

πŸ“¦ View Changed
Filename Size Change
dist/cjs/index.js 293 kB +64 B (+0.02%)
dist/es/index.mjs 290 kB +62 B (+0.02%)
ℹ️ View Unchanged
Filename Size
dist/cjs/audioProcessing.js 1.74 kB
dist/cjs/channel-detail.js 23.1 kB
dist/cjs/emojis.js 2.56 kB
dist/cjs/mp3-encoder.js 814 B
dist/cjs/ReactPlayerWrapper.js 547 B
dist/cjs/useChannelHeaderOnlineStatus.js 41.1 kB
dist/cjs/useMessageComposerController.js 1.01 kB
dist/cjs/useNotificationApi.js 57.5 kB
dist/css/channel-detail.css 2.84 kB
dist/css/emoji-picker.css 178 B
dist/css/emoji-replacement.css 456 B
dist/css/index.css 41.4 kB
dist/es/audioProcessing.mjs 1.65 kB
dist/es/channel-detail.mjs 22.7 kB
dist/es/emojis.mjs 2.48 kB
dist/es/mp3-encoder.mjs 768 B
dist/es/ReactPlayerWrapper.mjs 485 B
dist/es/useChannelHeaderOnlineStatus.mjs 40.6 kB
dist/es/useMessageComposerController.mjs 935 B
dist/es/useNotificationApi.mjs 56.1 kB

compressed-size-action

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 1 line in your changes missing coverage. Please review.
βœ… Project coverage is 85.18%. Comparing base (1068e79) to head (def2530).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/components/MessageComposer/MessageComposer.tsx 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3249      +/-   ##
==========================================
- Coverage   85.22%   85.18%   -0.04%     
==========================================
  Files         506      506              
  Lines       15795    15800       +5     
  Branches     5023     5027       +4     
==========================================
- Hits        13462    13460       -2     
- Misses       2333     2340       +7     

β˜” View full report in Codecov by Harness.
πŸ“’ Have feedback on the report? Share it here.

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

πŸ€– 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.

Inline comments:
In `@src/components/MessageComposer/MessageComposer.tsx`:
- Around line 82-85: Update the affected MessageComposer props guide to document
the public preventClearingOnUnmount prop, including that setting it to true
prevents clearing the established MessageComposerController state when the
component unmounts. Keep the existing inline API documentation unchanged.
- Line 102: Update the guard in the MessageComposer flow before accessing
messageComposer.channel.disconnected to also handle a missing channel,
preserving the existing early return for disconnected channels and preventing
dereferencing an absent channel.
- Around line 108-112: Update the effect cleanup around messageComposer so
changes to props.preventClearingOnUnmount do not trigger cleanup while the
component remains mounted. Store the latest prop value in a ref and read that
ref inside the cleanup, while limiting the effect dependency array to
messageComposer; preserve the existing unmount guard and promise.finally clear
behavior.
- Around line 104-110: Update the cleanup flow around
messageComposer.createDraft() so draft-creation rejections are explicitly
handled rather than becoming unhandled promises. Preserve the
preventClearingOnUnmount early return while attaching rejection handling to the
started promise, and ensure the promise returned by finally() in the normal
clearing path is also consumed or otherwise handled.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4aa86f9b-989b-450d-8875-a21196c7a8a5

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 0d1c643 and def2530.

πŸ“’ Files selected for processing (1)
  • src/components/MessageComposer/MessageComposer.tsx

Comment on lines +82 to +85
/**
* When set to `true` disables clearing established state of the MessageComposerController upon component unmount.
*/
preventClearingOnUnmount?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟠 Major | ⚑ Quick win

Document the new public prop in the guide page.

This changes the public MessageComposerProps API. Add preventClearingOnUnmount and its unmount semantics to the affected MessageComposer props guide; the inline comment alone is insufficient.

As per coding guidelines, public API changes must update inline docs and affected guide pages.

πŸ€– 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 `@src/components/MessageComposer/MessageComposer.tsx` around lines 82 - 85,
Update the affected MessageComposer props guide to document the public
preventClearingOnUnmount prop, including that setting it to true prevents
clearing the established MessageComposerController state when the component
unmounts. Keep the existing inline API documentation unchanged.

Source: Coding guidelines

useEffect(
() => () => {
messageComposer.createDraft().finally(() => messageComposer.clear());
if (messageComposer.channel.disconnected) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | πŸ”΄ Critical | ⚑ Quick win

Guard a missing channel before dereferencing it.

The same provider checks !messageComposer.channel at Line 119, so the channel can be absent. Accessing .disconnected first can throw during channel removal or unmount.

Proposed fix
-      if (messageComposer.channel.disconnected) return;
+      if (!messageComposer.channel || messageComposer.channel.disconnected) return;
πŸ“ 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
if (messageComposer.channel.disconnected) return;
if (!messageComposer.channel || messageComposer.channel.disconnected) return;
πŸ€– 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 `@src/components/MessageComposer/MessageComposer.tsx` at line 102, Update the
guard in the MessageComposer flow before accessing
messageComposer.channel.disconnected to also handle a missing channel,
preserving the existing early return for disconnected channels and preventing
dereferencing an absent channel.

Comment on lines +104 to +110
const promise = messageComposer.config.drafts.enabled
? messageComposer.createDraft()
: Promise.resolve();

if (props.preventClearingOnUnmount) return;

promise.finally(() => messageComposer.clear());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== find file =="
fd -a 'MessageComposer\.tsx$' . || true

echo "== outline =="
ast-grep outline src/components/MessageComposer/MessageComposer.tsx --view expanded || true

echo "== relevant lines =="
cat -n src/components/MessageComposer/MessageComposer.tsx | sed -n '70,130p'

echo "== search createDraft implementations/usages =="
rg -n "createDraft|preventClearingOnUnmount|clear\(" src -S

Repository: GetStream/stream-chat-react

Length of output: 5837


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate message composer controller files =="
rg -n "createDraft|useMessageComposerController|MessageComposerController|createDraft" src -S | head -200

echo "== files likely implementing controller =="
rg -l "createDraft|useMessageComposerController|MessageComposerController" src -S

Repository: GetStream/stream-chat-react

Length of output: 17001


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package references for stream-chat dependency specs =="
for f in package.json yarn.lock package-lock.json pnpm-lock.yaml; do
  [ -f "$f" ] && { echo "--- $f"; rg -n "(^stream-chat:|stream-chat@|/stream-chat@|stream-chat-react )" "$f" | head -80; }
done

echo "== inspect MessageComposer hook implementation =="
cat -n src/components/MessageComposer/hooks/useMessageComposerController.ts

Repository: GetStream/stream-chat-react

Length of output: 240


🌐 Web query:

stream-chat JavaScript MessageComposer createDraft disconnected channel draft implementation

πŸ’‘ Result:

When using the Stream Chat MessageComposer in a JavaScript or React environment, implementing draft persistenceβ€”especially when a user navigates away or disconnectsβ€”requires explicit handling because createDraft() is an asynchronous operation that must be triggered by the application [1][2]. Draft Implementation Strategy: 1. Enable Drafts: Drafts are disabled by default. You must enable them in your MessageComposer configuration [3][2]. ts // Example configuration const composer = new MessageComposer({ client, compositionContext: channel, config: { drafts: { enabled: true } }, }); 2. Triggering Draft Creation: The MessageComposer does not automatically save drafts when the component unmounts or the network disconnects [2]. You must call messageComposer.createDraft() explicitly [1][4]. - For React/React Native, this is commonly done using lifecycle hooks or navigation listeners (e.g., beforeRemove in react-navigation) to capture the user's intent to leave the channel [2]. - If the client is disconnected, asynchronous network calls like createDraft() may fail or be queued. It is recommended to check the connection status before attempting operations or ensure your application handles the promise rejection gracefully [5]. 3. Handling Disconnection: Calling methods on a channel or composer instance while the client is disconnected can lead to errors [5]. If you are implementing a cleanup effect, ensure you verify that the client or channel instance is valid before execution: ts // Example of safe cleanup const handleUnmount = async () => { if (chatClient.wsConnection.isAlive) { await messageComposer.createDraft(); } }; Key API Notes: - createDraft(): Extracts the current composition state (text, attachments, etc.) and pushes it to the server [1][4]. - State Management: The composer automatically listens for draft.updated and draft.deleted WebSocket events to synchronize local state when the client is connected [1][6][7]. - Middleware: If you need to customize what is saved in the draft (e.g., stripping specific data), you can use the draftCompositionMiddlewareExecutor to insert custom logic into the composition pipeline [8]. For complex applications, use the useMessageComposerController() hook to access the current MessageComposer instance directly within your functional components to trigger these saves [9].

Citations:


🌐 Web query:

github stream-chat Javascript MessageComposer createDraft fetch draft source

πŸ’‘ Result:

In the Stream Chat JavaScript/React SDKs, the MessageComposer class acts as the central orchestrator for message composition, including the management of drafts [1][2]. Regarding the createDraft and fetch (getDraft) functionality within the MessageComposer: 1. Create Draft: You can create a draft using the composer's createDraft method [1][3]. This method does not require parameters as it automatically extracts data from the composer's internal state and configured draft composition middleware [1][3]. It typically performs an asynchronous operation to save the draft to the server [1][3]. 2. Fetch (Get) Draft: You can retrieve a draft using the composer's getDraft method [1][3]. This method initiates the process of fetching an existing draft for the channel or thread associated with the composer instance and initializes the composer's state with the retrieved data [4]. Internally, it may first attempt to retrieve a draft from a local database (if configured) before fetching the latest version from the server via the underlying channel's getDraft method [4]. Key Implementation Details: - Enabling Drafts: Drafts must be enabled in the MessageComposer configuration (e.g., config: { drafts: { enabled: true } }) [1][5]. - Automatic State Handling: The MessageComposer is designed to automatically handle local state updates in response to draft-related events (such as draft.updated or draft.deleted), reducing the need for manual synchronization in most use cases [1][3]. - Architecture: The MessageComposer uses middleware systems to handle various aspects of composition, including drafts [6][7]. The core logic is centralized within the MessageComposer class, which coordinates with the channel or thread context [1][2]. For developers building custom UI, the MessageComposer instance can be accessed via the useMessageComposer hook in React, which provides access to these draft management methods [8][9].

Citations:


🌐 Web query:

github stream-chat Javascript draft messages createDraft source

πŸ’‘ Result:

In the Stream Chat JavaScript SDK, you can create a draft message for a channel or thread using the createDraft method on the channel instance [1][2]. Only one draft per channel or thread can exist at a time; creating a new one will override the previous draft [1][3]. To create or update a draft, pass a DraftMessagePayload object to the method [2]: const draft = await channel.createDraft({ text: "this is a draft message", }); For thread-specific drafts, include the parent_id in the payload [1]: const draft = await channel.createDraft({ text: "this is a draft message", parent_id: parentMessageId, }); The createDraft method automatically handles queuing if offline support is enabled, ensuring that the draft creation is executed once connectivity is restored [2]. If you are using the React SDK's MessageComposer component, it provides a higher-level API for draft management [4][5]. The composer manages draft state internally and can be used as follows: // Create a draft await composer.createDraft; // Delete a draft await composer.deleteDraft; Additionally, the SDK allows you to query all draft messages for the current user using the client's queryDrafts method [6][3]: const response = await client.queryDrafts({ filter: { channel_cid: { $in: ["channel-1", "channel-2"] } }, sort: [{ field: "created_at", direction: -1 }], }); The SDK supports WebSocket events such as draft.updated and draft.deleted to keep your application state in sync [6][3].

Citations:


🌐 Web query:

stream-chat-js MessageComposer createDraft source channel.sendDraft

πŸ’‘ Result:

In the Stream Chat SDK, draft management is handled differently depending on whether you are using the higher-level MessageComposer UI component or the lower-level Channel API. MessageComposer (UI SDKs) The MessageComposer component provides a high-level API to manage drafts within your UI [1][2]. You can enable draft functionality through the MessageComposer configuration [1][3]. The MessageComposer exposes the following methods to manage these drafts directly [1][4]: await composer.createDraft; await composer.deleteDraft; await composer.getDraft; These methods do not require parameters, as the MessageComposer automatically extracts the necessary data from its internal state and draft composition middleware [1][3]. It also automatically handles local state updates in response to draft.updated and draft.deleted events [1][4]. Channel API (Core JS SDK) If you are interacting directly with the Channel object, you use the createDraft method provided by the channel instance [5][6]. This method creates or updates a draft message for a channel or a thread [5][7]. To create or update a draft using the channel object, pass a payload containing the message details [5][6]: await channel.createDraft({ text: "this is a draft message", parent_id: parentMessageId // optional, for threads }); Key points regarding the Channel API: - Only one draft per channel or thread can exist at a time; creating a new one overrides the existing draft [5][7]. - The method includes logic to handle offline support, queuing the task if it fails due to connectivity issues [6][8]. - You can also manage drafts using channel.getDraft and channel.deleteDraft [6][8].

Citations:


🌐 Web query:

stream-chat-js draft sendDraft source

πŸ’‘ Result:

There is no method named sendDraft in the Stream Chat JavaScript SDK. The draft functionality in stream-chat-js is managed through creation, retrieval, and deletion methods on the channel object, and there is no direct "send" operation for a draft [1][2][3]. To handle draft messages, you typically use the following methods on a channel instance: 1. Create or Update a Draft: Use channel.createDraft(message). This creates or updates a draft message for that channel (or thread, if a parent_id is provided) [1][3]. 2. Retrieve a Draft: Use channel.getDraft({ parent_id }). This fetches the current draft for the channel or specified thread [1][3]. 3. Delete a Draft: Use channel.deleteDraft({ parent_id }). This removes the draft from the channel or thread [3]. If you are using the Stream Chat React SDK, the MessageComposer component provides a high-level API for managing drafts, such as composer.createDraft, composer.deleteDraft, and composer.getDraft [4][5]. The composer can also call a compose or composeDraft method to process the message, but it does not use a method named sendDraft [4][5]. To send a draft, you would typically retrieve the draft content and then call the standard channel.sendMessage or composer.compose methods [1][4][6].

Citations:


🌐 Web query:

jsdelivr stream-chat 9.38.0 src messageComposer messageComposer.ts createDraft

πŸ’‘ Result:

In the stream-chat JavaScript SDK version 9.38.0, the createDraft method within the MessageComposer class is responsible for persisting a draft message to the server (and potentially an offline database) [1]. The createDraft function in src/messageComposer/messageComposer.ts performs the following logical steps [1]: 1. Early Returns: It checks if the message is currently being edited (this.editedMessage) or if the draft feature is disabled (!this.config.drafts.enabled). If either is true, the method returns early [1]. 2. Compose Draft: It calls this.composeDraft(), which triggers the draftCompositionMiddlewareExecutor to generate the draft state [1]. 3. State Update: If a valid composition is returned, it updates the local state with the new draft ID using this.state.partialNext({ draftId: draft.id }) [1]. 4. Offline DB Support: If an offline database is configured (this.client.offlineDb), it attempts to optimistically upsert the draft record (including metadata like channel_cid, created_at, parent_id, and quoted_message) into the local storage [1][2]. 5. Logging and Sync: It logs the update timestamp and finally calls this.channel.createDraft(draft) to synchronize the draft with the Stream Chat backend [1]. Unlike channel.createDraft(message), which accepts a DraftMessagePayload as an argument [3], the MessageComposer.createDraft() method requires no arguments [4][5]. It internally extracts the necessary composition data from its own state and middleware pipeline [4][5]. This method is typically invoked when a user navigates away from a channel or thread to ensure their progress is saved [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== fetch stream-chat 9.38.0 MessageComposer createDraft source =="
python3 - <<'PY'
import json, urllib.request
url="https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/messageComposer/messageComposer.ts"
try:
    resp=urllib.request.urlopen(url, timeout=20)
    data=resp.read().decode("utf-8")
    lines=data.splitlines()
    for i,l in enumerate(lines,start=1):
        if "createDraft" in l:
            start=max(1,i-15); end=min(len(lines),i+80)
            print(f"--- {i} ---")
            for n in range(start,end+1):
                print(f"{n:5}: {lines[n-1]}")
            print()
except Exception as e:
    print(f"ERROR: {e}")
    exit(2)
PY

echo "== fetch stream-chat 9.38.0 channel.ts draft createDraft source =="
python3 - <<'PY'
import urllib.request
url="https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/channel.ts"
try:
    resp=urllib.request.urlopen(url, timeout=20)
    data=resp.read().decode("utf-8")
    lines=data.splitlines()
    for i,l in enumerate(lines,start=1):
        if "createDraft" in l or "deleteDraft" in l or "getDraft" in l:
            start=max(1,i-5); end=min(len(lines),i+45)
            print(f"--- {i} ---")
            for n in range(start,end+1):
                s=lines[n-1]
                if n==i or "offlineDb" in s or "shouldQueueTask" in s or "executeTask" in s or "channel.update" in s or "return" in s or "catch" in s or "try" in s:
                    print(f"{n:5}: {s}")
            print()
except Exception as e:
    print(f"ERROR: {e}")
    exit(2)
PY

Repository: GetStream/stream-chat-react

Length of output: 373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ssl, urllib.request, urllib.error
ctx=ssl._create_unverified_context() if hasattr(ssl, '_create_unverified_context') else None
for url in [
    "https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/messageComposer/messageComposer.ts",
    "https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/channel.ts",
]:
    try:
        req=urllib.request.Request(url, headers={"User-Agent":"curl"})
        data=urllib.request.urlopen(req, context=ctx, timeout=20).read().decode("utf-8")
        lines=data.splitlines()
        print(f"--- SOURCE {url}")
        for i,l in enumerate(lines,start=1):
            if "createDraft" in l or "deleteDraft" in l or "getDraft" in l:
                start=max(1,i-8); end=min(len(lines),i+55)
                print(f"--- TOKEN {i} ---")
                for n in range(start,end+1):
                    s=lines[n-1]
                    if n==i or any(k in s for k in ["offlineDb","shouldQueueTask","executeTask","channel.update","return","catch","try","composeDraft","draftId","draft"]):
                        print(f"{n:5}: {s}")
                print()
    except urllib.error.HTTPError as e:
        print(f"HTTP {e.code} {url}")
    except Exception as e:
        print(f"ERROR {url}: {e}")
PY

Repository: GetStream/stream-chat-react

Length of output: 28311


Handle rejected draft creation during cleanup.

If the channel disconnects after createDraft() is started, the network request can reject. The preventClearingOnUnmount path currently leaves that rejection unhandled, and the promise from finally() is ignored in the normal cleanup path too.

Proposed fix
-      if (props.preventClearingOnUnmount) return;
-      promise.finally(() => messageComposer.clear());
+      if (props.preventClearingOnUnmount) {
+        void promise.catch(console.error);
+        return;
+      }
+      void promise
+        .catch(console.error)
+        .finally(() => messageComposer.clear());
πŸ“ 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
const promise = messageComposer.config.drafts.enabled
? messageComposer.createDraft()
: Promise.resolve();
if (props.preventClearingOnUnmount) return;
promise.finally(() => messageComposer.clear());
const promise = messageComposer.config.drafts.enabled
? messageComposer.createDraft()
: Promise.resolve();
if (props.preventClearingOnUnmount) {
void promise.catch(console.error);
return;
}
void promise
.catch(console.error)
.finally(() => messageComposer.clear());
πŸ€– 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 `@src/components/MessageComposer/MessageComposer.tsx` around lines 104 - 110,
Update the cleanup flow around messageComposer.createDraft() so draft-creation
rejections are explicitly handled rather than becoming unhandled promises.
Preserve the preventClearingOnUnmount early return while attaching rejection
handling to the started promise, and ensure the promise returned by finally() in
the normal clearing path is also consumed or otherwise handled.

Comment on lines +108 to +112
if (props.preventClearingOnUnmount) return;

promise.finally(() => messageComposer.clear());
},
[messageComposer],
[messageComposer, props.preventClearingOnUnmount],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate MessageComposer files"
fd -a 'MessageComposer\.tsx$' . || true

echo
echo "Relevant snippet and nearby effect code"
if [ -f src/components/MessageComposer/MessageComposer.tsx ]; then
  wc -l src/components/MessageComposer/MessageComposer.tsx
  sed -n '1,170p' src/components/MessageComposer/MessageComposer.tsx | cat -n
fi

Repository: GetStream/stream-chat-react

Length of output: 8005


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect controller draft/create/clear definitions"
rg -n "createDraft|clear\\(|drafts|state" src/components/MessageComposer -S
echo
echo "Inspect controller hooks"
fd -a 'Hooks?|Controller|.*Controller.*|.*Controller.*' src/components/MessageComposer | sed 's#^`#/`#' | head -50
echo
fd -a 'Hooks?|.*Controller.*' src/components/MessageComposer | while read -r f; do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,260p' "$f" | cat -n
done

echo
echo "Check package React version type/imports are safe for useRef"
if [ -f package.json ]; then
  node -e "const p=require('./package.json'); console.log(JSON.stringify({react:p.dependencies?.react ?? p.devDependencies?.react}, null, 2))"
fi
rg "react|react-dom" package.json yarn.lock 2>/dev/null | head -40 || true

Repository: GetStream/stream-chat-react

Length of output: 29499


Avoid running unmount cleanup while re-running this effect.

props.preventClearingOnUnmount is a dependency, so changing it from false to true runs the existing cleanup before the new effect. That cleanup can call createDraft() and then clear() while the component is still mounted, dropping the current composer state. Key this only on messageComposer and read the latest prop from a ref.

πŸ€– 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 `@src/components/MessageComposer/MessageComposer.tsx` around lines 108 - 112,
Update the effect cleanup around messageComposer so changes to
props.preventClearingOnUnmount do not trigger cleanup while the component
remains mounted. Store the latest prop value in a ref and read that ref inside
the cleanup, while limiting the effect dependency array to messageComposer;
preserve the existing unmount guard and promise.finally clear behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants