Skip to content

Convert handler-only pendingFormValues from useState to useRef on Alternative Login Identifier page - #10586

Open
dannguyen24 wants to merge 4 commits into
wso2:masterfrom
dannguyen24:fix-issue-27957
Open

Convert handler-only pendingFormValues from useState to useRef on Alternative Login Identifier page#10586
dannguyen24 wants to merge 4 commits into
wso2:masterfrom
dannguyen24:fix-issue-27957

Conversation

@dannguyen24

Copy link
Copy Markdown
Contributor

What does this PR do?

Converts the pendingFormValues value on the Alternative Login Identifier page from useState to useRef. This value holds the form data that is temporarily stashed while the user is shown the uniqueness-scope confirmation modal. It is only ever written and read inside event handlers (processFormSubmission and handleConsentedSubmit) and is never referenced in the component's render output, so backing it with a ref avoids an unnecessary re-render on every update while preserving identical behavior.

Why was this PR needed?

The React Doctor linter flagged this location under the rerender-state-only-in-handlers rule: "Each update to pendingFormValues redraws your component for nothing because this useState is set but never shown on screen." Investigation of alternative-login-identifier-page.tsx confirmed the value is set (setPendingFormValues) only when opening the confirmation modal and read only when the user consents — both purely inside handlers, never in the JSX. useRef is the correct primitive for a mutable value that does not drive rendering, so it is swapped in with no functional change.

What are the relevant issue numbers?

Closes wso2/product-is#27957

Screenshots / Recordings (if applicable)

Not applicable — this is an internal state-management refactor with no visual or behavioral change. The confirmation-modal consent flow behaves identically.

Does this PR meet the acceptance criteria?

  • Tests added for new/changed behavior — N/A, no behavioral change; no existing test suite for this feature
  • All tests passing
  • Follows project style guide (explicit MutableRefObject<...> annotation, typed useRef generic)
  • No breaking changes introduced
  • Documentation updated (if applicable) — N/A

dannguyen24 and others added 3 commits July 12, 2026 22:04
React Doctor's rerender-state-only-in-handlers rule flags pendingFormValues
in alternative-login-identifier-page.tsx: it is written in processFormSubmission
and read in handleConsentedSubmit, but never in the component's return. Each
setPendingFormValues therefore schedules a re-render with no visible change.

Move it to useRef so the value still persists across the confirmation-modal
round-trip while setting it no longer forces a render. The re-render the modal
relies on still comes from setShowConfirmationModal(true), so behavior is
identical (pure performance change).

connector (also flagged) is left as useState: it is a false positive, read in
the useEffect dependency array that drives initializeForm()/initialFormValues
(which is rendered). Converting it would stop the effect from firing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lockfile changes were unintended environment noise (stripped libc
fields, changed dependency hash) unrelated to the useRef refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes replace handler-only React state with refs across account, consent, recovery, association, and session components, while preserving pending submissions, cached lookups, initialization checks, form values, and modal targets.

Changes

Handler-Only State Refactor

