Skip to content

Fix input validation in React SDK.#22

Open
SajidMannikeri17 wants to merge 1 commit into
thunder-id:mainfrom
Infosys:bug/input-validation
Open

Fix input validation in React SDK.#22
SajidMannikeri17 wants to merge 1 commit into
thunder-id:mainfrom
Infosys:bug/input-validation

Conversation

@SajidMannikeri17

@SajidMannikeri17 SajidMannikeri17 commented Jul 9, 2026

Copy link
Copy Markdown

Purpose

Fix two defects in the input-validation feature introduced in #3301 that made client-side validation and server-side fieldErrors rendering unusable in real flows:

  1. Infinite render loopBaseSignIn and BaseRecovery re-created the formFields array on every render, which cascaded through useForm's memoized callbacks (getFieldConfigvalidateFieldsetTouched) and re-fired the server-error projection useEffect every render, triggering Maximum update depth exceeded. BaseSignUp was already patched with useMemo; the other two were missed.
  2. Server fieldErrors silently wiped from the UIBaseSignIn, BaseSignUp and BaseRecovery marked fields touched via useForm's setTouched, which internally runs validateField and deletes the field's error from state when client-side rules pass. Whenever server-side rules were stricter than client-side rules (the common case), the server error was set and then immediately removed on the same render — the UI showed nothing.

After this PR the SDK correctly renders both client-side validation errors on blur/submit and server-side data.fieldErrors returned by the flow engine.

Approach

1. Stabilise formFields in every useForm-based Base component

useForm's getFieldConfig, validateField, and setTouched all use fields in their useCallback dependency arrays. When fields is a fresh array on every render, all three callbacks change reference on every render, which makes any useEffect depending on them fire on every render. Wrapping the field extraction in useMemo (keyed on [components, extractFormFields]) freezes the reference until the flow's components actually change, breaking the loop.

Applied to BaseSignIn.tsx and BaseRecovery.tsx. BaseSignUp.tsx already had this fix.

2. Use setTouchedFields instead of setTouched when projecting server errors

useForm exposes two ways to mark fields as touched:

  • setTouched(name, isTouched) — sets touched and re-runs validateField(name), mutating errors based on client-side rules. Intended for blur/change handlers where re-validation is desired.
  • setTouchedFields(fieldsMap) — bulk state setter that only updates touched, no validation triggered.

The server-error projection useEffect was calling setTouched, which caused the client-side revalidation to overwrite the server error. Switched to setTouchedFields in the three useForm-based Base components. setTouched is still used inline in handleInputBlur where re-validation is the intended behaviour.

3. Scope

BaseAcceptInvite and BaseInviteUser manage formErrors and touchedFields via plain useState — not useForm — so neither defect applies to them and they are unchanged.

Related Issues

Related PRs

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • Bug Fixes
    • Improved sign-in, sign-up, and account recovery forms so validation errors from the server now appear more reliably in the UI.
    • Existing errors are cleared before new ones are applied, reducing stale or duplicated messages.
    • Form fields are now marked as touched automatically when errors are returned, so messages show up consistently.
    • Enhanced form validation on sign-in to better reflect the configured field rules and display translated feedback.

Signed-off-by: sajid.mannikeri <sajid.mannikeri@infosys.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Recovery, SignIn, and SignUp base auth components now memoize their form field extraction via useMemo and rewrite server-side fieldErrors projection to build combined errors and touched maps, applying them in bulk via setTouchedFields and setFormErrors. SignIn additionally adds declarative rule-based field validation using buildValidatorFromRules.

Changes

Auth form updates

Layer / File(s) Summary
Recovery: memoized fields and error projection
packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx
formFields is now computed via useMemo; the fieldErrors effect clears errors then builds errors/touched maps applied via setTouchedFields and setFormErrors.
SignIn: rule-based validation and server error projection
packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx
Adds buildValidatorFromRules-based validation for component rules, memoizes formFields, and adds a serverFieldErrors prop with an effect projecting server errors into form state via setTouchedFields/setFormErrors.
SignUp: bulk touched field error projection
packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx
Destructures setTouchedFields from useForm and rewrites the fieldErrors effect to build errors/touched maps applied together instead of looping setFormTouched.

