TIL 210715

React useEffect() only run on first render with dependencies

컴포넌트 내, mount시에만 실행시키고 싶은 effect를 custom hooks로 만들었다. effect 내에서 사용되어야 하는 값들을 dependencies array에 추가해주니 array 내에 포함된 값이 변경될 때 뿐만 아니라, 컴포넌트 내 다른 state가 변경되어 re-render 될 때마다 effect가 실행되고 있었다. 🤔

이 hooks에서는 데이터를 fetch 후 action 을 dispatch 하는 로직이 있었는데 action이 불필요하게 자주 dispatch 되는 걸 보고 알 수 있었다.

그렇다고 dependencies array 를 []로 넣어주니 React가 useEffect에 dependency가 빠졌다고 필요한 것들 다 array에 추가하던지 아니면 array를 없애던지 둘 중에 하나를 하라는 warning을 보여줬다; (둘 다 내가 원하는게 아닌데..)

찾아보니 useRef, useState로 didLoad 같은 값을 만들어서 useEffect 내에서 한번만 실행되게 만드는 방법을 알려줬다. 과연 이 방법이 최선일까?

궁금증 1 dependencies array가 [] 일 때는 mount 후에 한번 로직이 실행되는 거라 이해하지만, dependecies array에 특정 값이 있을 때는 왜 컴포넌트가 re-render 될 때마다 useEffect가 실행되는 걸까?

궁금증 2 mount 시에만 redux action을 dispatch 해야 하는 경우 didLoad 같은 값을 만들어서 !didLoad 일때만 로직을 실행시키도록 하는 방법 외에 뭐가 있을까?

내가 잘못하고 있었던 점 useEffect 내에서 사용하는 값은 folder.folderId , folder.resourceList 였는데 dependencies array에 folder를 넣어주고 있었다. 제대로 위의 값들을 넣어주니 불 필요하게 useEffect가 실행되지 않았다.

// NO
const useCheck = folder => {
  useEffect(() => {
    if (folder.folderId) {
      getAddedByExtension(folder.resourceList)
    }
  }, [folder])
}

// YES!
const useCheck = folder => {
  useEffect(() => {
    if (folder.folderId) {
      getAddedByExtension(folder.resourceList)
    }
  }, [folder.folderId, folder.resourceList])
}

dependencies array에 object를 넣어주면 object의 reference를 비교해서 effect를 실행시킬지 말지 판단한다. 하지만 object가 새로 생성되었는지 아닌지는 보장하지 못한다. object 내의 몇 가지 properties 들을 사용하고 있고 그 값이 primitive type 이기 때문에 이 값들을 dependencies array에 추가해줘야 한다.

useEffect 내에서 사용하는 object 값들을 위해 object를 넘겨주지 말고, 정확한 property 들을 dependency array에 추가하자.

참고: https://www.benmvp.com/blog/object-array-dependencies-react-useEffect-hook/

firestore batch, transaction

  • transaction : 1개 이상의 문서에 대한 읽기 및 쓰기 작업
  • batch: 1개 이상의 문서에 대한 쓰기 작업의 집합

트랜잭션은 여러 개의 get() 작업과 이어서 수행되는 set(), update(), delete() 등의 여러 쓰기 작업으로 구성된다. 동시 수정의 경우 Firestore에서는 전체 트랜잭션을 다시 실행한다. 예를 들어 한 트랜잭션에서 문서를 읽고 다른 클라이언트가 그 문서를 수정하는 경우 Firestore는 해당 트랜잭션을 다시 시도한다. 그래서 트랜잭션이 항상 일관된 최신 데이터로 실행됩니다.

참고 : runTransaction

runTransaction (updateFuction : (transaction) => Promise ): Promise

const sfDocRef = db.collection('cities').doc('SF')

return db.runTransaction(transaction => {
  return transaction // transaction.get(documentRef) : DocumentSnapshot
    .get(sfDocRef)
    .then(sfDoc => {
      if (!sfDoc.exists) {
        throw 'Document does not exist'
      }

      let newPopulation = sfDoc.data().population + 1
      transaction.update(sfDocRef, { population: newPopulation })
    })
    .then(() => {
      console.log('Transaction successfully committed')
    })
})

QuerySnapshot, DocumentSnapshot

  • 여러 문서를 query 하는 경우

    const museumsQuery = db.collection('landmarks').where('type', '==', 'museum') // [where return Query](https://firebase.google.com/docs/reference/js/firebase.firestore.Query#where)
    museumsQuery.get().then(querySnapshot => {
    querySnapshot.forEach(doc => {
      console.log(doc.id, '=>', doc.data())
    })
    })
    • where 문은 Query를 return 한다. Query 에 해당하는 데이터를 보려면 Query.get() 메소드를 실행시킨다.
    • get()은 Promise QuerySnapshot을 return 한다.
    • QuerySnapshot은 0 또는 여러개의 DocumentSnapshot object를 가지고 있다.
    • 여러개의 documents는 docs property나 forEach method로 array 처럼 접근할 수 있다.
    • QueryDocumentSnapshot은 .data() 또는 .get(field) 로 데이터를 가져올 수 있다.=

Written by@Seokyung
가끔 개발 일지를 씁니다. 그리고..

GitHub