Layer / File(s) Summary
Consent-pending form submission flow
features/admin.alternative-login-identifier.v1/pages/alternative-login-identifier-page.tsx
Pending form values are stored in a ref before consent confirmation and read when the submission resumes.
Consent cache and recovery initialization
apps/myaccount/src/components/consents/consents.tsx, apps/myaccount/src/components/account-recovery/options/security-questions-recovery.tsx
Purpose models and the security-question initialization flag are stored in refs and read by subsequent operations and effects.
Form input and modal target references
apps/myaccount/src/components/linked-accounts/*, apps/myaccount/src/components/federated-associations/federated-associations.tsx, apps/myaccount/src/components/user-sessions/user-sessions.tsx
Username input and selected deletion or termination targets are assigned and read through refs in event handlers.

Suggested reviewers: pavindulakshan


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Changeset Required ❌ Error The PR diff lists 6 changed files and none are under .changeset/; no new changeset markdown is included. Add a new .changeset/*.md file (not README.md) covering the affected packages and version bumps.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main refactor and the affected page.
Description check ✅ Passed The description covers purpose, motivation, issue linkage, and no-behavior-change impact, though it doesn't mirror the repo template exactly.
Linked Issues check ✅ Passed The refactor replaces handler-only state with refs and uses ref.current reads and writes while preserving behavior, matching #27957.
Out of Scope Changes check ✅ Passed All changes are the same state-to-ref refactor pattern across related components and stay within the linked issue's scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ create changeset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai
coderabbitai Bot requested a review from pavinduLakshan July 27, 2026 23:53

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
features/admin.alternative-login-identifier.v1/pages/alternative-login-identifier-page.tsx (1)

113-116: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Type the ref as nullable.

pendingFormValuesRef is initialized with null, so its explicit type should include null rather than narrowing it to the form values only.

Proposed fix
-    const pendingFormValuesRef: MutableRefObject<AlternativeLoginIdentifierFormInterface> =
-        useRef<AlternativeLoginIdentifierFormInterface>(null);
+    const pendingFormValuesRef: MutableRefObject<AlternativeLoginIdentifierFormInterface | null> =
+        useRef<AlternativeLoginIdentifierFormInterface | null>(null);

     const handleConsentedSubmit = (): void => {
-        if (!pendingFormValuesRef.current) {
+        const pendingFormValues: AlternativeLoginIdentifierFormInterface | null =
+            pendingFormValuesRef.current;
+
+        if (!pendingFormValues) {
             return;
         }

-        processFormSubmission(pendingFormValuesRef.current, true);
+        processFormSubmission(pendingFormValues, true);
🤖 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
`@features/admin.alternative-login-identifier.v1/pages/alternative-login-identifier-page.tsx`
around lines 113 - 116, Update the explicit type of pendingFormValuesRef to
allow null, matching its useRef initialization while preserving the existing
AlternativeLoginIdentifierFormInterface value type.
🤖 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.

Nitpick comments:
In
`@features/admin.alternative-login-identifier.v1/pages/alternative-login-identifier-page.tsx`:
- Around line 113-116: Update the explicit type of pendingFormValuesRef to allow
null, matching its useRef initialization while preserving the existing
AlternativeLoginIdentifierFormInterface value type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: fcd04961-0479-464b-8baf-fd02ff1ebda5

📥 Commits

Reviewing files that changed from the base of the PR and between bcbb37c and adedd5f.

📒 Files selected for processing (1)
  • features/admin.alternative-login-identifier.v1/pages/alternative-login-identifier-page.tsx

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.46%. Comparing base (fa28b45) to head (358b252).
⚠️ Report is 784 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10586      +/-   ##
==========================================
+ Coverage   72.71%   73.46%   +0.75%     
==========================================
  Files         469      471       +2     
  Lines       70846    71454     +608     
  Branches      484      469      -15     
==========================================
+ Hits        51513    52492     +979     
+ Misses      19037    18687     -350     
+ Partials      296      275      -21     

see 61 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Addresses react-doctor rerender-state-only-in-handlers for 6 My Account
components where the value is set and read only inside handlers (or a
run-once mount effect guard) and never in render:
  - security-questions-recovery: isInit
  - consents: purposeDetailModels
  - federated-associations: id
  - linked-accounts-edit: userName
  - linked-accounts-list: userID
  - user-sessions: editingUserSession

Skipped (unsafe to convert) in this area:
  - backup-code-authenticator: isMFAConfigured (read in effect body; ref
    would change stale-closure semantics)
  - dashboard: previousViewportWasMobile (used in an effect dependency array)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx`:
- Around line 62-63: Keep handler-only refs nullable in linked-accounts-edit.tsx
at lines 62-63, and initialize userNameRef with an empty string or validate it
before constructing getFormValues(). In linked-accounts-list.tsx lines 57-58,
narrow userIDRef before passing it to handleLinkedAccountRemove(); in
federated-associations.tsx lines 72-73, narrow idRef before passing it to
removeFederatedAssociation().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d1736eb-c1f1-4ec2-bc56-475f83e9971a

📥 Commits

Reviewing files that changed from the base of the PR and between adedd5f and 358b252.

📒 Files selected for processing (6)
  • apps/myaccount/src/components/account-recovery/options/security-questions-recovery.tsx
  • apps/myaccount/src/components/consents/consents.tsx
  • apps/myaccount/src/components/federated-associations/federated-associations.tsx
  • apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx
  • apps/myaccount/src/components/linked-accounts/linked-accounts-list.tsx
  • apps/myaccount/src/components/user-sessions/user-sessions.tsx

Comment on lines +62 to +63
// Captures the latest typed username; read only on submit, never rendered, so a ref avoids per-keystroke re-renders.
const userNameRef: MutableRefObject<string> = useRef<string>(undefined);

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -HI 'tsconfig*.json' -t f -x rg -n '"strictNullChecks"|"strict"' {}
rg -n 'useRef<string>\((null|undefined)\)' apps/myaccount/src/components

Repository: wso2/identity-apps

Length of output: 2909


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
wc -l apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx apps/myaccount/src/components/linked-accounts/linked-accounts-list.tsx apps/myaccount/src/components/federated-associations/federated-associations.tsx

echo "== edit relevant =="
cat -n apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx | sed -n '1,130p'

echo "== list relevant =="
cat -n apps/myaccount/src/components/linked-accounts/linked-accounts-list.tsx | sed -n '1,130p'

echo "== federated relevant =="
cat -n apps/myaccount/src/components/federated-associations/federated-associations.tsx | sed -n '1,260p'

echo "== references to values =="
rg -n 'userNameRef|userIDRef|idRef|LinkedAccount|onLinkedAccountRemove|removeFederatedAssociation' apps/myaccount/src/components/linked-accounts apps/myaccount/src/components/federated-associations

Repository: wso2/identity-apps

Length of output: 33976


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tsconfig files relevant to app =="
for f in package.json apps/myaccount/package.json apps/myaccount/tsconfig.json apps/myaccount/tsconfig.app.json apps/myaccount/tsconfig.dev.json tsconfig.base.json tsconfig.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '/"compilerOptions"/,/^[[:space:]]*}/p' "$f" 2>/dev/null | head -80
  fi
done

echo "== edit remaining lines =="
cat -n apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx | sed -n '130,175p'

echo "== federated remaining relevant lines =="
cat -n apps/myaccount/src/components/federated-associations/federated-associations.tsx | sed -n '260,295p'

Repository: wso2/identity-apps

Length of output: 7201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== vite config files =="
fd -HI 'vite.config.*' . -t f -x sh -c 'echo "--- $1 ---"; sed -n "1,220p" "$1"' sh {}

echo "== tsconfig inheritance =="
for f in apps/myaccount/tsconfig.json tsconfig.base.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat "$f"
  fi
done

echo "== exact compiler options in apps/myaccount/tsconfig.json =="
python3 - <<'PY'
import json
from pathlib import Path
p=Path("apps/myaccount/tsconfig.json")
data=json.loads(p.read_text())
print(json.dumps(data, indent=2))
PY

echo "== compile-time availability without installing =="
node - <<'JS'
try {
  const ts = require("typescript");
  console.log("typescript available", ts.version);
} catch (e) {
  console.log("typescript not available", e.message);
}
JS

Repository: wso2/identity-apps

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== root/react tsconfig null-related options =="
for f in tsconfig.base.react.json tsconfig.base.json apps/myaccount/tsconfig.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n '"strict"|"strictNullChecks"|"strictPropertyInitialization"|"exactOptionalPropertyTypes"|"paths"|"extends"' "$f" || true
  fi
done

echo "== all tsconfig null-related options =="
rg -n --glob 'tsconfig*.json' '"strict"|"strictNullChecks"|"strictPropertyInitialization"|"exactOptionalPropertyTypes"' .

Repository: wso2/identity-apps

Length of output: 454


Keep handler-only ref contents nullable.

userIDRef and idRef are typed as non-nullable strings but are initialized/cleared with null; narrow/refine before passing them into handleLinkedAccountRemove() and removeFederatedAssociation(). For userNameRef, initialize with "" or validate the missing value before building getFormValues() so submit cannot receive a non-string username.

📍 Affects 3 files
  • apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx#L62-L63 (this comment)
  • apps/myaccount/src/components/linked-accounts/linked-accounts-list.tsx#L57-L58
  • apps/myaccount/src/components/federated-associations/federated-associations.tsx#L72-L73
🤖 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 `@apps/myaccount/src/components/linked-accounts/linked-accounts-edit.tsx`
around lines 62 - 63, Keep handler-only refs nullable in
linked-accounts-edit.tsx at lines 62-63, and initialize userNameRef with an
empty string or validate it before constructing getFormValues(). In
linked-accounts-list.tsx lines 57-58, narrow userIDRef before passing it to
handleLinkedAccountRemove(); in federated-associations.tsx lines 72-73, narrow
idRef before passing it to removeFederatedAssociation().

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.

[React Doctor] rerender-state-only-in-handlers: useState "connector" is updated but never read in the component's return — us... (300 occurrences)

1 participant