I’m encountering a challenging issue where my React component, designed to sync data with a backend, executes its sync operation twice in quick succession. This happens even though I’ve implemented several measures to prevent such duplicates.
Context:
Framework/Library Versions: React with Dexie for IndexedDB and axios for HTTP requests.
Component Goal: The SyncWorker component aims to synchronize unsynced items from IndexedDB to a backend server upon detecting an online status.
Duplicate Operation Issue: Despite measures to prevent duplicate operations, the sync action is executed twice, roughly a second apart, confirmed by backend logs and client-side observations.
Detailed Component Implementation:
import { useCallback, useEffect, useRef, useState } from "react";
import { useLiveQuery } from "dexie-react-hooks";
import axios from "axios";
import { DeviceConfiguration, db } from "../../db"; // IndexedDB setup
import uploadFile from "../helpers/uploadFile"; // Handles file uploads
import useFirebase from "../hooks/useFirebase"; // Custom hook for Firebase storage
import { base64ToFile } from "../helpers/base64ToFile"; // Utility for file conversions
function SyncWorker() {
const queryResults = useLiveQuery(() => db.configurations.toArray(), []);
const { storage } = useFirebase();
const [isOnline, setIsOnline] = useState(navigator.onLine);
const processingIdsRef = useRef<Set<number>>(new Set());
const syncItems = useCallback(async (unsynced: DeviceConfiguration[]) => {
for (const item of unsynced) {
if (processingIdsRef.current.has(item.id!)) continue;
processingIdsRef.current.add(item.id!);
console.log("Processing id: ", item.id);
const imageAsFile = base64ToFile(item.photo, `${item.position_identifier}_${item.serial_number}_${new Date().toISOString()}`);
try {
const imageUrl = await uploadFile(storage!, imageAsFile, `jobs/${item.ongoing_job_id}/${item.position_identifier}`, imageAsFile.name);
await axios.patch(`${process.env.REACT_APP_BACKEND_URL}/ongoing-jobs/configure-device`, {
ongoing_job_id: item.ongoing_job_id,
type: item.type,
image_url: imageUrl,
position_identifier: item.position_identifier,
serial_number: item.serial_number,
});
await db.configurations.update(item.id!, { synced: true });
processingIdsRef.current.delete(item.id!);
} catch (err) {
processingIdsRef.current.delete(item.id!);
console.error("Error syncing item: ", err);
}
}
}, [storage]);
useEffect(() => {
if (!queryResults || !isOnline) return;
const unsynced = queryResults.filter(item => !item.synced);
if (unsynced.length === 0) return;
console.log("Starting sync for", unsynced.length, "items");
syncItems(unsynced);
}, [queryResults, isOnline, syncItems]);
return null;
}
export default SyncWorker;
Observations and Debugging Attempts:
- Server-Side Logs: Upon placing a log at the beginning of my route handler on the server, it prints twice for a single sync operation initiation from the client, indicating the server receives two requests.
- Client-Side Logs: The log in my React component (console.log(“Starting sync for”, unsynced.length, “items”)) prints once in the browser’s console. However, using Console Ninja (a tool that shows logs inline in VS Code), the same log appears twice, albeit spaced closely together (about a second apart).
- Measures Taken: I’ve confirmed that React Strict Mode is disabled to rule out its effect of doubling lifecycle methods for debugging purposes. Additionally, I’ve used a Set to track processing items by ID, aiming to prevent re-processing of items already in sync.
Questions:
- What could be causing the duplicate execution of the sync operation, especially given the preventive measures in place?
- How can I further diagnose or debug this issue, considering the discrepancies between different logging methods?
- Is there any aspect of the useEffect or useCallback hooks, or perhaps the way state is managed and updated, that could inadvertently trigger duplicate operations?