fix(k8s): resolve react-hooks/rules-of-hooks in KubeObject#5324
fix(k8s): resolve react-hooks/rules-of-hooks in KubeObject#5324ipsitapp8 wants to merge 2 commits intokubernetes-sigs:mainfrom
Conversation
This fix addresses the Storybook test timeout by preventing an infinite re-render loop in the ReleaseNotes component. The appConfig listener is now registered only once on mount, and the AbortController is handled via a Ref to avoid state-induced re-runs of the effect. Ensures Storybook tests can complete within the 5000ms timeout.
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: ipsitapp8 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
illume
left a comment
There was a problem hiding this comment.
Thanks for this PR.
A few of the commits don't quite follow the project guidelines. We use Linux kernel style for git commits — have a look at the contributing guide and previous commits with git log.
Commit guidelines
- Use atomic commits focused on a single change.
- Use the title format
<area>: <description of changes>. - Keep the title under 72 characters (soft requirement).
- Explain the intention and why the change is needed.
- Make commit titles meaningful and describe what changed.
- Do not add code that a later commit rewrites; squash or reorder commits instead.
- Do not include
Fixes #NNin commit messages.
Good examples:
frontend: HomeButton: Fix so it navigates to homebackend: config: Add enable-dynamic-clusters flag
There was a problem hiding this comment.
Pull request overview
This PR aims to eliminate react-hooks/rules-of-hooks violations in frontend/src/lib/k8s/KubeObject.ts by moving hook implementations into top-level exported hook functions while keeping the existing KubeObject.*use* static APIs as delegating wrappers.
Changes:
- Refactors
KubeObject.useApiList,KubeObject.useList, andKubeObject.useGetto delegate to new exported hooks (useKubeApiList,useKubeList,useKubeGet). - Introduces exported hook utilities in
KubeObject.ts(includinguseKubeApiGet). - Refactors desktop Release Notes fetching/abort flow to use an
AbortControllerstored in a ref.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| frontend/src/lib/k8s/KubeObject.ts | Extracts hook bodies into exported top-level hooks and keeps static use* methods as wrappers to address hooks linting. |
| frontend/src/components/common/ReleaseNotes/ReleaseNotes.tsx | Refactors update-check/release fetching logic to use a ref-held AbortController and simplifies effect dependencies. |
| React.useEffect(() => { | ||
| if (desktopApi) { | ||
| desktopApi.receive( | ||
| 'appConfig', | ||
| (config: { appVersion: string; checkForUpdates: boolean }) => { | ||
| const { appVersion: currentBuildAppVersion, checkForUpdates = true } = config; | ||
| if (!checkForUpdates) { | ||
| console.debug("Skipping update check because config's checkForUpdates is false"); | ||
| return; | ||
| if (!desktopApi) { | ||
| return; | ||
| } | ||
|
|
||
| desktopApi.receive('appConfig', (config: { appVersion: string; checkForUpdates: boolean }) => { | ||
| const { appVersion: currentBuildAppVersion, checkForUpdates = true } = config; |
There was a problem hiding this comment.
desktopApi.receive('appConfig', ...) registers a listener but the effect doesn’t remove it on unmount. Since desktopApi appears to support removeListener elsewhere, consider storing the handler function in a const and returning a cleanup that removes the listener (and aborts any in-flight fetch via controllerRef) to avoid leaks / setState-on-unmounted-component cases.
| export default function ReleaseNotes() { | ||
| const { desktopApi } = window; | ||
| const [releaseNotes, setReleaseNotes] = React.useState<string>(''); | ||
| const [releaseDownloadURL, setReleaseDownloadURL] = React.useState<string | null>(null); | ||
| const [fetchingRelease, setFetchingRelease] = React.useState<boolean>(false); | ||
| const [releaseFetchFailed, setReleaseFetchFailed] = React.useState<boolean>(false); | ||
| const [skipFetch, setSkipFetch] = React.useState(false); | ||
|
|
||
| // network controller this makes sure if the github request is still lying around we | ||
| // abort it on fetch release skip press button click | ||
| const controller = new AbortController(); | ||
| const signal = controller.signal; | ||
| const controllerRef = React.useRef<AbortController | null>(null); |
There was a problem hiding this comment.
The PR description is focused on fixing react-hooks/rules-of-hooks violations in KubeObject.ts, but this file has a non-trivial refactor of the GitHub release fetching/abort logic as well. Please either update the PR description to cover this behavior change or split it into a separate PR to keep scope focused.
| export function useKubeApiList<K extends KubeObject>( | ||
| kubeObjectClass: (new (...args: any) => K) & typeof KubeObject<any>, | ||
| onList: (...arg: any[]) => any, | ||
| onError?: (err: ApiError, cluster?: string) => void, | ||
| opts?: ApiListOptions |
There was a problem hiding this comment.
useKubeApiList is currently preceded by a JSDoc block that describes makeKubeObject (deprecated/params/returns). Because JSDoc attaches to the next symbol, this ends up documenting useKubeApiList incorrectly and leaves makeKubeObject undocumented. Please move that JSDoc down to makeKubeObject (or replace it with a correct doc for useKubeApiList).
| // We do the type conversion here because we want to be able to use hooks that may not have | ||
| // the exact signature as get callbacks. | ||
| const getCallback = onGet as (item: K) => void; | ||
| useConnectApi(kubeObjectClass.apiGet(getCallback, name, namespace, onError, opts)); | ||
| } |
There was a problem hiding this comment.
There is still a // eslint-disable-next-line react-hooks/rules-of-hooks in KubeObject.useApiGet (around line 378) calling useConnectApi(...), so the original lint violation isn’t fully resolved. Since useKubeApiGet is now introduced, please update static useApiGet to delegate to useKubeApiGet (and remove the suppression) the same way useApiList/useList/useGet were updated.
| * Also sets the following state whilst running: | ||
| * - fetchingRelease | ||
| * - releaseFetchFailed | ||
| * - skipFetch |
There was a problem hiding this comment.
This comment still lists skipFetch as managed state, but the skipFetch state was removed in this refactor. Please update the comment to reflect the actual state being set (or remove the bullet).
| * - skipFetch |
illume
left a comment
There was a problem hiding this comment.
Thanks for the contribution.
There are some open Copilot review comments — could you take a look at them?
The frontend lint job in CI is failing. Run cd frontend && npm run lint to see the errors.
How to fix lint errors
Run cd frontend && npm run lint to see all ESLint errors. Many can be fixed automatically with cd frontend && npm run lint -- --fix. Remaining errors need manual attention.
Fixes #5242
Description
This PR resolves the
react-hooks/rules-of-hooksESLint violation inKubeObject.tswhere React hooks were being called directly inside static class methods.To fix this without blowing up the diff or touching the 30+ files that rely on these methods across the codebase, this PR:
useApiList,useList,useGet, anduseApiGetout ofKubeObjectand into standalone, exported top-level functions (useKubeApiList,useKubeList, etc.).return useKubeList(this, ...args)).// eslint-disable-next-line react-hooks/rules-of-hookssuppressions.Why this approach?
PodListcan continue doingPod.useList()identically.eslint-plugin-react-hooksrule natively allows static method delegation to hooks as long as the method name starts withuse.ReturnTypeconstraints are strictly preserved.How to test
npm run lintand verify there are no longer anyreact-hooks/rules-of-hookserrors inKubeObject.ts.npm run tscto verify TypeScript builds cleanly.