app-catalog: Fix early returns before hooks in EditorDialog components#665
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to fix React Rules of Hooks violations in the app-catalog EditorDialog components by removing early returns that occurred before hook calls.
Changes:
- Moves the
releasenull-guard inreleases/EditorDialog.tsxto after hook declarations and makes initial state creation null-safe. - Moves the
chartnull-guard incharts/EditorDialog.tsxto occur after some hooks (intended to be after all hooks).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| app-catalog/src/components/releases/EditorDialog.tsx | Moves the !release guard after hooks and makes initial state null-safe (but introduces state/effect safety issues when release is initially null). |
| app-catalog/src/components/charts/EditorDialog.tsx | Moves the !chart guard (but still leaves it before later useEffect hooks, so the hooks violation persists). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
d11f4a8 to
214296f
Compare
|
@illume Addressed all 3 Copilot comments in the latest commit:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
app-catalog/src/components/charts/EditorDialog.tsx:31
- Now that the
!chartguard is after hooks,Namespace.useList()(and related state/effects) will run even whenchartis undefined and the component returnsnull. That introduces an unnecessary cluster API query and extra work for a render that produces no UI. Consider a wrapper component that returns null when!chartand renders a child component containing the hooks whenchartis present.
const { openEditor, handleEditor, chart, chartProfile } = props;
const { t } = useTranslation();
const [installLoading, setInstallLoading] = useState(false);
const [namespaces, error] = K8s.ResourceClasses.Namespace.useList();
const [chartValues, setChartValues] = useState<string>('');
| useEffect(() => { | ||
| if (!release) return; | ||
| let isMounted = true; |
214296f to
e39f537
Compare
| useEffect(() => { | ||
| if (!release) return; | ||
| const merged = Object.assign({}, release.chart.values, release.config); | ||
| setValuesToShow(merged); | ||
| setValues(merged); | ||
| setUserValues(release.config); | ||
| }, [release]); |
| const [valuesToShow, setValuesToShow] = useState({}); | ||
| const [values, setValues] = useState({}); | ||
| const [userValues, setUserValues] = useState({}); |
| useEffect(() => { | ||
| if (!chart) return; | ||
| if (selectedVersion) { | ||
| handleChartValueFetch(chart); | ||
| } | ||
| }, [selectedVersion]); |
| } | ||
| }, [selectedVersion]); | ||
|
|
||
| if (!props.chart) return null; |
React's Rules of Hooks require hooks to be called unconditionally on every render. Both EditorDialog components had early returns before hook calls, violating this rule. releases/EditorDialog.tsx: moved 'if (!release) return null' guard to after all hooks and useEffects. State initializers made null-safe with empty object defaults. Added useEffect to sync state when release loads. Added null guard inside useEffect that dereferences release to prevent runtime errors. charts/EditorDialog.tsx: moved 'if (!props.chart) return null' guard to after all hooks and useEffects. Added null guards inside effects that dereference chart. Fixes headlamp-k8s#525 Signed-off-by: Ashutosh Sharma <itsashutosh769@gmail.com>
e39f537 to
f3767b7
Compare
|
Addressed all 4 Copilot comments in the latest commit (f3767b7):
|
| const [valuesToShow, setValuesToShow] = useState<Record<string, unknown>>({}); | ||
| const [values, setValues] = useState<Record<string, unknown>>({}); | ||
| const [userValues, setUserValues] = useState<Record<string, unknown>>({}); |
| const merged = Object.assign({}, release.chart.values, release.config); | ||
| setValuesToShow({ ...merged }); | ||
| setValues({ ...merged }); | ||
| setUserValues({ ...release.config }); |
| useEffect(() => { | ||
| if (!chart) return; | ||
| if (chartCfg.chartProfile === chartProfile) { | ||
| const versionsArray = | ||
| AVAILABLE_VERSIONS instanceof Map && AVAILABLE_VERSIONS.get && chart.name |
|
Thanks for the feedback. The current implementation is sufficient for the scope of this fix (moving guards after hooks). The lazy initializer suggestion adds complexity beyond what's needed here, and Object.assign({}, ...) already creates a new object so the spread is redundant but harmless. I'll leave these as potential follow-up improvements. The core Rules of Hooks violation is fixed. |
Fixes #525
Problem
Both
EditorDialogcomponents had early returns before hook calls,violating React's Rules of Hooks which require hooks to be called
unconditionally on every render.
releases/EditorDialog.tsx:if (!release) return nullat line 52,before 10+ hook calls
charts/EditorDialog.tsx:if (!props.chart) return nullat line 27,before all hook calls
Fix
Moved both guards to after all hooks. State initializers in
releases/EditorDialog.tsxmade null-safe with optional chaining(
release?.chart?.values,release?.config).Testing
npm run tsc,npm run lint, andnpm run formatall pass clean.