[jules] ux: Swipe-to-delete for expenses with undo option#314
[jules] ux: Swipe-to-delete for expenses with undo option#314
Conversation
Implemented swipe-to-delete functionality for expenses on the Group Details screen in the mobile app. - Created an optimistic deletion flow with a 5-second undo Snackbar using `react-native-paper`. - Wrapped expense cards with `Swipeable` from `react-native-gesture-handler`. - Added the `deleteExpense` function to the API client (`mobile/api/groups.js`). - Created `mobile/babel.config.js` to support the required `react-native-reanimated` plugin. - Added necessary ref-based timeout management to prevent state mismatch during rapid interactions. - Updated `.Jules/todo.md` and `.Jules/changelog.md` to reflect the completed task. Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
✅ Deploy Preview for split-but-wiser canceled.
|
WalkthroughAdds swipe-to-delete for user-paid expenses on the Group Details mobile screen with optimistic removal, a 5-second undo Snackbar, ref-based timeout handling, and deferred DELETE API calls. Adds Changes
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ 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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #314 +/- ##
=======================================
Coverage ? 63.55%
=======================================
Files ? 21
Lines ? 2456
Branches ? 254
=======================================
Hits ? 1561
Misses ? 831
Partials ? 64
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mobile/babel.config.js`:
- Around line 1-7: Babel references 'react-native-reanimated/plugin' and
GroupDetailsScreen.js imports 'react-native-gesture-handler' but those packages
are missing from mobile/package.json; add "react-native-reanimated" and
"react-native-gesture-handler" to the mobile app dependencies (or run npx expo
install react-native-reanimated react-native-gesture-handler) and then
reinstall/lock dependencies so the Babel plugin and GroupDetailsScreen imports
resolve at build/runtime.
In `@mobile/screens/GroupDetailsScreen.js`:
- Around line 116-136: The handleDeleteSwipe flow registers timeouts in
deleteTimeouts.current but never clears them on unmount; add a useEffect cleanup
that iterates deleteTimeouts.current, calls clearTimeout for each stored
timeoutId and deletes the entries to prevent state updates after unmount. Locate
deleteTimeouts, handleDeleteSwipe, and the optimistic state setters
(setHiddenExpenses, setExpenses) and ensure the cleanup only clears the pending
timers (clearTimeout(timeoutId)) and removes them from deleteTimeouts.current to
avoid memory leaks and React warnings.
- Around line 277-284: The current global undo (handleUndoGlobal) restores all
pending deletions while the snackbar only reflects the most recent deletion;
update the UX by computing pendingCount =
Object.keys(deleteTimeouts.current).length wherever you set the snackbar and
call setSnackbarMessage(pendingCount > 1 ? `Deleted ${pendingCount} expenses` :
`Deleted "${item.description}"`), so the snackbar accurately reflects multiple
pending deletes; keep handleUndoGlobal logic that iterates
deleteTimeouts.current to clear timeouts and call setHiddenExpenses, then clear
deleteTimeouts.current and setSnackbarVisible(false) so the message and undo
behavior are consistent (use the symbols handleUndoGlobal,
deleteTimeouts.current, setHiddenExpenses, setSnackbarMessage,
setSnackbarVisible to locate code).
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c582df4d-25fa-41f9-85fa-e0bd22b5a1ee
📒 Files selected for processing (5)
.Jules/changelog.md.Jules/todo.mdmobile/api/groups.jsmobile/babel.config.jsmobile/screens/GroupDetailsScreen.js
| const handleDeleteSwipe = () => { | ||
| // Optimistic delete | ||
| setHiddenExpenses((prev) => [...prev, item._id]); | ||
| setSnackbarMessage(`Deleted "${item.description}"`); | ||
| setSnackbarVisible(true); | ||
|
|
||
| const timeoutId = setTimeout(async () => { | ||
| try { | ||
| await deleteExpense(groupId, item._id); | ||
| setExpenses((prev) => prev.filter((exp) => exp._id !== item._id)); | ||
| } catch (error) { | ||
| console.error("Failed to delete expense:", error); | ||
| setHiddenExpenses((prev) => prev.filter((id) => id !== item._id)); | ||
| Alert.alert("Error", "Failed to delete expense."); | ||
| } | ||
| setHiddenExpenses((prev) => prev.filter((id) => id !== item._id)); | ||
| delete deleteTimeouts.current[item._id]; | ||
| }, 5000); // 5 seconds to undo | ||
|
|
||
| deleteTimeouts.current[item._id] = { timeoutId, item }; | ||
| }; |
There was a problem hiding this comment.
Memory leak: Pending timeouts not cleared on component unmount.
If the user navigates away from this screen before the 5-second timeout completes, the timeout will still fire and attempt to update state on an unmounted component, causing a React warning and potential memory leak.
🔧 Proposed fix: Add cleanup effect
Add a cleanup effect after the state declarations (around line 36):
const deleteTimeouts = useRef({});
+ useEffect(() => {
+ return () => {
+ // Clear all pending deletion timeouts on unmount
+ Object.values(deleteTimeouts.current).forEach(({ timeoutId }) => {
+ clearTimeout(timeoutId);
+ });
+ };
+ }, []);
+
// Currency configuration - can be made configurable later🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mobile/screens/GroupDetailsScreen.js` around lines 116 - 136, The
handleDeleteSwipe flow registers timeouts in deleteTimeouts.current but never
clears them on unmount; add a useEffect cleanup that iterates
deleteTimeouts.current, calls clearTimeout for each stored timeoutId and deletes
the entries to prevent state updates after unmount. Locate deleteTimeouts,
handleDeleteSwipe, and the optimistic state setters (setHiddenExpenses,
setExpenses) and ensure the cleanup only clears the pending timers
(clearTimeout(timeoutId)) and removes them from deleteTimeouts.current to avoid
memory leaks and React warnings.
| const handleUndoGlobal = () => { | ||
| Object.keys(deleteTimeouts.current).forEach(id => { | ||
| clearTimeout(deleteTimeouts.current[id].timeoutId); | ||
| setHiddenExpenses((prev) => prev.filter((expId) => expId !== id)); | ||
| }); | ||
| deleteTimeouts.current = {}; | ||
| setSnackbarVisible(false); | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider UX for multiple pending deletions.
The global undo restores all pending deletions, but the snackbar only shows the most recent deletion message. If a user rapidly deletes multiple expenses, pressing "Undo" will restore all of them, which may be unexpected.
💡 Optional enhancement: Show count when multiple items pending
+ const pendingCount = Object.keys(deleteTimeouts.current).length;
+
const handleUndoGlobal = () => {
Object.keys(deleteTimeouts.current).forEach(id => {
clearTimeout(deleteTimeouts.current[id].timeoutId);
setHiddenExpenses((prev) => prev.filter((expId) => expId !== id));
});
deleteTimeouts.current = {};
setSnackbarVisible(false);
};And update snackbar message when multiple items:
setSnackbarMessage(pendingCount > 1
? `Deleted ${pendingCount} expenses`
: `Deleted "${item.description}"`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mobile/screens/GroupDetailsScreen.js` around lines 277 - 284, The current
global undo (handleUndoGlobal) restores all pending deletions while the snackbar
only reflects the most recent deletion; update the UX by computing pendingCount
= Object.keys(deleteTimeouts.current).length wherever you set the snackbar and
call setSnackbarMessage(pendingCount > 1 ? `Deleted ${pendingCount} expenses` :
`Deleted "${item.description}"`), so the snackbar accurately reflects multiple
pending deletes; keep handleUndoGlobal logic that iterates
deleteTimeouts.current to clear timeouts and call setHiddenExpenses, then clear
deleteTimeouts.current and setSnackbarVisible(false) so the message and undo
behavior are consistent (use the symbols handleUndoGlobal,
deleteTimeouts.current, setHiddenExpenses, setSnackbarMessage,
setSnackbarVisible to locate code).
Implemented swipe-to-delete functionality for expenses on the Group Details screen in the mobile app. - Created an optimistic deletion flow with a 5-second undo Snackbar using `react-native-paper`. - Wrapped expense cards with `Swipeable` from `react-native-gesture-handler`. - Added the `deleteExpense` function to the API client (`mobile/api/groups.js`). - Created `mobile/babel.config.js` to support the required `react-native-reanimated` plugin. - Added necessary ref-based timeout management to prevent state mismatch during rapid interactions. - Updated `.Jules/todo.md` and `.Jules/changelog.md` to reflect the completed task. Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mobile/package.json`:
- Around line 25-27: The package.json currently pins
react-native-gesture-handler and react-native-reanimated to versions newer than
Expo SDK 54 supports; update the dependencies for react-native-gesture-handler
and react-native-reanimated to the SDK-54-compatible versions (e.g.,
react-native-gesture-handler ~2.28.0 and react-native-reanimated ~4.1.1) or run
`npx expo install react-native-gesture-handler react-native-reanimated` to let
Expo resolve compatible versions, then update package.json and reinstall to
ensure lockfile/modules match; leave react-native-paper unchanged unless `npx
expo install` suggests a different compatible version.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e72302d4-9939-4e8f-a0e6-5f639806bb9a
⛔ Files ignored due to path filters (1)
mobile/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (1)
mobile/package.json
| "react-native-gesture-handler": "^2.31.0", | ||
| "react-native-paper": "^5.14.5", | ||
| "react-native-reanimated": "^4.3.0", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verifies dependency compatibility against current Expo SDK without mutating app code.
npx expo install --check react-native-gesture-handler react-native-reanimatedRepository: Devasy/splitwiser
Length of output: 1390
🏁 Script executed:
# First, let's find and read the mobile/package.json file
find . -name "package.json" -path "*/mobile/*" | head -5Repository: Devasy/splitwiser
Length of output: 82
🏁 Script executed:
# Check the actual content of mobile/package.json around the relevant lines
cd mobile && cat -n package.json | sed -n '20,35p'Repository: Devasy/splitwiser
Length of output: 647
🏁 Script executed:
# Get the full dependency section to understand the context
cd mobile && cat package.json | jq '.dependencies' 2>/dev/null || cat package.json | grep -A 50 '"dependencies"'Repository: Devasy/splitwiser
Length of output: 735
🌐 Web query:
Expo SDK 54 react-native-gesture-handler compatible version
💡 Result:
The compatible version of react-native-gesture-handler for Expo SDK 54 is ~2.28.0. Install it using npx expo install react-native-gesture-handler which will resolve to the correct compatible version for your Expo SDK.
Citations:
- 1: https://docs.expo.dev/versions/v54.0.0/sdk/gesture-handler
- 2: https://medium.com/@shanavascruise/upgrading-to-expo-54-and-react-native-0-81-a-developers-survival-story-2f58abf0e326
- 3: https://stackoverflow.com/questions/79775297/typeerror-useanimatedgesturehandler-is-not-a-function-after-upgrading-to-expo-s
🌐 Web query:
Expo SDK 54 react-native-reanimated compatible version
💡 Result:
The compatible version of react-native-reanimated for Expo SDK 54 is ~4.1.1, along with react-native-worklets 0.5.1. Use npx expo install react-native-reanimated to install the correct bundled version. Reanimated v4 requires the New Architecture (enabled by default in SDK 54), though v3 can still be used with Legacy Architecture if needed.
Citations:
- 1: fix(companion): update react-native-reanimated to Expo 54 compatible version calcom/cal.com#25741
- 2: https://docs.swmansion.com/react-native-reanimated/docs/guides/building-on-windows/
- 3: Expo 54 and Reanimated 4.1.0 support nandorojo/moti#391
- 4: Reanimated / worklets version mismatch in Expo SDK 54 – confusion about dependency ownership software-mansion/react-native-reanimated#8778
- 5: https://www.reddit.com/r/expo/comments/1nq5jpq/react_native_reanimated_worklets_issue_after/
- 6: Expo SDK 54: TypeError: Object is not a function at react-native-worklets/src/threads.ts 👉 TypeError in react-native-worklets (worked in SDK 53) software-mansion/react-native-reanimated#8880
🌐 Web query:
react-native-gesture-handler 2.31.0 react-native 0.81 compatibility
💡 Result:
Yes, react-native-gesture-handler version 2.31.0 is compatible with React Native 0.81. Official documentation states that react-native-gesture-handler supports the three latest minor releases of React Native, which includes 0.81 given the current versions up to 0.84. The library added explicit support for React Native 0.81 via pull request #3607 (merged August 2025), and subsequent releases like 2.31.0 (April 2026) build on this. The GitHub repo compatibility table shows versions from 2.28.0+ support React Native 0.79.0+, placing 0.81 well within the supported range. Issues reporting problems with 0.81 were resolved as user configuration errors, not library incompatibilities.
Citations:
- 1: https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation/
- 2: https://www.npmjs.com/package/react-native-gesture-handler
- 3: https://github.com/software-mansion/react-native-gesture-handler
- 4: 0.81.0 not supported software-mansion/react-native-gesture-handler#3675
- 5: Support
react-native0.81 software-mansion/react-native-gesture-handler#3607
🌐 Web query:
react-native-reanimated 4.3.0 compatibility with react-native 0.81
💡 Result:
Yes, react-native-reanimated 4.3.0 is compatible with react-native 0.81. It requires the React Native New Architecture (Fabric/Bridgeless). The official compatibility table confirms support for RN 0.81 with Reanimated 4.3.x (marked "yes"). The 4.3.0 release bumped the minimum RN version to 0.81. Reanimated 4.x supports only the three latest RN versions at release time and the New Architecture exclusively.
Citations:
- 1: https://docs.swmansion.com/react-native-reanimated/docs/guides/compatibility/
- 2: https://www.npmjs.com/package/react-native-reanimated/v/4.3.0
- 3: https://github.com/software-mansion/react-native-reanimated/releases/tag/4.3.0
Use Expo SDK 54-compatible versions for native modules.
Lines 25 and 27 specify versions newer than those tested with Expo SDK 54. The official Expo documentation recommends react-native-gesture-handler ~2.28.0 and react-native-reanimated ~4.1.1 for SDK 54 compatibility. Using npx expo install will resolve to the correct compatible versions.
Suggested change
- "react-native-gesture-handler": "^2.31.0",
+ "react-native-gesture-handler": "~2.28.0",
- "react-native-reanimated": "^4.3.0",
+ "react-native-reanimated": "~4.1.1",📝 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.
| "react-native-gesture-handler": "^2.31.0", | |
| "react-native-paper": "^5.14.5", | |
| "react-native-reanimated": "^4.3.0", | |
| "react-native-gesture-handler": "~2.28.0", | |
| "react-native-paper": "^5.14.5", | |
| "react-native-reanimated": "~4.1.1", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mobile/package.json` around lines 25 - 27, The package.json currently pins
react-native-gesture-handler and react-native-reanimated to versions newer than
Expo SDK 54 supports; update the dependencies for react-native-gesture-handler
and react-native-reanimated to the SDK-54-compatible versions (e.g.,
react-native-gesture-handler ~2.28.0 and react-native-reanimated ~4.1.1) or run
`npx expo install react-native-gesture-handler react-native-reanimated` to let
Expo resolve compatible versions, then update package.json and reinstall to
ensure lockfile/modules match; leave react-native-paper unchanged unless `npx
expo install` suggests a different compatible version.
Implemented a complete swipe-to-delete system for expenses on the mobile Group Details screen, addressing the requested task in
todo.md.Changes Made:
deleteExpense(groupId, expenseId)tomobile/api/groups.jsbased on the backend Fast API route (/groups/{group_id}/expenses/{expense_id}).mobile/screens/GroupDetailsScreen.jsto wrap the existing expense cards withSwipeablefromreact-native-gesture-handler, allowing a right-to-left swipe to reveal a "Delete" action.hiddenExpensesstate), and aSnackbarappears offering an "Undo" button.useRefdictionary (deleteTimeouts) to track active 5-second countdowns for each deleted item. This ensures that rapid deletions don't cause state collisions, and tapping "Undo" cleanly clears the specific pending API calls.mobile/babel.config.jsto explicitly register thereact-native-reanimated/plugin, which is required for gesture handler animations to work smoothly..Jules/todo.mdand.Jules/changelog.md.PR created automatically by Jules for task 4411574858630447702 started by @Devasy23
Summary by CodeRabbit