Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions app/src/components/NavBar/Points.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,35 @@ const Points: React.FC = () => {
}, []),
);

//TODO - uncomment after merging - https://github.com/selfxyz/self/pull/1363/
// useEffect(() => {
// const backupEvent = usePointEventStore
// .getState()
// .events.find(
// event => event.type === 'backup' && event.status === 'completed',
// );
const getBackupState = usePointEventStore(state => {
const backups = state.events.filter(e => e.type === 'backup');
console.log('backups', backups);
const isPending = backups.some(e => e.status === 'pending');
const isCompleted = backups.some(e => e.status === 'completed');
return {
pending: isPending,
completed: isCompleted,
started: isPending || isCompleted,
};
});

// if (backupEvent && !hasCompletedBackupForPoints) {
// setBackupForPointsCompleted();
// }
// }, [setBackupForPointsCompleted, hasCompletedBackupForPoints]);
//we show the backup success message immediately. This flips the flag to false undo the action
//and to show the backup button again.
//Another way is to show success modal here, but this ccan be delayed (as polling can be upto 32 seconds)
useEffect(() => {
if (
!getBackupState.pending &&
!getBackupState.completed &&
hasCompletedBackupForPoints
) {
setBackupForPointsCompleted(false);
}
}, [
getBackupState.completed,
getBackupState.pending,
hasCompletedBackupForPoints,
setBackupForPointsCompleted,
]);

// Track if we should check for backup completion on next focus
const shouldCheckBackupRef = React.useRef(false);
Expand Down
12 changes: 9 additions & 3 deletions app/src/screens/account/settings/CloudBackupScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ const CloudBackupScreen: React.FC<CloudBackupScreenProps> = ({
trackEvent(BackupEvents.CLOUD_BACKUP_ENABLED_DONE);

if (params?.returnToScreen) {
navigation.navigate(params.returnToScreen);
setTimeout(() => {
navigation.navigate(params.returnToScreen);
}, 500);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cleanup missing setTimeout timers to prevent memory leaks and stale navigation.

The three setTimeout calls delay navigation by 500ms but don't store the timer ID for cleanup. If the component unmounts during this delay, the navigation call will still execute, potentially causing errors or navigating from an unmounted component.

Store and clean up the timeout:

+  const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
+
+  React.useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+    };
+  }, []);

  // In handleICloudBackup:
  if (params?.returnToScreen) {
-    setTimeout(() => {
+    timeoutRef.current = setTimeout(() => {
      navigation.navigate(params.returnToScreen);
    }, 500);
  }

Apply similar changes to the other two setTimeout calls at lines 181-183 and 195-197.

Also consider documenting why the 500ms delay is necessary—this appears related to backup state synchronization but the rationale isn't clear from the code.

Also applies to: 181-183, 195-197

🤖 Prompt for AI Agents
In app/src/screens/account/settings/CloudBackupScreen.tsx around lines 132-134,
181-183 and 195-197, the setTimeout calls that delay navigation by 500ms are not
storing the timer IDs and therefore cannot be cleared on unmount; capture each
setTimeout return value into a const (e.g. const timeoutId = setTimeout(...)),
keep them in component scope (or state/ref) and clear them in the component
cleanup (useEffect return or componentWillUnmount) via clearTimeout(timeoutId);
apply this pattern to all three timeouts and add a short comment noting the
500ms rationale if still required.

}
} catch (error) {
console.error('iCloud backup error', error);
Expand Down Expand Up @@ -176,7 +178,9 @@ const CloudBackupScreen: React.FC<CloudBackupScreenProps> = ({
setTurnkeyPending(false);

if (params?.returnToScreen) {
navigation.navigate(params.returnToScreen);
setTimeout(() => {
navigation.navigate(params.returnToScreen);
}, 500);
}
} catch (error) {
if (error instanceof Error && error.message === 'already_exists') {
Expand All @@ -188,7 +192,9 @@ const CloudBackupScreen: React.FC<CloudBackupScreenProps> = ({
) {
console.log('Already backed up with Turnkey');
if (params?.returnToScreen) {
navigation.navigate(params.returnToScreen);
setTimeout(() => {
navigation.navigate(params.returnToScreen);
}, 500);
} else if (params?.nextScreen) {
navigation.navigate(params.nextScreen);
} else {
Expand Down
4 changes: 2 additions & 2 deletions app/src/stores/settingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ export const useSettingStore = create<SettingsState>()(
setTurnkeyBackupEnabled: (turnkeyBackupEnabled: boolean) =>
set({ turnkeyBackupEnabled }),
hasCompletedBackupForPoints: false,
setBackupForPointsCompleted: () =>
set({ hasCompletedBackupForPoints: true }),
setBackupForPointsCompleted: (value: boolean = true) =>
set({ hasCompletedBackupForPoints: value }),
resetBackupForPoints: () => set({ hasCompletedBackupForPoints: false }),
pointsAddress: null,
setPointsAddress: (address: string | null) =>
Expand Down
7 changes: 5 additions & 2 deletions app/src/utils/points/jobStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ export async function checkEventProcessingStatus(
// 200 means completed or failed - check the response body
if (response.status === 200) {
const data: JobStatusResponse = await response.json();
if (data.status === 'complete') {
if (data.success === true) {
return 'completed';
}
if (data.status === 'failed') {
if (data.success === false) {
if (data.message && data.message === 'Address already verified') {
return 'completed';
}
return 'failed';
}
}
Expand Down
Loading