Skip to content

refactor: useEffect audit and best practices improvements#15

Merged
kilbot merged 3 commits intomainfrom
refactor/useEffect-audit
Jan 23, 2026
Merged

refactor: useEffect audit and best practices improvements#15
kilbot merged 3 commits intomainfrom
refactor/useEffect-audit

Conversation

@kilbot
Copy link
Copy Markdown
Contributor

@kilbot kilbot commented Jan 23, 2026

Summary

Comprehensive useEffect audit following React best practices from the codebase conventions.

Changes

  1. EditableName - Replaced manual prop→state sync with useControllableState hook
  2. useSiteInfo - Added isLoading and error return values for better UX
  3. Form reset pattern - Replaced defaultValues + useEffect(() => form.reset()) with values prop (react-hook-form best practice) in 11 form files
  4. Documentation - Added justification comments to legitimate useEffect usages

Why values instead of defaultValues + reset?

  • defaultValues only initializes once - async data arrives after initialization
  • values prop makes the form reactive to external data changes
  • Eliminates the double-reset bug noted in GeneralSettings (@FIXME removed)
  • Official react-hook-form recommendation for async/external data

Test Plan

Settings Forms (highest risk)

  • General Settings - Change store name, currency, default customer → changes persist after closing/reopening modal
  • Tax Settings - Toggle tax options, change tax display → changes persist
  • Barcode Settings - Change threshold values, prefix/suffix → changes persist
  • Restore Server Settings button works in General and Tax settings

UI Settings Forms

  • Orders UI Settings - Toggle columns, change visibility → persists
  • Cart UI Settings - Toggle auto-show receipt, change quick discounts → persists
  • Customers UI Settings - Toggle columns → persists
  • Logs UI Settings - Toggle columns → persists
  • Reports UI Settings - Toggle columns → persists
  • Reset to Defaults button works in all UI settings

Order/Customer Forms

  • Edit Order Form - Edit order status, customer, billing/shipping → save works
  • Edit Cart Customer - Edit billing/shipping address → "Save to Order" works
  • Edit Cart Customer - "Save to Order & Customer" updates both order and customer
  • Add New Customer - Create customer from cart → closes dialog, adds to order
  • Edit Order Meta - Edit currency, transaction ID → saves correctly

Component Changes

  • EditableName - Click product name in cart → edit → blur/submit → name updates
  • Loader - Spinner animation works (visible during loading states)
  • Textarea - Type text, clear text → height resets to minimum

Edge Cases

  • Form fields maintain focus during editing (not resetting unexpectedly)
  • Debounced text inputs save after typing stops (not on every keystroke)
  • Online/offline status updates correctly when network changes

Files Changed

Category Files
Components input/index.tsx, loader/index.tsx, textarea/index.tsx, use-form-change-handler.ts
Hooks use-site-info.ts, use-online-status.web.tsx, use-query.ts
Screens editable-name.tsx, add-customer.tsx, 6x ui-settings-form.tsx, edit-cart-customer.tsx, edit/form.tsx, edit-order-meta/form.tsx, general.tsx, tax.tsx, barcode-scanning/settings.tsx

Summary by CodeRabbit

  • New Features

    • Optional debouncing for form change persistence (default 300ms).
    • Site info hook now exposes loading and error states.
    • Editable name component supports new controlled/uncontrolled edit flow.
  • Improvements

    • Many forms now bind reactively to external data (values) and no longer perform manual resets.
    • Removed redundant reset logic for cleaner data flow.
  • Documentation

    • Expanded clarifying comments across inputs, loaders, textareas, hooks, and query utilities.

✏️ Tip: You can customize this high-level summary in your review settings.

- Replace prop→state sync with useControllableState in EditableName
- Add loading/error states to useSiteInfo hook
- Replace defaultValues + useEffect reset pattern with `values` prop
  in 11 form files (react-hook-form best practice)
- Add justification comments to legitimate useEffect usages
- Fix double-reset bug in GeneralSettings form

Affected forms:
- orders/ui-settings-form.tsx
- logs/ui-settings-form.tsx
- reports/ui-settings-form.tsx
- pos/cart/ui-settings-form.tsx
- customers/ui-settings-form.tsx
- settings/tax.tsx
- settings/barcode-scanning/settings.tsx
- settings/general.tsx
- orders/edit/form.tsx
- pos/cart/edit-cart-customer.tsx
- pos/cart/buttons/edit-order-meta/form.tsx
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 23, 2026

Warning

Rate limit exceeded

@kilbot has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Refactors multiple forms to bind external data via values (removing imperative resets); extends useFormChangeHandler with a debounceMs parameter and reset-tracking to ignore programmatic resets; useSiteInfo now tracks loading/error and validates responses; EditableName uses internal edit state; various comment clarifications added.

Changes

