Conversation
Safe, no-behavior-change fixes across the monorepo: - Remove 75+ unused imports, variables, and type declarations - Fix no-undef errors by adding eslint-env node to Node.js scripts - Fix react/no-unescaped-entities by escaping apostrophes in JSX - Remove unused catch parameters - Add missing displayName to forwardRef components - Fix missing key props in iterators - Replace empty interfaces with type aliases Packages affected: - @wcpos/core: 82 → 36 problems - @wcpos/components: 43 → 25 problems - @wcpos/web-bundle: 2 → 2 problems (all fixed, remaining are no-undef) - @wcpos/main: 4 → 2 problems - @wcpos/utils: 4 → 3 problems Total reduction: 213 → 138 problems (~35% reduction)
Fixes for hooks dependency issues: - Add eslint-disable comments for intentional patterns (cache busters, identity keys) - Remove unnecessary dependencies where safe - Add txInstance to translations context dependencies - Fix stable ref patterns in collapsible primitives - Remove unused type declarations and imports from query hooks Query hooks cleaned: - categories.tsx, customers.tsx, orders.tsx, products.ts - tags.tsx, tax-rates.tsx, variations.ts Packages affected: - @wcpos/query: 33 → 15 problems - @wcpos/components: Added eslint-disable for intentional patterns Total reduction: 138 → 116 problems
Fixed mutation-during-render issues in UI settings forms: - Moved ref mutations to useEffect (customers, orders, logs, reports, pos-cart, pos-products, products) - Added eslint-disable comments for intentional ref mutations from useObservableRef Fixed mutation issues in: - pos/cart/cells/actions.tsx - moved newRowUUIDs update to useEffect - use-date-format.tsx - suppress warning for intentional visibleRef mutation - pos/products/index.tsx - suppress for expandedRef mutations in table config - products/products.tsx - same expandedRef pattern Note: Some react-compiler "skipped optimizing" warnings remain as an acceptable trade-off for maintaining the ref mutation patterns required by the architecture.
📝 WalkthroughWalkthroughThis pull request performs extensive codebase cleanup and refactoring, including removing unused imports and type declarations, consolidating import statements, moving render-time mutations to useEffect hooks, and adding ESLint suppress comments. No functional behavior changes; improvements to code quality and React rule compliance. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
packages/components/src/format-number/format-number.tsx (2)
132-139: Undefined variableindexwill cause a runtime error.The
getMaskAtIndexcallback referencesindexon line 138, but this variable is never defined within the function scope or received as a parameter. This will throw aReferenceErrorat runtime.🐛 Proposed fix to accept index as a parameter
- const getMaskAtIndex = React.useCallback(() => { + const getMaskAtIndex = React.useCallback((index: number) => { const { mask = ' ' } = props; if (typeof mask === 'string') { return mask; } return mask[index] || ' '; - }, [props]); + }, [props.mask]);
172-173: Type mismatch:formatfunction expectsnumberbut receivesstring.The
formatprop is typed as(value: number) => stringon line 28, butformattedValueis a string. Either update the type signature or convert the value appropriately.🔧 Option 1: Update type to accept string
- format?: string | ((value: number) => string); + format?: string | ((value: string) => string);🔧 Option 2: Parse the string to number before calling
} else if (typeof format === 'function') { - formattedValue = format(formattedValue); + formattedValue = format(parseFloat(formattedValue));packages/core/src/screens/main/pos/cart/table.tsx (1)
208-209: Replaceconsole.logwith logger and avoidanytype.Two guideline violations:
- Line 208: Uses
anytype—should use a strict type or generic- Line 209: Uses
console.log—should use the logger libraryAs per coding guidelines: "Use the logger library exclusively for logging; DO NOT use
console.log" and "Do not use 'any' type; use strict types and generics instead."Proposed fix
+import log from '@wcpos/utils/logger';meta: { - onChange: (data: any) => { - console.log('onChange called without handler', data); + onChange: (data: unknown) => { + log.warn('onChange called without handler', data); },packages/components/src/format/list.tsx (1)
5-7: Fix thearraytype definition.The type
array?: []is an empty tuple type, meaning it can only accept an empty array. This should be a proper array type to allow actual items.🐛 Proposed fix
export interface FormatListProps { - array?: []; + array?: (string | React.ReactNode)[]; }packages/core/src/screens/main/products/cells/actions.tsx (1)
1-30: Fix import ordering: move@tanstack/react-tablewith external deps.
@tanstack/react-tableis an external dependency and should be grouped with other externals right after React imports, before@wcpos/*and local imports.🔧 Suggested reorder
import * as React from 'react'; import { useRouter } from 'expo-router'; +import type { CellContext } from '@tanstack/react-table'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, @@ import { Text } from '@wcpos/components/text'; import { useT } from '../../../../contexts/translations'; import useDeleteDocument from '../../contexts/use-delete-document'; import usePullDocument from '../../contexts/use-pull-document'; -import type { CellContext } from '@tanstack/react-table'; - type ProductDocument = import('@wcpos/database').ProductDocument;As per coding guidelines: Enforce import ordering in TypeScript/JavaScript files: React first, then external dependencies, then
@wcpos/* internal imports.packages/core/src/utils/merge-stores.ts (1)
32-121: Add unit tests for this utility function.This utility handles critical business logic with multiple code paths (store removal, upsert, error handling) and data transformations. Per coding guidelines, utility functions with data transformations and business logic require colocated unit tests in a
merge-stores.test.tsfile.packages/core/src/screens/main/products/products.tsx (1)
201-237: Mutable ref pattern violates React Compiler rules; refactor instead of narrowing suppression scope.The block suppression of
react-compiler/react-compilercorrectly identifies a violation, but narrowing the scope alone doesn't resolve it. React Compiler bails out of optimization when ESLint suppressions are present, even if narrowed. The deeper issue is thatexpandedRefis both mutated (lines 205, 207, 218) and used in themetaobject passed to JSX (line 222), which violates React Compiler's requirement to avoid reading/writing mutable refs during render.The comment mentions a TanStack minification bug with computed property destructuring, not a React Compiler limitation. Rather than suppressing the rule, consider:
- Keeping the ref fully internal without passing it to JSX meta objects, or
- Converting
expandedRefto React state if its value affects rendering, or- Using a stable callback wrapper pattern that avoids exposing the mutable ref in dependencies.
Simply adding a
// eslint-disable-next-linecomment (as shown in the previous suggestion) will still cause the compiler to bail, and the issue will remain unresolved.packages/utils/src/logger/index.ts (1)
82-105: Replaceanywithunknownin thesafeStringifyparameter.The function safely handles any input type through JSON.stringify and try-catch, making
unknownthe appropriate choice for type safety.Proposed change
-function safeStringify(obj: any, maxLength = 10000): string { +function safeStringify(obj: unknown, maxLength = 10000): string {packages/core/src/screens/main/components/header/user-menu.tsx (1)
87-89: Replaceconsole.logwith logger.Per coding guidelines, use the logger library exclusively for logging. This
console.logshould useuiLoggerwhich is already imported.Proposed fix
onChange: (data: any) => { - console.log('onChange called without handler', data); + uiLogger.warn('onChange called without handler', data); },As per coding guidelines: "Use the logger library exclusively for logging; DO NOT use
console.log."packages/components/src/data-table/index.web.tsx (2)
87-89: Replaceconsole.logwith logger.Per coding guidelines, use the logger library exclusively. Consider importing the logger and replacing this debug statement.
Proposed fix
Add import at the top of the file:
import { getLogger } from '@wcpos/utils/logger'; const tableLogger = getLogger(['wcpos', 'components', 'data-table']);Then update the handler:
onChange: (data: any) => { - console.log('onChange called without handler', data); + tableLogger.warn('onChange called without handler', data); },As per coding guidelines: "Use the logger library exclusively for logging; DO NOT use
console.log."
66-66: Remove unusedextraDataprop.The
extraDataprop is declared in the interface (line 36) and destructured (line 66), but it is not used anywhere in the component. Remove it from both theDataTablePropsinterface and the destructuring statement.packages/components/src/data-table/index.tsx (1)
98-100: Replaceconsole.logwith logger and avoidanytype.This code violates the coding guidelines:
- Uses
console.loginstead of the logger library- Uses
anytype for thedataparameterProposed fix
+import { getLogger } from '@wcpos/utils/logger'; + +const logger = getLogger(['wcpos', 'components', 'data-table']); + meta: { expanded$, expandedRef, - onChange: (data: any) => { - console.log('onChange called without handler', data); + onChange: (data: unknown) => { + logger.warn('onChange called without handler', { context: { data } }); },As per coding guidelines: "Use the logger library exclusively for logging; DO NOT use
console.log" and "Do not use 'any' type; use strict types and generics instead".
🤖 Fix all issues with AI agents
In `@packages/components/src/format/list.tsx`:
- Around line 12-17: The map callback in array?.map returns string items with a
key but returns non-string items (returned as item) without ensuring a key,
which can trigger React key warnings; update the map so non-string React
elements also get a key — e.g., wrap returned non-string items in a keyed
React.Fragment or use React.cloneElement to add key when the map callback
(array?.map) returns item, ensuring the key uses the index or a stable
identifier (index in this code) so every branch (the typeof item === 'string'
branch and the else branch that returns item) supplies a key.
In `@packages/core/src/screens/main/pos/cart/cells/actions.tsx`:
- Around line 35-52: Add an explicit explanatory comment above the
React.useEffect block (the one referencing isNew, table.options.meta, uuid)
stating that useEffect is required because rowRef.pulseAdd(...) is asynchronous
(runs its callback after the ~800ms animation) so cleanup of
table.options.meta.newRowUUIDs must occur when the animation completes, and
therefore cannot be performed in the row-add event handler which has no hook for
post-animation timing; keep the existing eslint-disable comment about
table.options.meta being safe to update in the effect.
🧹 Nitpick comments (7)
packages/core/src/screens/main/components/customer-select.tsx (1)
21-21: Use a type-only import forCustomerDocument.It’s only used as a type (Line 137), so make this a type-only import to avoid unnecessary runtime dependency.
Proposed change
-import { CustomerDocument } from '@wcpos/database'; +import type { CustomerDocument } from '@wcpos/database';packages/core/src/screens/main/pos/products/index.tsx (1)
174-206: Narrow the eslint-disable scope to only the offending line.The disable currently spans the table config too, but the justification references
setRowExpanded. Prefereslint-disable-next-line(or a tightly scoped block) to minimize rule suppression.♻️ Suggested change
- /* eslint-disable react-compiler/react-compiler -- expandedRef is a mutable ref from useObservableRef */ - const setRowExpanded = React.useCallback( + /* eslint-disable-next-line react-compiler/react-compiler -- expandedRef is a mutable ref from useObservableRef */ + const setRowExpanded = React.useCallback( (rowId: string, expanded: boolean) => { if (expanded) { expandedRef.current = { ...expandedRef.current, [rowId]: true }; } else { expandedRef.current = omit(expandedRef.current, rowId); } }, [expandedRef] ); @@ - /* eslint-enable react-compiler/react-compiler */As per coding guidelines: “Enforce React Compiler rules in TypeScript/JavaScript files.”
packages/components/src/format-number/format-number.tsx (1)
186-188: Implicitanytype onvalparameter.The
valparameter lacks a type annotation, resulting in an implicitanytype. As per coding guidelines, explicit types should be used instead ofany.♻️ Proposed fix to add explicit type
- const formatInput = React.useCallback((val) => { + const formatInput = React.useCallback((val: string) => { return val; }, []);packages/components/src/select/index.tsx (1)
92-96: FIXME comments indicate pending animation issues.There are two related FIXME comments here: the flash on unmount (lines 93-95) and the content rendering prematurely (lines 84-86). These appear to be interconnected—the early render workaround at line 87 (
if (!open) return null) may be conflicting with Reanimated's exit animations.Would you like me to open an issue to track investigating these animation issues? A potential approach would be to use
useAnimatedStylewith opacity transitions instead ofentering/exitingprops, or explore@rn-primitives/select's built-in presence handling.packages/core/src/utils/merge-stores.ts (1)
41-41: Avoid usinganytype in theremoteStoresparameter.The
[key: string]: anyindex signature violates the coding guidelines. Consider defining a proper type for the remote store response shape or using a generic type parameter.♻️ Suggested refactor
+// Define at the top of the file or in a shared types module +interface RemoteStore { + id: number; + name?: string; + // Add other known properties from the API response + [key: string]: unknown; // Use 'unknown' if additional properties are expected +} + export async function mergeStoresWithResponse({ userDB, wpUser, remoteStores, user, siteID, }: { userDB: UserDatabase; wpUser: WPCredentialsDocument; - remoteStores: { id: number; [key: string]: any }[]; + remoteStores: RemoteStore[]; user: { uuid: string }; siteID: string; }) {As per coding guidelines: "Do not use 'any' type; use strict types and generics instead".
packages/components/src/command/index.tsx (1)
107-109: Prefer semantic color classes +classNamefor theme compatibility.Inline styles with hardcoded
#e5e7ebcan break theme switching; use semantic classes instead.♻️ Suggested change
-const CommandSeparator = React.forwardRef<View, CommandSeparatorProps>((props, ref) => ( - <View ref={ref} style={[{ height: 1, backgroundColor: '#e5e7eb' }]} {...props} /> -)); +const CommandSeparator = React.forwardRef<View, CommandSeparatorProps>((props, ref) => ( + <View ref={ref} className="h-px bg-muted" {...props} /> +));As per coding guidelines, “Always prefer semantic color classes (bg-background, bg-card, bg-sidebar, bg-muted, bg-table-header) over hardcoded colors” and “Prefer className over inline styles to ensure proper theme switching.”
packages/query/src/use-relational-query.ts (1)
103-104: Minor: The comment could mention both identity keys.The comment references only
parentIdentityKey, butchildIdentityKeyis also involved through theregisterQueriescallback dependency. Consider updating the comment for completeness:-// eslint-disable-next-line react-hooks/exhaustive-deps -- parentIdentityKey triggers re-registration when query identity changes +// eslint-disable-next-line react-hooks/exhaustive-deps -- identity keys trigger re-registration via registerQueries when query identity changes
| {array?.map((item, index) => { | ||
| if (typeof item === 'string') { | ||
| return <Text>{item}, </Text>; | ||
| return <Text key={index}>{item}, </Text>; | ||
| } | ||
| return item; | ||
| })} |
There was a problem hiding this comment.
Non-string items returned without a key will trigger React warnings.
Line 14 correctly adds a key for string items, but line 16 returns non-string items (potentially React elements) without ensuring they have keys. If item is a React element without a key, React will warn about missing keys in the list.
🔧 Proposed fix using React.Fragment with key
{array?.map((item, index) => {
if (typeof item === 'string') {
return <Text key={index}>{item}, </Text>;
}
- return item;
+ return <React.Fragment key={index}>{item}</React.Fragment>;
})}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {array?.map((item, index) => { | |
| if (typeof item === 'string') { | |
| return <Text>{item}, </Text>; | |
| return <Text key={index}>{item}, </Text>; | |
| } | |
| return item; | |
| })} | |
| {array?.map((item, index) => { | |
| if (typeof item === 'string') { | |
| return <Text key={index}>{item}, </Text>; | |
| } | |
| return <React.Fragment key={index}>{item}</React.Fragment>; | |
| })} |
🤖 Prompt for AI Agents
In `@packages/components/src/format/list.tsx` around lines 12 - 17, The map
callback in array?.map returns string items with a key but returns non-string
items (returned as item) without ensuring a key, which can trigger React key
warnings; update the map so non-string React elements also get a key — e.g.,
wrap returned non-string items in a keyed React.Fragment or use
React.cloneElement to add key when the map callback (array?.map) returns item,
ensuring the key uses the index or a stable identifier (index in this code) so
every branch (the typeof item === 'string' branch and the else branch that
returns item) supplies a key.
| * Use pulse effect for new rows | ||
| * Move to effect to avoid mutating props during render | ||
| */ | ||
| const isNew = table.options.meta.newRowUUIDs.includes(uuid); | ||
| if (isNew) { | ||
| const rowRef = table.options.meta.rowRefs.current.get(uuid); | ||
| if (rowRef && rowRef?.pulseAdd) { | ||
| rowRef.pulseAdd(() => { | ||
| table.options.meta.newRowUUIDs = table.options.meta.newRowUUIDs.filter((id) => id !== uuid); | ||
| }); | ||
|
|
||
| // eslint-disable-next-line react-compiler/react-compiler -- table.options.meta is table config, safe to update in effect | ||
| React.useEffect(() => { | ||
| if (isNew) { | ||
| const rowRef = table.options.meta.rowRefs.current.get(uuid); | ||
| if (rowRef && rowRef?.pulseAdd) { | ||
| rowRef.pulseAdd(() => { | ||
| // Create new array instead of mutating | ||
| const currentUUIDs = table.options.meta.newRowUUIDs; | ||
| table.options.meta.newRowUUIDs = currentUUIDs.filter((id) => id !== uuid); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| }, [isNew, table.options.meta, uuid]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/core/src/screens/main/pos/cart/cells/actions.tsx | head -60Repository: wcpos/monorepo
Length of output: 2198
🏁 Script executed:
rg -n "newRowUUIDs" packages/core/src/screens/main/pos/cart/cells/actions.tsx -B 5 -A 5Repository: wcpos/monorepo
Length of output: 818
🏁 Script executed:
# Find where newRowUUIDs is set/updated across the codebase
rg -n "newRowUUIDs\s*=" -C 3Repository: wcpos/monorepo
Length of output: 1274
🏁 Script executed:
# Get more context about where newRowUUIDs is computed and updated
cat -n packages/core/src/screens/main/pos/cart/table.tsx | sed -n '120,150p'Repository: wcpos/monorepo
Length of output: 1075
🏁 Script executed:
# Search for where rows are added to understand the control flow
rg -n "newRowUUIDs|addRow" packages/core/src/screens/main/pos/cart/ -C 2Repository: wcpos/monorepo
Length of output: 2006
🏁 Script executed:
# Check if there are any event handlers that add rows
rg -n "handleAdd|onCreate|onAdd" packages/core/src/screens/main/pos/cart/ -C 3Repository: wcpos/monorepo
Length of output: 5742
🏁 Script executed:
# Find pulseAdd definition to understand if it's async
rg -n "pulseAdd" packages/core/src/screens/main/pos/cart/ -C 3Repository: wcpos/monorepo
Length of output: 897
🏁 Script executed:
# Look for rowRefs definition and pulseAdd implementation
rg -n "pulseAdd|pulseRemove" packages/core/src/ -C 2 | head -50Repository: wcpos/monorepo
Length of output: 1266
🏁 Script executed:
# Check if there's a row component that manages pulseAdd animation
fd -e tsx -e ts . packages/core/src/screens/main/pos/cart/ | xargs grep -l "pulseAdd\|pulseRemove" 2>/dev/nullRepository: wcpos/monorepo
Length of output: 115
🏁 Script executed:
# Find the row component and its pulseAdd/pulseRemove implementation
rg -n "class.*Row|const.*Row" packages/core/src/screens/main/pos/cart/ -C 2 | grep -i "row\|tsx"Repository: wcpos/monorepo
Length of output: 2756
🏁 Script executed:
# Search for row component that might define pulseAdd
fd -name "*row*.tsx" packages/core/src/screens/main/pos/cart/Repository: wcpos/monorepo
Length of output: 288
🏁 Script executed:
# Look at the cart table structure to find row component
cat -n packages/core/src/screens/main/pos/cart/table.tsx | sed -n '1,50p'Repository: wcpos/monorepo
Length of output: 2305
🏁 Script executed:
# Find PulseTableRow component
fd "PulseTableRow\|pulse-table-row" packages/Repository: wcpos/monorepo
Length of output: 40
🏁 Script executed:
# Search for PulseTableRow and its implementation
rg -n "PulseTableRow|pulseAdd" packages/components -C 3 | head -80Repository: wcpos/monorepo
Length of output: 3563
🏁 Script executed:
# Look at the table component file to understand pulseAdd
rg -n "pulseAdd\|export.*Pulse" packages/components/src/table/ -C 3Repository: wcpos/monorepo
Length of output: 40
🏁 Script executed:
# Get more context on pulseAdd implementation
cat -n packages/components/src/table/pulse-row.tsx | sed -n '60,100p'Repository: wcpos/monorepo
Length of output: 1400
Add a comment explaining why useEffect is necessary here instead of moving cleanup to the event handler.
The useEffect is justified because pulseAdd() is asynchronous—the callback only executes after the 800ms animation completes. You cannot move this cleanup to the row-add event handler since there's no event mechanism to trigger cleanup after animation finishes. However, per coding guidelines, add a comment explaining WHY useEffect is necessary here, rather than just relying on the React Compiler disable comment.
Current code (lines 40-52)
// eslint-disable-next-line react-compiler/react-compiler -- table.options.meta is table config, safe to update in effect
React.useEffect(() => {
if (isNew) {
const rowRef = table.options.meta.rowRefs.current.get(uuid);
if (rowRef && rowRef?.pulseAdd) {
rowRef.pulseAdd(() => {
// Create new array instead of mutating
const currentUUIDs = table.options.meta.newRowUUIDs;
table.options.meta.newRowUUIDs = currentUUIDs.filter((id) => id !== uuid);
});
}
}
}, [isNew, table.options.meta, uuid]);
Suggested update:
// useEffect needed: pulseAdd animation is async (800ms), callback runs after animation completes.
// Cannot be in event handler since animation timing is not synchronous with row addition.
// eslint-disable-next-line react-compiler/react-compiler -- table.options.meta is table config, safe to update in effect
React.useEffect(() => {
if (isNew) {
const rowRef = table.options.meta.rowRefs.current.get(uuid);
if (rowRef && rowRef?.pulseAdd) {
rowRef.pulseAdd(() => {
// Create new array instead of mutating
const currentUUIDs = table.options.meta.newRowUUIDs;
table.options.meta.newRowUUIDs = currentUUIDs.filter((id) => id !== uuid);
});
}
}
}, [isNew, table.options.meta, uuid]);
🤖 Prompt for AI Agents
In `@packages/core/src/screens/main/pos/cart/cells/actions.tsx` around lines 35 -
52, Add an explicit explanatory comment above the React.useEffect block (the one
referencing isNew, table.options.meta, uuid) stating that useEffect is required
because rowRef.pulseAdd(...) is asynchronous (runs its callback after the ~800ms
animation) so cleanup of table.options.meta.newRowUUIDs must occur when the
animation completes, and therefore cannot be performed in the row-add event
handler which has no hook for post-animation timing; keep the existing
eslint-disable comment about table.options.meta being safe to update in the
effect.
Summary by CodeRabbit
Bug Fixes
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.