Skip to content

feat(lint): integrate eslint-plugin-react-hooks with rules-of-hooks and exhaustive-deps as errors#10809

Open
mmorel-35 wants to merge 1 commit intoswagger-api:masterfrom
mmorel-35:eslint-plugin-react-hooks
Open

feat(lint): integrate eslint-plugin-react-hooks with rules-of-hooks and exhaustive-deps as errors#10809
mmorel-35 wants to merge 1 commit intoswagger-api:masterfrom
mmorel-35:eslint-plugin-react-hooks

Conversation

@mmorel-35
Copy link
Copy Markdown

Adds eslint-plugin-react-hooks@7.0.1 and enforces both rules-of-hooks and exhaustive-deps as errors across the entire codebase, eliminating pre-existing hook violations before activating the rules.

ESLint configuration

  • Added react-hooks plugin to .eslintrc.js with both rules at error level (2)
  • Did not use plugin:react-hooks/recommended — v7 bundles additional rules (refs, static-components, set-state-in-effect) incompatible with the codebase's plugin-system patterns

Phase 1 — rules-of-hooks fixes

Pre-existing violations corrected before enabling the rule:

File Issue Fix
hooks.js (useRenderedSchemas) useContext called inside an if branch — hook order not guaranteed Unconditional call moved before the guard
All $-prefixed keyword components Component function names not PascalCase — hooks inside not recognized by the rule All nine functions renamed to PascalCase; all consuming usages updated

The useRenderedSchemas fix is the most impactful: a conditional useContext can silently cause diverging hook call order between renders.

// Before — conditional hook (violates rules-of-hooks)
export const useRenderedSchemas = (schema = undefined) => {
  if (typeof schema === "undefined") {
    return useContext(JSONSchemaCyclesContext) // ← hook behind if-guard
  }
  const renderedSchemas = useContext(JSONSchemaCyclesContext)
  return new Set([...renderedSchemas, schema])
}

// After — unconditional
export const useRenderedSchemas = (schema = undefined) => {
  const renderedSchemas = useContext(JSONSchemaCyclesContext)
  if (typeof schema === "undefined") return renderedSchemas
  return new Set([...renderedSchemas, schema])
}

All nine $-prefixed keyword component function names have been renamed to PascalCase for consistency and correct rules-of-hooks analysis:

File Old name New name Import alias
$defs.jsx $defs Defs KeywordDefs
$vocabulary.jsx $vocabulary Vocabulary KeywordVocabulary
$ref.jsx $ref Ref KeywordRef
$dynamicRef.jsx $dynamicRef DynamicRef KeywordDynamicRef
$schema.jsx $schema Schema KeywordSchema
$anchor.jsx $anchor Anchor KeywordAnchor
$id.jsx $id Id KeywordId
$dynamicAnchor.jsx $dynamicAnchor DynamicAnchor KeywordDynamicAnchor
$comment.jsx $comment Comment KeywordComment

All renames are fully propagated to consuming files (hoc.jsx, index.js, JSONSchema.jsx): import aliases, local variable names, and JSX element names. Component registration/lookup string keys (e.g. "Keyword$ref", "JSONSchema202012Keyword$ref") are preserved for backward compatibility.

Phase 2+3 — exhaustive-deps fixes and suppressions

Each violation resolved either by correcting deps or suppressing with a documented comment:

  • servers.jsx — second useEffect gets missing Redux action deps (setSelectedServer, setServerVariableValue); mount-only first effect suppressed
  • models.jsxschemasPath hoisted as Object.freeze(["components", "schemas"]) module constant; missing layoutActions/schemasPath added to both useCallback deps; useEffect suppressed (intentionally watches only isOpen/defaultModelsExpandDepth to avoid over-firing)
  • hooks.js (useIsExpanded) — useEffect and two useCallbacks suppressed: pathMutator is recreated every render; including it would cause infinite update loops or defeat the purpose of useCallback
  • model-example.jsxtabs.example added to useEffect dep array (trivial: tabs is stable via useMemo([], []))
  • flavors/swagger-ui-react/index.jsx — three effects suppressed: mount-only SwaggerUI initializer, and two effects using usePrevious values where adding those deps would cause double-firing

Motivation and Context

eslint-plugin-react-hooks catches two categories of bugs:

  • rules-of-hooks: hooks called conditionally or outside components/hooks → unpredictable render behavior
  • exhaustive-deps: stale closures in effects/callbacks → subtle data staleness bugs that are hard to reproduce

The useRenderedSchemas violation was a real latent bug. Renaming all $-prefixed component functions to PascalCase is required for the rule to correctly analyze hook calls inside those components, and ensures consistent naming across the entire keyword component set.

How Has This Been Tested?

npm run lint-errors (used in CI) passes with zero errors. All 829 existing unit tests pass.

Screenshots (if appropriate):

N/A

Checklist

My PR contains...

  • No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • Dependency changes (any modification to dependencies in package.json)
  • Bug fixes (non-breaking change which fixes an issue)
  • Improvements (misc. changes to existing features)
  • Features (non-breaking change which adds functionality)

My changes...

  • are breaking changes to a public API (config options, System API, major UI change, etc).
  • are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • are not breaking changes.

Documentation

  • My changes do not require a change to the project documentation.
  • My changes require a change to the project documentation.
  • If yes to above: I have updated the documentation accordingly.

Automated tests

  • My changes can not or do not need to be tested.
  • My changes can and should be tested by unit and/or integration tests.
  • If yes to above: I have added tests to cover my changes.
  • If yes to above: I have taken care to cover edge cases in my tests.
  • All new and existing tests passed.

…nd exhaustive-deps as errors

Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
@mmorel-35 mmorel-35 force-pushed the eslint-plugin-react-hooks branch from 3a5540a to 6eb087d Compare April 16, 2026 18:08
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.

1 participant