I have a code snippet basically sending data to Firestore. In the form, I’ve set a
field call "regNo" which is unique to different entries, what I’m trying to do is avoid duplicate entries if I could use the regNo as a primary key so as to alert the users on the application if it already exists, Here’s the code showing entries from the application:
import Sidebar from './../../components/sidebar/Sidebar';
import Navbar from './../../components/navbar/Navbar';
import "./new.scss"
import { useEffect, useState } from 'react';
import DriveFolderUploadIcon from '@mui/icons-material/DriveFolderUpload';
import { addDoc, collection,doc,getDoc,serverTimestamp, setDoc} from "firebase/firestore";
import { db } from '../../firebase';
import { useNavigate, useParams } from 'react-router-dom';
import { isEmpty } from 'lodash';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const New = ({title, btnUpd}) => {
const [file, setFile] = useState("");
const navigate = useNavigate();
const findUs = ["Referral Doctor", "Website", "Friends", "Facebook", "Nil"]
const statusCheck = ["married", "divorced", "single"]
const titled = ["Mr", "Mrs"]
const sexes = ["Male", "Female"]
const [userInfo, setUserInfo] = useState({
regNo: "",
title: "",
firstName: "",
lastName: "",
sex: "",
email: "",
reffBy: "",
address: "",
phoneNumber: "",
age: "",
date: "",
birth: "",
status: "",
state: "",
occupation: "",
duration: "",
finding: "",
});
// fetch id from the URL
const {userId} = useParams();
let id = userId
useEffect(() => {
let getUserDoc = async (id) => {
let docRef = doc(db, "mrisystems/" + id);
let docSnap = await getDoc(docRef);
console.log('getUserDoc() received:', docSnap);
if (docSnap.exists) {
let theData = docSnap.data();
console.log('getUserDoc() setting userInfo:', theData);
setUserInfo(theData);
}
}
if (id) {
getUserDoc(id);
console.log("IdUser", id)
} else {
console.log('useEffect() -- no ID value');
}
}, [id]);
const handleInput = (e) => {
const {name, value} = e.target;
setUserInfo({...userInfo, [name]: value});
}
// Adding and Updating Existing Records....
const handleAdd = async(e) => {
e.preventDefault();
if(isEmpty(id)){
await addDoc(collection(db, "mrisystems"), {
...userInfo,
timeStamp: serverTimestamp(),
});
toast.error("User Already Exists");
} else {
await setDoc(doc(db, "mrisystems/" + id), {
...userInfo,
timeStamp: serverTimestamp(),
});
}
navigate("/users")
}
What I tried to do was update the condition to check also if the "regNo" state is null, here’s the code snippet:
// Adding and Updating Existing Records....
const handleAdd = async(e) => {
e.preventDefault();
if(isEmpty(id) && userInfo.regNo===null){
await addDoc(collection(db, "mrisystems "), {
...userInfo,
timeStamp: serverTimestamp(),
});
toast.error("User Already Exists");
} else {
await setDoc(doc(db, "mrisystems/" + id), {
...userInfo,
timeStamp: serverTimestamp(),
});
}
navigate("/users")
}
4
Answers
Firestore doesn’t have concept of primary key and unique values like SQL. If it’s just a sting value, use it as your
documentId
, which is guaranteed to be unique.In all other cases, you may want to create a string with unique combination of fields and use that as
documetId
or check the existance of duplicate field by querying firestore database before adding new entry.According to your last comment:
I understand the ID of the document is autogenerated, and the
regNo
is a field inside the document. In this case, to know if a collection already contains a document where theregNo
field holds a specific value, then you have to perform a query. In code, it should look like this:You must use a custom ID that is either the regNo, or some sort of hash that accumulates the essential data.
if you have more than one unique entry, you must make a query from the client and validate it with a cloud function to ensure that it is truly unique.
an alternative is to keep a key/value pair in realtime database to track what regNo is linked too without dedicating a whole document to it.
Since you use the Firebase JS Client SDK (you tag the question with
react
) you have to use another collection where you create a document perregNo
, withregNo
being the document ID.Then you have two possibilities:
1/ Use a Transaction:
In this Transaction you check if the document exists in the other collection (let’s call it
uniquenessRegNo
) before writing the doc:2/ Use a batched write and specific security rules:
Same philosophy than solution 1: We write to the two collections in a atomic way (with a batched write) but here we do the check for the doc in the
uniquenessRegNo
collection through a security rule.A batched write will ensure that multiple documents writes completes atomically and therefore if a document with ID = regNbr exists in the
uniquenessRegNo
collection the two writes will fail.The security rules shall be as follows:
and the batched write is as follows:
HOWEVER, for both the proposed above approaches, there is the possibility that users in the front end bypass the Transaction or the Batched Write by only writing to the
mrisystems
collection (without checking theuniquenessRegNo
collection).So if you want to prevent this possibility you’ll need to implement the Transaction in a Callable Cloud Function that you call from the front-end.
In the Cloud Function, since you use the Admin SDK it is possible to execute a Query in the Transaction with the
get()
method (which is not possible with the client SDKs, see the doc). So you can implement the check with only themrisystems
collection.