-
Notifications
You must be signed in to change notification settings - Fork 13.6k
fix: prevent thread sidebar from getting stuck #40048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -19,7 +19,6 @@ import { | |||||
| useUserId, | ||||||
| useRoomToolbox, | ||||||
| } from '@rocket.chat/ui-contexts'; | ||||||
| import { createPortal } from 'react-dom'; | ||||||
|
|
||||||
| import ThreadChat from './components/ThreadChat'; | ||||||
| import ThreadSkeleton from './components/ThreadSkeleton'; | ||||||
|
|
@@ -42,6 +41,13 @@ const Thread = ({ tmid }: ThreadProps) => { | |||||
| closeTab(); | ||||||
| }, | ||||||
| }); | ||||||
| if ( | ||||||
| mainMessageQueryResult.isSuccess && | ||||||
| (!mainMessageQueryResult.data || mainMessageQueryResult.data.t === 'rm') | ||||||
| ) { | ||||||
| closeTab(); | ||||||
| return null; | ||||||
| } | ||||||
|
Comment on lines
+44
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No, a React component cannot conditionally return before hooks that were called on a previous render. Hooks must be called at the top level of the component, in the same order on every render, before any early returns. A conditional early return before all hooks would violate the Rules of Hooks, causing React to lose track of hook state and leading to bugs or errors like "Rendered fewer hooks than expected." Side effects such as state updates (e.g., setState) or navigation during render are not allowed. Render must be a pure function that computes and returns JSX without mutating state, DOM, or performing I/O. Such side effects belong in event handlers or useEffect/useLayoutEffect. Workarounds: - For conditional logic before hooks: Split into an outer component that checks the condition and returns null or renders an inner component containing the hooks. - For side effects: Move them to useEffect (for post-render) or event handlers. Citations:
🏁 Script executed: head -100 apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx | tail -60Repository: RocketChat/Rocket.Chat Length of output: 1481 Move this deleted-message close path below the hook declarations. The conditional check at lines 44–50 short-circuits before Derive a 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| const t = useTranslation(); | ||||||
| const dispatchToastMessage = useToastMessageDispatch(); | ||||||
|
|
@@ -83,85 +89,64 @@ const Thread = ({ tmid }: ThreadProps) => { | |||||
| closeTab(); | ||||||
| }; | ||||||
|
|
||||||
| const isExpanded = canExpand && expanded; | ||||||
| const portalTarget = isExpanded ? document.getElementById('main-content') : null; | ||||||
|
|
||||||
| const threadContent = ( | ||||||
| <Contextualbar | ||||||
| rcx-thread-view | ||||||
| className={ | ||||||
| isExpanded | ||||||
| ? css` | ||||||
| max-width: 855px !important; | ||||||
| @media (min-width: 780px) and (max-width: 1135px) { | ||||||
| max-width: calc(100% - var(--sidebar-width)) !important; | ||||||
| } | ||||||
| ` | ||||||
| : undefined | ||||||
| } | ||||||
| position='absolute' | ||||||
| display='flex' | ||||||
| flexDirection='column' | ||||||
| width='full' | ||||||
| overflow='hidden' | ||||||
| zIndex={100} | ||||||
| insetBlock={0} | ||||||
| border='none' | ||||||
| > | ||||||
| <ContextualbarHeader> | ||||||
| <ContextualbarBack onClick={handleGoBack} /> | ||||||
| {(mainMessageQueryResult.isLoading && <Skeleton width='100%' />) || | ||||||
| (mainMessageQueryResult.isSuccess && <ThreadTitle mainMessage={mainMessageQueryResult.data} />) || | ||||||
| null} | ||||||
| <ContextualbarActions> | ||||||
| {canExpand && ( | ||||||
| <ContextualbarAction | ||||||
| name={expanded ? 'arrow-collapse' : 'arrow-expand'} | ||||||
| title={expanded ? t('Collapse') : t('Expand')} | ||||||
| onClick={handleToggleExpand} | ||||||
| /> | ||||||
| )} | ||||||
| <ContextualbarAction | ||||||
| name={following ? 'bell' : 'bell-off'} | ||||||
| title={following ? t('Following') : t('Not_Following')} | ||||||
| disabled={!mainMessageQueryResult.isSuccess || toggleFollowingMutation.isPending} | ||||||
| onClick={handleToggleFollowing} | ||||||
| /> | ||||||
| <ContextualbarClose onClick={handleClose} /> | ||||||
| </ContextualbarActions> | ||||||
| </ContextualbarHeader> | ||||||
|
|
||||||
| {(mainMessageQueryResult.isLoading && <ThreadSkeleton />) || | ||||||
| (mainMessageQueryResult.isSuccess && ( | ||||||
| <ChatProvider tmid={tmid}> | ||||||
| <ThreadChat mainMessage={mainMessageQueryResult.data} /> | ||||||
| </ChatProvider> | ||||||
| )) || | ||||||
| null} | ||||||
| </Contextualbar> | ||||||
| ); | ||||||
|
|
||||||
| return ( | ||||||
| <ContextualbarDialog> | ||||||
| <ContextualbarInnerContent> | ||||||
| {portalTarget ? ( | ||||||
| createPortal( | ||||||
| <> | ||||||
| <ModalBackdrop | ||||||
| className={css` | ||||||
| position: absolute !important; | ||||||
| `} | ||||||
| onClick={handleBackdropClick} | ||||||
| /> | ||||||
| {threadContent} | ||||||
| </>, | ||||||
| portalTarget, | ||||||
| ) | ||||||
| ) : ( | ||||||
| <Box flexGrow={1} position='relative'> | ||||||
| {threadContent} | ||||||
| </Box> | ||||||
| )} | ||||||
| {canExpand && expanded && <ModalBackdrop onClick={handleBackdropClick} />} | ||||||
| <Box flexGrow={1} position={expanded ? 'static' : 'relative'}> | ||||||
| <Contextualbar | ||||||
| rcx-thread-view | ||||||
| className={ | ||||||
| canExpand && expanded | ||||||
| ? css` | ||||||
| max-width: 855px !important; | ||||||
| @media (min-width: 780px) and (max-width: 1135px) { | ||||||
| max-width: calc(100% - var(--sidebar-width)) !important; | ||||||
| } | ||||||
| ` | ||||||
| : undefined | ||||||
| } | ||||||
| position={expanded ? 'fixed' : 'absolute'} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Gate fixed positioning with Prompt for AI agents
Suggested change
Comment on lines
+95
to
+109
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gate the fixed positioning with Line 95 uses 💡 Suggested fix const canExpand = useLayoutContextualBarExpanded();
const [expanded, setExpanded] = useLocalStorage('expand-threads', false);
+const isExpanded = canExpand && expanded;
...
- {canExpand && expanded && <ModalBackdrop onClick={handleBackdropClick} />}
- <Box flexGrow={1} position={expanded ? 'static' : 'relative'}>
+ {isExpanded && <ModalBackdrop onClick={handleBackdropClick} />}
+ <Box flexGrow={1} position={isExpanded ? 'static' : 'relative'}>
<Contextualbar
rcx-thread-view
className={
- canExpand && expanded
+ isExpanded
? css`
max-width: 855px !important;
`@media` (min-width: 780px) and (max-width: 1135px) {
max-width: calc(100% - var(--sidebar-width)) !important;
}
`
: undefined
}
- position={expanded ? 'fixed' : 'absolute'}
+ position={isExpanded ? 'fixed' : 'absolute'}
display='flex'🤖 Prompt for AI Agents |
||||||
| display='flex' | ||||||
| flexDirection='column' | ||||||
| width='full' | ||||||
| overflow='hidden' | ||||||
| zIndex={100} | ||||||
| insetBlock={0} | ||||||
| border='none' | ||||||
| > | ||||||
| <ContextualbarHeader> | ||||||
| <ContextualbarBack onClick={handleGoBack} /> | ||||||
| {(mainMessageQueryResult.isLoading && <Skeleton width='100%' />) || | ||||||
| (mainMessageQueryResult.isSuccess && <ThreadTitle mainMessage={mainMessageQueryResult.data} />) || | ||||||
| null} | ||||||
| <ContextualbarActions> | ||||||
| {canExpand && ( | ||||||
| <ContextualbarAction | ||||||
| name={expanded ? 'arrow-collapse' : 'arrow-expand'} | ||||||
| title={expanded ? t('Collapse') : t('Expand')} | ||||||
| onClick={handleToggleExpand} | ||||||
| /> | ||||||
| )} | ||||||
| <ContextualbarAction | ||||||
| name={following ? 'bell' : 'bell-off'} | ||||||
| title={following ? t('Following') : t('Not_Following')} | ||||||
| disabled={!mainMessageQueryResult.isSuccess || toggleFollowingMutation.isPending} | ||||||
| onClick={handleToggleFollowing} | ||||||
| /> | ||||||
| <ContextualbarClose onClick={handleClose} /> | ||||||
| </ContextualbarActions> | ||||||
| </ContextualbarHeader> | ||||||
|
|
||||||
| {(mainMessageQueryResult.isLoading && <ThreadSkeleton />) || | ||||||
| (mainMessageQueryResult.isSuccess && ( | ||||||
| <ChatProvider tmid={tmid}> | ||||||
| <ThreadChat mainMessage={mainMessageQueryResult.data} /> | ||||||
| </ChatProvider> | ||||||
| )) || | ||||||
| null} | ||||||
| </Contextualbar> | ||||||
| </Box> | ||||||
| </ContextualbarInnerContent> | ||||||
| </ContextualbarDialog> | ||||||
| ); | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Added conditional early return before later hooks breaks hook ordering and performs
closeTab()side effect during render.Prompt for AI agents