Estimated code review effort: 2 (Simple) | ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: fixing input validation in the React SDK.
Description check ✅ Passed The description follows the template with Purpose, Approach, related links, checklist, and security checks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (1)
packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx (1)

260-276: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared server-error projection helper

The server-error projection effect (clear → build errors/touched maps → setTouchedFieldssetFormErrors) is duplicated verbatim across BaseRecovery.tsx (lines 260-276), BaseSignIn.tsx (lines 377-392), and BaseSignUp.tsx (lines 489-505). Extracting a helper would prevent drift and simplify future maintenance.

♻️ Proposed shared helper
+// packages/react/src/utils/projectServerFieldErrors.ts
+import type {FieldError} from '`@thunderid/browser`';
+
+interface FormErrorProjectionAPI {
+  clearErrors: () => void;
+  setTouchedFields: (touched: Record<string, boolean>) => void;
+  setErrors: (errors: Record<string, string>) => void;
+}
+
+export function projectServerFieldErrors(
+  fieldErrors: FieldError[] | undefined,
+  api: FormErrorProjectionAPI,
+): void {
+  api.clearErrors();
+  if (!fieldErrors || fieldErrors.length === 0) {
+    return;
+  }
+  const errors: Record<string, string> = {};
+  const touched: Record<string, boolean> = {};
+  for (const fe of fieldErrors) {
+    if (!(fe.identifier in errors)) {
+      errors[fe.identifier] = fe.message;
+      touched[fe.identifier] = true;
+    }
+  }
+  api.setTouchedFields(touched);
+  api.setErrors(errors);
+}

Then each effect simplifies to:

 useEffect(() => {
-  clearFormErrors();
-  const responseFieldErrors: FieldError[] | undefined = (currentFlow?.data as any)?.fieldErrors;
-  if (!responseFieldErrors || responseFieldErrors.length === 0) {
-    return;
-  }
-  const errors: Record<string, string> = {};
-  const touched: Record<string, boolean> = {};
-  for (const fe of responseFieldErrors) {
-    if (!(fe.identifier in errors)) {
-        errors[fe.identifier] = fe.message;
-        touched[fe.identifier] = true;
-      }
-    }
-    setTouchedFields(touched);
-    setFormErrors(errors);
+  projectServerFieldErrors(
+    (currentFlow?.data as any)?.fieldErrors,
+    {clearErrors: clearFormErrors, setTouchedFields, setErrors: setFormErrors},
+  );
 }, [currentFlow, setFormErrors, setTouchedFields, clearFormErrors]);
🤖 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 `@packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx`
around lines 260 - 276, The server-error projection logic is duplicated in
BaseRecovery, BaseSignIn, and BaseSignUp, so extract the shared “clear → build
errors/touched maps → setTouchedFields → setFormErrors” flow into a reusable
helper. Create a helper that takes the current flow data and the form
setters/clearer, then have the effects in BaseRecovery, BaseSignIn, and
BaseSignUp call that helper instead of inlining the projection logic. Keep the
behavior identical, including deduping by identifier and preserving the current
effect dependencies.
🤖 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 `@packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx`:
- Around line 260-276: The server-error projection logic is duplicated in
BaseRecovery, BaseSignIn, and BaseSignUp, so extract the shared “clear → build
errors/touched maps → setTouchedFields → setFormErrors” flow into a reusable
helper. Create a helper that takes the current flow data and the form
setters/clearer, then have the effects in BaseRecovery, BaseSignIn, and
BaseSignUp call that helper instead of inlining the projection logic. Keep the
behavior identical, including deduping by identifier and preserving the current
effect dependencies.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 490ed76a-017a-418d-b755-e925987320f3

📥 Commits

Reviewing files that changed from the base of the PR and between b356f40 and f1e8d30.

📒 Files selected for processing (3)
  • packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx
  • packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx
  • packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx

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