I’m facing a weird error in my Firebase update operations though it doesn’t happen every time but happens sometimes, the update operation sometimes overlaps with a different entry in my Firebase collection.
Explaining everything with in detail here
Here is the image of my user entries in my Firebase Authentication ( I’m using signinwithEmailAndPassword
functionality )
Now here is the Firestore entry of my first user whose ID is dv6d...
with it’s data
Now when I applied update operation on my entery with user id pzF1..
my firestore collection for that ID is updated with the entries of user with id dv6d..
and the entires for that particular use has lost
here is the screenshot of that
I don’t know why it is happening
Here is the code which I worte for the update operations
This file displays all the users inside the dashboard
import React, { useState, useEffect, useMemo } from "react";
import PageTitle from "../components/Typography/PageTitle";
import {
TableBody,
TableContainer,
Table,
TableHeader,
TableCell,
TableRow,
TableFooter,
Badge,
Pagination,
Button,
} from "@windmill/react-ui";
import { EditIcon, TrashIcon } from "../icons";
import { useAdmin } from "../context/AdminContext";
import { EditUser } from "../components/Dashboard/User";
import ThemedSuspense from "../components/ThemedSuspense";
import Search from "../helpers/Search";
import useSearch from "../hooks/UseSearch";
function User() {
const { deleteData, getData } = useAdmin();
const [loading, setLoading] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [userData, setUserData] = useState([]);
const [page, setPage] = useState(1);
const [selectedUser, setSelectedUser] = useState(null);
const resultsPerPage = 10;
const searchOptions = {
keys: [
"fullName",
"email",
"phone",
"facilityName",
"facilityAddress",
"status",
"formId",
],
threshold: 0.3,
};
const { results, search, searchPattern } = useSearch(userData, searchOptions);
const paginatedUsers = useMemo(() => {
const startIndex = (page - 1) * resultsPerPage;
return results.slice(startIndex, startIndex + resultsPerPage);
}, [results, page]);
const totalResults = results.length;
const totalPages = Math.ceil(totalResults / resultsPerPage);
const handleSearchChange = (value) => {
search(value);
setPage(1);
};
const onPageChange = (newPage) => {
setPage(newPage);
};
const handleSelectedUser = (user) => {
setSelectedUser(user);
setIsModalOpen(true);
};
const handleDeleteUser = (id) => {
if (window.confirm("Are you sure you want to delete this User?")) {
deleteData(id, "Users", setLoading);
}
};
useEffect(() => {
const loadUserData = async () => {
setLoading(true);
try {
await getData("Users", setLoading, setUserData);
} catch (error) {
console.error("Error loading user data:", error);
} finally {
setLoading(false);
}
};
loadUserData();
}, [getData]);
return (
<>
<div className="flex items-center flex-wrap lg:gap-5 justify-between">
<PageTitle>Users</PageTitle>
<Search
searchQuery={searchPattern}
setSearchQuery={handleSearchChange}
/>
</div>
{loading ? (
<ThemedSuspense />
) : (
<TableContainer className="mb-8">
<Table>
<TableHeader>
<tr>
<TableCell>Full Name</TableCell>
<TableCell>FormId</TableCell>
<TableCell>Email</TableCell>
<TableCell>Phone</TableCell>
<TableCell>Facility Name</TableCell>
<TableCell>Facility Address</TableCell>
<TableCell>User Status</TableCell>
<TableCell>Status</TableCell>
<TableCell>Images</TableCell>
<TableCell>Actions</TableCell>
</tr>
</TableHeader>
<TableBody>
{paginatedUsers.map((user, i) => (
<TableRow key={i}>
<TableCell>
<span className="text-sm capitalize">{user?.fullName}</span>
</TableCell>
<TableCell>
<span className="text-sm capitalize">{user?.formId}</span>
</TableCell>
<TableCell>
<span className="text-sm lowercase">{user?.email}</span>
</TableCell>
<TableCell>
<span className="text-sm capitalize">{user?.phone}</span>
</TableCell>
<TableCell>
<span className="text-sm capitalize">
{user?.facilityName}
</span>
</TableCell>
<TableCell>
<span>{user?.facilityAddress}</span>
</TableCell>
<TableCell>
{user?.isVerified ? (
<Badge type="success">verified</Badge>
) : (
<Badge type="danger">Not verified</Badge>
)}
</TableCell>
<TableCell>
{user?.status === "verified" && (
<Badge type="success">{user?.status}</Badge>
)}
{user?.status === "rejected" && (
<Badge type="danger">{user?.status}</Badge>
)}
{user?.status === "underprocessing" && (
<Badge type="warning">{user?.status}</Badge>
)}
{user?.status !== "verified" &&
user?.status !== "rejected" &&
user?.status !== "underprocessing" && (
<Badge>{user?.status}</Badge>
)}
</TableCell>
<TableCell>
<Button>
<a
href={user?.images[0]}
target="_blank"
rel="noopener noreferrer"
>
Check Images
</a>
</Button>
</TableCell>
<TableCell>
<div className="flex items-center space-x-4">
<Button
layout="link"
size="icon"
aria-label="Edit"
onClick={() => handleSelectedUser(user)}
>
<EditIcon className="w-5 h-5" aria-hidden="true" />
</Button>
<Button
layout="link"
size="icon"
aria-label="Delete"
onClick={() => handleDeleteUser(user?.id)}
>
<TrashIcon className="w-5 h-5" aria-hidden="true" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<TableFooter>
<Pagination
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onChange={onPageChange}
label="Table navigation"
/>
</TableFooter>
</TableContainer>
)}
{selectedUser && (
<EditUser
user={selectedUser}
isModalOpen={isModalOpen}
closeModal={() => setIsModalOpen(false)}
/>
)}
</>
);
}
export default User;
Here is the EditUser component file
import React, { useState } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
Input,
Label,
Select,
} from "@windmill/react-ui";
import { useAdmin } from "../../context/AdminContext";
import { sendMail } from "../../helpers/email";
export const EditUser = ({ user, isModalOpen, closeModal }) => {
const [loading, setLoading] = useState(false);
const { updateData } = useAdmin(); // updateData is a function from the AdminContext
const [updatedData, setUpdatedData] = useState({ ...user });
const handleSave = async () => {
await updateData(user?.id, "Users", updatedData, setLoading);
if (user.status !== updatedData.status) {
if (updatedData.status.toLowerCase() === "verified") {
sendMail(user?.email, "verified");
} else if (updatedData.status.toLowerCase() === "rejected") {
sendMail(user?.email, "rejected");
} else if (updatedData.status.toLowerCase() === "underprocessing") {
sendMail(user?.email, "underprocessing");
} else if (updatedData.status.toLowerCase() === "qrcodeprocessing") {
sendMail(user?.email, "qrcodeprocessing");
} else if (updatedData.status.toLowerCase() === "qrcodecompleted") {
sendMail(user?.email, "qrcodecompleted");
}
closeModal();
}
};
const handleChange = (e) => {
setUpdatedData({ ...updatedData, [e.target.name]: e.target.value });
};
return (
<Modal isOpen={isModalOpen} onClose={closeModal}>
<ModalHeader>Edit user</ModalHeader>
<ModalBody className="space-y-4">
<div className="grid md:grid-cols-2 grid-cols-1 gap-3">
<Label>
<span>Full Name</span>
<Input
className="mt-1"
placeholder="Ex. John"
defaultValue={user?.fullName}
name="fullName"
onChange={handleChange}
/>
</Label>
<Label>
<span>FormId</span>
<Input
className="mt-1"
placeholder="Ex. John"
defaultValue={user?.formId}
name="formId"
onChange={handleChange}
/>
</Label>
</div>
<div className="grid md:grid-cols-2 grid-cols-1 gap-3">
<Label>
<span>Facility Name</span>
<Input
className="mt-1"
name="facilityName"
defaultValue={user?.facilityName}
onChange={handleChange}
/>
</Label>
<Label>
<span>Facility Address</span>
<Input
className="mt-1"
placeholder="facilityAddress"
name="facilityAddress"
defaultValue={user?.facilityAddress}
onChange={handleChange}
/>
</Label>
</div>
<div className="grid md:grid-cols-2 grid-cols-1 gap-3">
<Label>
<span>Phone</span>
<Input
className="mt-1"
placeholder="number"
name="phone"
defaultValue={user?.phone}
onChange={handleChange}
/>
</Label>
<Label>
<span>Status</span>
<Select
className="mt-1"
name="status"
onChange={handleChange}
defaultValue={user?.status}
>
<option value="verified">Verified</option>
<option value="underprocessing">Under Processing</option>
<option value="qrcodeprocessing">QRCode Processing</option>
<option value="qrcodecompleted">QRCode Completed</option>
<option value="rejected">Rejected</option>
</Select>
</Label>
</div>
</ModalBody>
<ModalFooter>
<div className="hidden sm:block">
<Button layout="outline" onClick={closeModal} disabled={loading}>
Cancel
</Button>
</div>
<div className="hidden sm:block" onClick={handleSave}>
<Button>Save</Button>
</div>
<div className="block w-full sm:hidden">
<Button block size="large" layout="outline" onClick={closeModal}>
Cancel
</Button>
</div>
<div className="block w-full sm:hidden">
<Button block size="large" disabled={loading} onClick={handleSave}>
Save
</Button>
</div>
</ModalFooter>
</Modal>
);
};
here is the update data funtion from my AdminContext
const updateData = async (docId, dbName, updatedData, setLoading) => {
try {
setLoading(true);
const docRef = doc(db, dbName, docId);
await runTransaction(db, async (transaction) => {
const docSnapshot = await transaction.get(docRef);
if (!docSnapshot.exists()) {
throw new Error("Document does not exist!");
}
const { id, ...dataWithoutId } = updatedData;
// Perform the update within the transaction
transaction.update(docRef, dataWithoutId);
});
toastSuccess(`${dbName} Updated Successfully`);
} catch (error) {
toastError(error.message);
setError(error.message);
} finally {
setLoading(false);
}
};
I’m using firebase transaction to ensure atomicity but still the update operations is overlapping my enteries
Please let me know if there is any confusion.