Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ReactEventHandler} from 'react';
import {useState} from 'react';
import {useMemo, useState} from 'react';
import {css} from '@emotion/react';
import styled from '@emotion/styled';

Expand Down Expand Up @@ -72,7 +72,11 @@
onDelete(screenshotAttachmentId);
}

const AttachmentComponent = getImageAttachmentRenderer(screenshot) ?? ImageViewer;
const AttachmentComponent = useMemo(

Check failure on line 75 in static/app/components/events/eventTagsAndScreenshot/screenshot/index.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render
() => getImageAttachmentRenderer(screenshot) ?? ImageViewer,
// eslint-disable-next-line react-hooks/exhaustive-deps
[screenshot.mimetype]
);

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.

Conditional useMemo breaks hooks

High Severity

The new useMemo for AttachmentComponent runs only after the if (!hasRole) return null guard, so renders without role skip that hook while renders with role call it. If hasRole changes on the same mount, React’s hook order no longer matches and the tree can crash.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 61841ab. Configure here.

const downloadUrl = `/api/0/projects/${organization.slug}/${projectSlug}/events/${eventId}/attachments/${screenshot.id}/`;

return (
Comment on lines +75 to 82

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.

Bug: The useMemo hook is called conditionally after an early return. This violates the Rules of Hooks and can cause a crash if the hasRole condition changes between renders.
Severity: LOW

Suggested Fix

Move the useMemo hook to be called unconditionally before the early return at line 61. Alternatively, restructure the component to avoid placing hooks after conditional guards.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
static/app/components/events/eventTagsAndScreenshot/screenshot/index.tsx#L75-L82

Potential issue: The `useMemo` hook at line 75 is called after a conditional early
return at line 61. The condition depends on the `hasRole` variable, which is derived
from the `useRole` hook. If the value of `hasRole` changes between renders for the same
component instance (e.g., from `true` to `false` due to an asynchronous permission
update), the number of hooks executed will differ. This violates the Rules of Hooks and
will cause React to throw an error, crashing the component. While the trigger condition
is rare in practice, the code structure is inherently unstable.

Did we get this right? 👍 / 👎 to inform future reviews.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ComponentProps} from 'react';
import {Fragment, useState} from 'react';
import {Fragment, useMemo, useState} from 'react';
import {css} from '@emotion/react';
import styled from '@emotion/styled';

Expand Down Expand Up @@ -97,8 +97,11 @@ export function ScreenshotModal({
};
}

const AttachmentComponent =
getImageAttachmentRenderer(currentEventAttachment) ?? ImageViewer;
const AttachmentComponent = useMemo(
() => getImageAttachmentRenderer(currentEventAttachment) ?? ImageViewer,
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentEventAttachment.mimetype]
);

return (
<Fragment>
Expand Down
Loading