Skip to content

refactor(lint): enable stateful react-hooks v7 rules - #171

Merged
fx merged 2 commits into
mainfrom
refactor/0003-react-hooks-rules-stateful
Jul 18, 2026
Merged

refactor(lint): enable stateful react-hooks v7 rules#171
fx merged 2 commits into
mainfrom
refactor/0003-react-hooks-rules-stateful

Conversation

@fx

@fx fx commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Group B of docs/changes/0003-reenable-react-hooks-rules.md — the final PR for the change. Enables react-hooks/set-state-in-effect and react-hooks/preserve-manual-memoization at error; all five react-hooks v7 rules are now enforced with zero violations and zero suppressions added.

Post-merge audit found 5 set-state-in-effect violations (the 4 inventoried in the Group A PR, plus one introduced by Group A's own teardown restructure in useWebRTC); preserve-manual-memoization confirmed at 0.

Fixes (underlying patterns, no blanket suppressions)

  • app/utils/responsive.tsuseMediaQuery rewritten on useSyncExternalStore (no effect at all; re-syncs on query change via snapshot).
  • InputNumberCard.tsx / InputDateTimeCard.tsxlocalValue syncs from the entity during render with previous-value guards reproducing the old effect's triggers; in-progress edits preserved.
  • KeepAlive.tsx — cached portal element resolved during render (lazy init + render-time cache-key sync); the effect now only mounts/unmounts the element.
  • useWebRTC.ts — teardown moved to a dedicated enabled-keyed effect's cleanup return (setState in effect-cleanup is allowed); disable/unmount behavior unchanged, validated by the existing reactive-teardown test.
  • Stale exhaustive-deps sweep: only CardConfig.tsx:434 remains and is still load-bearing; left in place.

Completes change 0003

  • Change doc: Group B tasks ticked, Open Question resolved (all five rules at error, matching the zero-warning bar), Status → complete.
  • docs/index.yml / docs/index.md: 0003 → complete.
  • Architecture spec: lint scenario now states all five rules enforced; resolved Open Question removed.

Testing

  • npm test — 539 passed, 2 pre-existing skips (+8 new tests: responsive, KeepAlive, InputNumberCard, InputDateTimeCard; no existing tests modified)
  • npm run lint — 0 errors, 0 warnings
  • npm run typecheck — clean
  • npm run build:ha:prod — passes
  • Local Codex review clean; CodeRabbit gated by the PR-level check

Enable react-hooks/set-state-in-effect and react-hooks/preserve-manual-memoization
at 'error' (Group B of change 0003), completing the react-hooks v7 rollout — all
five rules now enforced. Fix all set-state-in-effect violations:

- app/utils/responsive.ts: useMediaQuery now subscribes via useSyncExternalStore
  instead of syncing state in an effect.
- InputNumberCard / InputDateTimeCard: sync localValue from the entity during
  render (previous-value guards) instead of in an effect; behavior preserved.
- KeepAlive: resolve the cached portal element during render (lazy init +
  render-time cacheKey sync); the effect only attaches/detaches it.
- useWebRTC: tear down on disable via an effect-cleanup return rather than a
  synchronous cleanup() in the effect body (this violation was introduced by
  Group A's setPeerConnection(null) in cleanup).

preserve-manual-memoization had zero violations (verified post-0001/0004). The
only exhaustive-deps suppression (CardConfig.tsx) remains load-bearing.

Add tests for useMediaQuery, KeepAlive, and the two input-card sync fixes; the
existing useWebRTC test covers the disable-teardown path. Finalize change 0003:
tick Group B + Open Question, flip status to complete in the doc and both
indexes, and sync the architecture spec lint section.
Copilot AI review requested due to automatic review settings July 18, 2026 08:40
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary

  • Enabled react-hooks/set-state-in-effect and react-hooks/preserve-manual-memoization at error, completing the React Hooks v7 lint rollout with zero violations or new suppressions.
  • Fixed five set-state-in-effect violations by:
    • Rewriting useMediaQuery to use useSyncExternalStore (instead of effect-driven match state).
    • Moving InputDateTimeCard/InputNumberCard entity→localValue synchronization into render-time guarded logic (removing syncing useEffects).
    • Resolving KeepAlive portal elements during render via cached portal element lookup by cache key, with effects only appending/removing the resolved element.
    • Moving useWebRTC teardown into effect cleanup (so cleanup runs on disable/unmount).
  • Added/extended regression coverage for media-query reactivity, input-card local value syncing behavior, and KeepAlive portal DOM reuse/movement.
  • Updated docs/index/architecture to mark change 0003 complete; lint, typecheck, tests, and production build pass.

Walkthrough

React hook implementations were migrated away from effect-driven state updates, WebRTC teardown was moved into effect cleanup, tests were added for updated behaviors, and React Hooks lint-rule documentation and statuses were completed.

Changes

React Hooks rule compliance

Layer / File(s) Summary
Media-query external store
app/utils/responsive.ts, app/utils/__tests__/responsive.test.ts
useMediaQuery now uses useSyncExternalStore with query subscriptions, SSR fallback, and coverage for updates, query changes, and unsubscription.
Render-time component synchronization
src/components/InputDateTimeCard.tsx, src/components/InputNumberCard.tsx, src/components/KeepAlive.tsx, src/components/__tests__/*
Input values and cached portal elements are synchronized during render, with tests covering entity updates, edit preservation, portal placement, and DOM reuse.
WebRTC teardown lifecycle
src/hooks/useWebRTC.ts
Disabled-state cleanup now runs through a dedicated effect cleanup path, while initialization no longer depends on cleanup.
Lint enforcement and documentation
eslint.config.js, docs/changes/*, docs/index.*, docs/specs/architecture/index.md
The remaining React Hooks rules are enabled at error, and related change records and architecture documentation are updated to complete status.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: enabling react-hooks v7 rules.
Description check ✅ Passed The description is directly related and accurately summarizes the code and docs changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes change 0003 by enabling the remaining stateful eslint-plugin-react-hooks v7 rules (set-state-in-effect and preserve-manual-memoization) at error, and refactors affected hooks/components to comply without adding suppressions. It also updates the project’s change/spec index docs and adds targeted tests for the behavioral refactors.

Changes:

  • Enable all five react-hooks v7 rules at error in eslint.config.js.
  • Refactor useMediaQuery, KeepAlive, input-* cards, and useWebRTC teardown patterns to satisfy react-hooks/set-state-in-effect.
  • Add/extend Vitest coverage for the refactored patterns and mark change 0003 as complete in docs.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/hooks/useWebRTC.ts Moves teardown behavior into effect cleanup keyed by enabled to avoid state updates in effect bodies.
src/components/KeepAlive.tsx Resolves cached portal element during render and narrows the effect to mounting/unmounting the element.
src/components/InputNumberCard.tsx Reworks local edit value syncing to avoid effect-driven setState.
src/components/InputDateTimeCard.tsx Reworks local edit value syncing to avoid effect-driven setState.
src/components/tests/KeepAlive.test.tsx Adds tests for portal caching and moving behavior.
src/components/tests/InputNumberCard.test.tsx Adds tests ensuring entity-driven updates don’t clobber in-progress edits and seed correctly.
src/components/tests/InputDateTimeCard.test.tsx Adds tests ensuring edit field seeds from latest entity state.
eslint.config.js Flips set-state-in-effect and preserve-manual-memoization from off to error.
docs/specs/architecture/index.md Updates the linting scenario to reflect all five rules enforced at error; removes the related open question.
docs/index.yml Marks change 0003 status as complete.
docs/index.md Marks change 0003 status as complete.
docs/changes/0003-reenable-react-hooks-rules.md Marks change 0003 as complete and checks off Group B.
app/utils/responsive.ts Rewrites useMediaQuery using useSyncExternalStore to avoid effect-driven setState and keep query changes in sync.
app/utils/tests/responsive.test.ts Adds tests for reactive updates, query changes, and unsubscribe behavior.
Comments suppressed due to low confidence (1)

docs/changes/0003-reenable-react-hooks-rules.md:12

  • This change doc still states that eslint.config.js disables the react-hooks v7 rules, but the PR enables them at error. Updating the Motivation bullet avoids leaving the doc in a contradictory state now that status is marked complete.
**Status:** complete
**Depends On:** —

## Motivation

Comment thread src/components/KeepAlive.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/KeepAlive.tsx (1)

13-27: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Don't mutate portalCache during render. Creating and storing the portal element here can leave detached nodes behind if React abandons a render, and the cache never evicts entries. Move creation/registration to a commit-safe effect with cleanup or eviction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/KeepAlive.tsx` around lines 13 - 27, Update
getOrCreatePortalElement and the KeepAlive lifecycle so portalCache is not
mutated during render. Create and register each portal element from a
commit-safe effect, then remove the cache entry and detach or clean up the
element when the owning component unmounts or the key changes, while preserving
cached reuse for committed instances.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/KeepAlive.tsx`:
- Around line 13-27: Update getOrCreatePortalElement and the KeepAlive lifecycle
so portalCache is not mutated during render. Create and register each portal
element from a commit-safe effect, then remove the cache entry and detach or
clean up the element when the owning component unmounts or the key changes,
while preserving cached reuse for committed instances.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3d921e6-5888-450f-9ae8-4f755ef6ae3b

📥 Commits

Reviewing files that changed from the base of the PR and between 91e5623 and d46d04d.

📒 Files selected for processing (1)
  • src/components/KeepAlive.tsx

@fx
fx merged commit fe180fd into main Jul 18, 2026
3 checks passed
@fx
fx deleted the refactor/0003-react-hooks-rules-stateful branch July 18, 2026 08:53
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.

2 participants