skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. According to your last comment:

    So currently I’m using auto ID, but regNo is a field in the document used to identify certain information of a user.

    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 the regNo field holds a specific value, then you have to perform a query. In code, it should look like this:

    const queryByRegNo = await getDocs(
      query(
        collection(db, "mrisystems"),
        where("regNo", "==", "94IAD"),
      )
    );
    queryByRegNo.forEach((doc) => {
      console.log(doc.id, " => ", doc.data());
    });
    
    Login or Signup to reply.
  3. 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.

    Login or Signup to reply.
  4. 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 per regNo, with regNo 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:

    import { runTransaction } from "firebase/firestore";
    
    try {
      const regNo = "...";
      await runTransaction(db, async (transaction) => {
        const uniquenessRegNoDocRef = doc(db, "uniquenessRegNo", regNo);
        const uniquenessRegNoDoc = await transaction.get(uniquenessRegNoDocRef);
        if (uniquenessRegNoDoc.exists()) {
          throw "Cannot create two docs with same regNo!";
        }
    
        // We write to the two collections in a atomic manner
        transaction.set(uniquenessRegNoDocRef, { foo: "bar" });
        const docRef = doc(db, "mrisystems/" + id);
        transaction.set(docRef, { regNo, ... });
      });
      console.log("Transaction successfully committed!");
    } catch (e) {
      console.log("Transaction failed: ", e);
    }
    

    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:

    match /mrisystems/{docId} { 
      allow read: if ...;
      allow create : if !exists(/databases/$(database)/documents/uniquenessRegNo/$(request.resource.data.regNbr));
    }   
    
    match /uniquenessRegNo/{docId} { 
      allow read: if ...;
      allow create : if true; (to adapt as necessary)
    } 
    

    and the batched write is as follows:

    try {
      const batch = writeBatch(db);
    
      const regNbo = "...";
      const docId = doc(collection(db, "uniquenessRegNo")).id;
    
      const testUniqueFieldDocRef = doc(db, "mrisystems", docId);
      batch.set(testUniqueFieldDocRef, { regNo, ... });
    
      const uniquenessRegNoDocRef = doc(db, "uniquenessRegNo", regNo);
      batch.set(uniquenessRegNoDocRef, { bar: "foo" });
    
    
      await batch.commit()
    } catch (error) {
      const errorCode = error.code;
      const errorMessage = error.message;
      console.log(errorMessage)
    }
    

    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 the uniquenessRegNo 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 the mrisystems collection.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search