Cohort / File(s) Change Summary
Form Change Handler
packages/components/src/form/use-form-change-handler.ts
Added debounceMs parameter (default 300); added isResettingRef to ignore programmatic resets; changed watch subscription to only persist user-initiated changes; debounced vs immediate persist logic and cleanup on unmount.
Reactive Form Initialization
packages/core/src/screens/main/customers/ui-settings-form.tsx, packages/core/src/screens/main/logs/ui-settings-form.tsx, packages/core/src/screens/main/orders/edit/form.tsx, packages/core/src/screens/main/orders/ui-settings-form.tsx, packages/core/src/screens/main/pos/cart/buttons/edit-order-meta/form.tsx, packages/core/src/screens/main/pos/cart/edit-cart-customer.tsx, packages/core/src/screens/main/pos/cart/ui-settings-form.tsx, packages/core/src/screens/main/reports/ui-settings-form.tsx, packages/core/src/screens/main/settings/barcode-scanning/settings.tsx, packages/core/src/screens/main/settings/general.tsx, packages/core/src/screens/main/settings/tax.tsx
Switched react-hook-form initialization from defaultValues to values: formData; removed useEffect-based resets so forms react directly to external data.
Site Info Hook
packages/core/src/hooks/use-site-info.ts
Added SiteInfoResult (isLoading, error); fetch flow now manages isLoading/error, validates response status/data, uses stable wpApiUrl/siteUrl deps, and calls site.incrementalPatch on valid data.
EditableName Component
packages/core/src/screens/main/components/editable-name.tsx
Replaced local value with internal editValue to avoid overwriting in-progress edits; added defaultValue prop; changed submit to call onChangeText?.(editValue ?? '').
Documentation / Comments
packages/components/src/input/index.tsx, packages/components/src/loader/index.tsx, packages/components/src/textarea/index.tsx, packages/core/src/screens/main/pos/cart/add-customer.tsx, packages/hooks/src/use-online-status.web.tsx, packages/query/src/use-query.ts
Expanded and clarified explanatory comments around useEffect blocks and workarounds (autoFocus, rotation init, empty-content height reset, modal cleanup, browser subscriptions, query cleanup).

Sequence Diagrams

sequenceDiagram
    participant User as User
    participant Form as React Hook Form
    participant Hook as useFormChangeHandler
    participant Debounce as Debounce Handler
    participant Persist as onChange Callback

    rect rgba(150, 100, 200, 0.5)
        Note over User,Form: User edits a form field
    end

    User->>Form: Edit field value
    Form->>Hook: watch subscription emits field change

    alt Programmatic reset (isResettingRef = true)
        Hook->>Hook: Ignore change
    else User-initiated change
        Hook->>Hook: Build changes object for changed path

        alt debounceMs > 0
            Hook->>Debounce: Schedule debounced call (debounceMs)
            Debounce->>Persist: onChange(changes) after debounce
        else
            Hook->>Debounce: Flush pending debounced calls
            Hook->>Persist: onChange(changes) immediately
        end
    end
Loading
sequenceDiagram
    participant Component as Consumer
    participant Hook as useSiteInfo
    participant HTTP as HTTP Client
    participant API as Backend API
    participant Site as Site Store

    rect rgba(100, 150, 200, 0.5)
        Note over Component,Hook: Hook invoked on mount / site change
    end

    Component->>Hook: useSiteInfo({ site })
    Hook->>Hook: setIsLoading(true), setError(null)

    rect rgba(100, 200, 150, 0.5)
        Note over Hook,API: Fetch site info using wpApiUrl
    end

    Hook->>HTTP: http.get(wpApiUrl)
    HTTP->>API: GET /wp-json/wcpos/v1/system-info
    API-->>HTTP: Response (status, data)
    HTTP-->>Hook: Response received

    alt 2xx & valid data
        Hook->>Site: site.incrementalPatch({ wp_version, wc_version, ... })
    else invalid status or data
        Hook->>Hook: setError(errorMsg)
    end

    Hook->>Hook: setIsLoading(false)
    Hook-->>Component: return { isLoading, error }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 With whiskers twitching, I note each change,

Debounce set to tame the typing range.
Forms now follow data, no reset surprise,
Site info loads with careful eyes.
I nibble docs and dance — hooray, new ties!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main focus of this PR: a comprehensive useEffect audit and improvements to React best practices across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

useControllableState wasn't suitable here because this component has
special semantics:
- Maintains internal editing buffer
- Only notifies parent on submit (blur/enter), not every keystroke
- Needs to sync from prop only when NOT editing
@kilbot kilbot merged commit f070ae3 into main Jan 23, 2026
2 checks passed
@kilbot kilbot deleted the refactor/useEffect-audit branch January 23, 2026 16:57
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
packages/core/src/screens/main/settings/tax.tsx (1)

133-135: Replace console.error with project logger.

Use @wcpos/utils/logger for structured logging and consistency. As per coding guidelines, please avoid console usage.

🛠️ Proposed fix
import { View } from 'react-native';
+import log from '@wcpos/utils/logger';

		} catch (error) {
-			console.error(error);
+			log.error('Failed to restore server settings', { context: { error } });
		} finally {
packages/components/src/form/use-form-change-handler.ts (1)

68-91: Wrap originalReset.apply() in try/finally to ensure flag clears even if reset throws.

If form.reset throws an error before queueMicrotask is called, isResettingRef.current remains true permanently, blocking all future onChange events from firing (see line 100 in the watch subscription). This prevents form changes from persisting.

Proposed fix
 form.reset = (...args) => {
 	isResettingRef.current = true;
-	const result = originalReset.apply(form, args);
-	// Reset the flag after a microtask to ensure watch has fired
-	queueMicrotask(() => {
-		isResettingRef.current = false;
-	});
-	return result;
+	try {
+		return originalReset.apply(form, args);
+	} finally {
+		// Reset the flag after a microtask to ensure watch has fired
+		queueMicrotask(() => {
+			isResettingRef.current = false;
+		});
+	}
 };

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