fix(MessageComposer): prevent draft creation/clearing on disconnected channel#3249
fix(MessageComposer): prevent draft creation/clearing on disconnected channel#3249arnautov-anton wants to merge 1 commit into
Conversation
58afcb4 to
def2530
Compare
π WalkthroughWalkthroughThe 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. ChangesMessage composer cleanup
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: π₯ Pre-merge checks | β 4 | β 1β Failed checks (1 warning)
β Passed checks (4 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
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. Comment |
|
Size Change: +126 B (+0.01%) Total Size: 881 kB π¦ View Changed
βΉοΈ View Unchanged
|
Codecov Reportβ Patch coverage is
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. π New features to boost your workflow:
|
There was a problem hiding this comment.
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
π Files selected for processing (1)
src/components/MessageComposer/MessageComposer.tsx
| /** | ||
| * When set to `true` disables clearing established state of the MessageComposerController upon component unmount. | ||
| */ | ||
| preventClearingOnUnmount?: boolean; |
There was a problem hiding this comment.
π 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; |
There was a problem hiding this comment.
π©Ί 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.
| 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.
| const promise = messageComposer.config.drafts.enabled | ||
| ? messageComposer.createDraft() | ||
| : Promise.resolve(); | ||
|
|
||
| if (props.preventClearingOnUnmount) return; | ||
|
|
||
| promise.finally(() => messageComposer.clear()); |
There was a problem hiding this comment.
π©Ί 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 -SRepository: 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 -SRepository: 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.tsRepository: 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:
- 1: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-api/
- 2: https://getstream.io/chat/docs/sdk/react-native/v8/guides/draft-messages/
- 3: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer/
- 4: https://getstream.io/chat/docs/sdk/react-native/v8/ui-components/message-input/composer/message-composer-api/
- 5: bug: ChannelInner can call methods on a disconnected channel, causing uncaught errorsΒ #2393
- 6: https://getstream.io/chat/docs/javascript/drafts/
- 7: https://getstream.io/chat/docs/react/drafts/
- 8: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer-middleware/
- 9: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-hooks/
π 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:
- 1: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-api/
- 2: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-class/
- 3: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer-api/
- 4: https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/messageComposer/messageComposer.ts
- 5: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer/
- 6: feat: message composerΒ stream-chat-js#1495
- 7: feat: message composerΒ stream-chat-js#1495
- 8: feat: message composerΒ #2669
- 9: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-hooks/
π 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:
- 1: https://getstream.io/chat/docs/javascript/drafts/
- 2: https://github.com/GetStream/stream-chat-js/blob/master/src/channel.ts
- 3: https://getstream.io/chat/docs/node/drafts/
- 4: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-api/
- 5: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer-api/
- 6: https://getstream.io/chat/docs/react/drafts/
π 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:
- 1: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-api/
- 2: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-class/
- 3: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer-api/
- 4: https://getstream.io/chat/docs/sdk/react-native/v8/ui-components/message-input/composer/message-composer-api/
- 5: https://getstream.io/chat/docs/javascript/drafts/
- 6: https://github.com/GetStream/stream-chat-js/blob/master/src/channel.ts
- 7: https://getstream.io/chat/docs/node/drafts/
- 8: https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/dist/types/channel.d.ts
π 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:
- 1: https://getstream.io/chat/docs/javascript/drafts/
- 2: https://getstream.io/chat/docs/node/drafts/
- 3: https://github.com/GetStream/stream-chat-js/blob/master/src/channel.ts
- 4: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-api/
- 5: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer-api/
- 6: https://getstream.io/chat/docs/sdk/react/v13/components/message-input-components/message-composer/
π 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:
- 1: https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/messageComposer/messageComposer.ts
- 2: GetStream/stream-chat-js@570e431
- 3: https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/dist/types/channel.d.ts
- 4: https://getstream.io/chat/docs/sdk/react/components/message-composer/message-composer-api/
- 5: https://getstream.io/chat/docs/sdk/react-native/v8/ui-components/message-input/composer/message-composer-api/
- 6: https://getstream.io/chat/docs/sdk/react-native/v8/guides/draft-messages/
π 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)
PYRepository: 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}")
PYRepository: 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.
| 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.
| if (props.preventClearingOnUnmount) return; | ||
|
|
||
| promise.finally(() => messageComposer.clear()); | ||
| }, | ||
| [messageComposer], | ||
| [messageComposer, props.preventClearingOnUnmount], |
There was a problem hiding this comment.
π― 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
fiRepository: 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 || trueRepository: 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.
π― Goal
Fixes: #3248
Closes: REACT-1046
As a side feauture, adds
preventClearingOnUnmountprop.Summary by CodeRabbit
New Features
Bug Fixes