Setting state before render with ref to firestore

Viewed 59

I am working through a simple logical problem, but I cannot seem to have things work smoothly. Let me share my most convincing code experiment and then I'll share some thoughts.

useEffect(() => {
    firebase
        .firestore()
        .collection("someCollection")
        .orderBy("date", "desc")
        .onSnapshot(docs => {
            let documents = []
            if (canGetUpdatesFromFirestore.current) {
                docs.forEach((doc) => {
                    documents.push(doc.data())
                })

                if(documents.length > 3) {
                    documents.splice(4, 0, {questionPostId: 0})
                    documents.splice(5, 0, {questionPostId: 1})
                }
                setAllQuestions(documents)
                setUsers(documents)

            }

        })
    if (searchValue.length > 2) {
        canGetUpdatesFromFirestore.current = false;
        functions.searchForSearchVal(searchValue, "Sexuality")
            .then((result) => {
                setAllQuestions(result);
            })
    } else {
        canGetUpdatesFromFirestore.current = true
    }
}, [searchValue])

function setUsers(docs){
    let arrFinal = []
    let copyOfAllQuestions = ""
        for(let i = 0; i< docs.length; i++) {
            console.log("HERE")
            if (docs[i].postedBy) {
                docs[i].ref.get().then(userFire => {
                    copyOfAllQuestions = {
                        ...allQuestions,
                        ...{hasPremium: userFire.data().hasPremium}
                    }
                })
                arrFinal.push(copyOfAllQuestions)
            }

        }
    setAllQuestions(arrFinal)
}

Let me share some of my current state and what I am trying to accomplish. I have a that display allQuestions. Each question data has a ref to its user document in firestore. For each question I need to check if that user hasPremium. How should I go about doing that the correct way? The problem currently is that I can get the data from my Users collection through the ref, but I have to refresh my state in order for it all to show. Could someone help me get on the right path / think correctly on this one please?

1 Answers

One approach that I put forward is to embrace data denormalization. That is, rather than putting references to other documents (Users) inside of the Questions document, put all the relevant user information directly into the Questions document.

This is antithetical to SQL database approaches, but that's okay because Firestore is "NoSQL". Embrace the anti-SQL-idity!!

Essentially, in your Question document you want to copy in whatever information is required in your app when working with a Question, and avoid doing "joins" by fetching other documents. You don't need to copy in all of the User document into a Question document - just the elements needed when your app is working with a Question.

For example, maybe in the question all you need is:

question: {
    name: ...,
    type: ...,
    lastUpdated: ...,
    postedBy: {
      email: ...,
      displayName: ...,
      avatarUrl: ...,
      hasPremium: true,
    }
}

With data duplicated, you often need a mechanism to keep duplicate data up-to-date from its "source". So you might consider a Cloud Function trigger for onUpdate() of User documents, and when a relevant value is modified (email, displayName, avatarUrl, and/or hasPremium) then you would loop through all questions that are postedBy that user and update accordingly.

The rules-of-thumb here are:

  • all data needed for one screen/function in your app goes into a SINGLE document
  • NoSQL document stores are used where reads are frequent and writes are infrequent
  • NoSQL data stores (typically) do not have "joins" - so don't design your app to require them (which is what your code above is doing: joining Question and Users)
  • often you don't care about updating ALL instances of duplicated data (e.g. if a user updates their displayName today, should you update a Question they posted 3 years ago? -- different apps/business needs will give different answers)
Related