Skip to content

[CLNP-8706] getchannel retry on reconnect#1441

Open
sf-tyler-jeong wants to merge 4 commits into
mainfrom
fix/CLNP-8706/getchannel-retry-on-reconnect
Open

[CLNP-8706] getchannel retry on reconnect#1441
sf-tyler-jeong wants to merge 4 commits into
mainfrom
fix/CLNP-8706/getchannel-retry-on-reconnect

Conversation

@sf-tyler-jeong

Copy link
Copy Markdown
Contributor

Summary

GroupChannelProvider fetches its channel via getChannel(channelUrl) in a
useAsyncEffect. If that fetch was cancelled or failed during an SDK
disconnect→reconnect cycle, currentChannel stayed null and the effect never
retried on a bare auto-reconnect (its deps didn't include connection state), so
the conversation view got stuck and useGroupChannelMessages logged
[useGroupChannelMessages] channel is required. The Desk ticket/channel still
existed server-side. Reported on @sendbird/uikit-react@3.16.9 /
@sendbird/chat@4.19.1; reproduced on current main.

Root cause

The channel-init effect's deps were [sdkStore.initialized, sdkStore.sdk, channelUrl],
none of which change on an auto-reconnect, so a failed getChannel was never retried.

Changes

src/modules/GroupChannel/context/GroupChannelProvider.tsx

  • Add config.isOnline to the effect deps so the fetch retries once the SDK
    reconnects (isOnline is driven by onReconnectSucceeded).
  • Skip the fetch when the channel is already loaded without error — avoids a
    redundant refetch on healthy reconnects and avoids nulling out a healthy
    channel when the connection drops.
  • Add a request-id guard so a stale getChannel resolve/reject can't clobber a
    newer fetch (the reconnect-driven re-runs make overlapping requests possible).

Tests

src/modules/GroupChannel/context/__tests__/GroupChannelProvider.reconnect.spec.tsx (new)

  1. failed fetch → isOnline false→true → retry succeeds → currentChannel recovers
  2. an already-loaded channel is not refetched across a disconnect/reconnect cycle
  3. a stale rejection arriving after a newer success is ignored (request-id guard)

Backward compatibility

No public API / prop / export / type / StringSet changes. Offline behavior with
local cache is unchanged (getChannel still serves from the local cache).

Notes

The ticket also suggested null-guarding useGroupChannelMessages instead of the
currentChannel! non-null assertion. That hook lives in @sendbird/uikit-tools
and is already runtime null-safe (init no-ops while the channel is null), so it
isn't required for recovery and is out of scope here.

sf-tyler-jeong and others added 2 commits July 3, 2026 15:30
GroupChannelProvider fetched the channel via getChannel(channelUrl) in a useAsyncEffect keyed only on [sdkStore.initialized, sdkStore.sdk, channelUrl]. When that fetch was cancelled or failed during an SDK disconnect->reconnect cycle, handleChannelError set currentChannel to null and nothing re-ran the effect on a bare auto-reconnect (none of those deps change), so the conversation stayed stuck on the invalid / 'channel is required' state until a manual remount.

Add config.isOnline to the effect's dependency array so the fetch retries once the SDK reconnects (config.isOnline is driven by onReconnectSucceeded), recovering a channel that previously stayed null. Skip the fetch when we already hold this channel loaded without error: this avoids a redundant refetch on healthy reconnects and avoids nulling out a healthy channel when the connection drops. Because isOnline in the deps can re-run the effect while a getChannel is still in flight, track a request id and ignore stale resolutions so a late resolve or reject cannot clobber a newer fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression tests for the reconnect-retry fix: (1) a getChannel that fails during a disconnect is retried and recovers currentChannel once config.isOnline flips false->true; (2) an already-loaded channel is not refetched across a disconnect/reconnect cycle; (3) a stale getChannel rejection arriving after a newer fetch has succeeded is ignored by the request-id guard and does not clobber the recovered channel. Uses stable sdk/config mock refs so only config.isOnline changes drive the effect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 3, 2026

Copy link
Copy Markdown

Deploy Preview for sendbird-uikit-react ready!

Name Link
🔨 Latest commit 429663f
🔍 Latest deploy log https://app.netlify.com/projects/sendbird-uikit-react/deploys/6a4b572d44ae2a000851d5ae
😎 Deploy Preview https://deploy-preview-1441--sendbird-uikit-react.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c29ba495e5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/modules/GroupChannel/context/GroupChannelProvider.tsx Outdated
Follow-up to the reconnect-retry guard. The skip-when-already-loaded guard also suppressed the refetch when SendbirdProvider replaces sdkStore.sdk for a new appId/userId session: GroupChannelProvider is keyed only by channelUrl, so a same-URL currentChannel from the previous SDK survived and the effect returned early, leaving the UI/message actions bound to the previous SDK's channel object. Track the SDK instance the channel was fetched with and only skip when it still matches sdkStore.sdk, so a new SDK instance triggers a refetch. Adds a regression test for the SDK-swap case. Addresses the Codex review comment on PR #1441.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sf-tyler-jeong

sf-tyler-jeong commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@chatgpt-codex-connector review again

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: d4ddefd870

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// failed during a disconnect would leave currentChannel === null with no retry.
const getChannelRequestIdRef = useRef(0);
const fetchedChannelSdkRef = useRef<unknown>(null);
useAsyncEffect(async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue에 대한 문제는 처리가 되는 것으로 보입니다.
다만, config.isOnline이 deps에 추가되는 시점에 useAsyncEffect()를 사용하는 것이 아닌, useEffect()를 사용하여 cleanUp을 활용하는 것이 어떨까 하는 생각입니다.

Suggested change
useAsyncEffect(async () => {
const fetchedChannelSdkRef = useRef<unknown>(null);
useEffect(() => {
if (!sdkStore.initialized || !channelUrl) return;
if (state.currentChannel?.url === channelUrl
&& !state.fetchedChannelError
&& fetchedChannelSdkRef.current === sdkstore.sdk ) return;
let candelled = false;
(async () => {
try {
const channel = await sdkStore.sdk.groupChannel.getChannel(channelUrl);
if (cancelled) return;
actions.setCurrentChannel(channel);
} catch(error) {
logger?.error?.('GroupChannelProvider: error when fetching channel', error);
if (cancelled) return;
actions.handleChannelError(error);
}
})();
return () => { cancelled = true; };
}, [sdkStore.initialized, sdkStore.sdk, channelUrl, config.isOnline]);

이런 형태를 생각해 봤습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

수정했습니다. 감사합니다!

Per review feedback on PR #1441 (danney-chun), replace the useAsyncEffect + request-id counter with a plain useEffect whose cleanup sets a 'cancelled' flag, so a superseded/in-flight getChannel cannot clobber a newer fetch (and it also no-ops after unmount). Behavior is unchanged; the SDK-instance skip-guard (fetchedChannelSdkRef) is kept. Drops the now-unused useAsyncEffect import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants