如何正确使用Promise,Firebase和React的UseEffect挂钩?

时间:2020-06-05 18:05:09

标签: javascript reactjs firebase google-cloud-firestore react-hooks

基本上,我想要实现的是仅在不再定义了变量之后才运行一些代码。但是,我对使用promise等待firebase响应以及使用React的useEffect挂钩感到非常困惑。

我做了一些研究,也许听众是我的答案?不过,我真的不知道如何实现它们。

const weekday = moment().isoWeekday() 

export const TimeGrid = () => {
  const { userObject } = useContext(Context)

  //(1) I wish to use the below to generate a list of times
  const [LIST_OF_TIMES_FROM_DATABASE, SET_LIST_OF_TIMES_FROM_DATABASE] = useState([])

  let businessId

  function getBusinessId() {
    if (userObject) businessId = userObject.businessId
  }

  getBusinessId()

  //defines function that will run below inside of first useEffect
  //also where the list of times is supposed to be set
  function getWorkingHoursAccordingToDayOfTheWeek(day, snapshot) {
    const workingHours = []
    const ALL_AVAILABLE_TIMES = []

    workingHours.push(snapshot.data().beginWeek)
    workingHours.push(snapshot.data().endWeek)

    const [begin, ending] = workingHours
    let bgn = moment(begin, 'HH:mm')
    const end = moment(ending, 'HH:mm')
    let i = 0
    while (bgn <= end) {
      ALL_AVAILABLE_TIMES.push({
        id: i,
        type: 'time',
        day: 'today',
        time: bgn.format('HHmm'),
      })
      bgn = bgn.add(30, 'minutes')
      i++
    }
    SET_LIST_OF_TIMES_FROM_DATABASE(ALL_AVAILABLE_TIMES) //(2) This is where I set the list, which won't work on its 1st run
  }

  useEffect(() => {
    async function getTimesFromDB() {
      const approved = await approvedBusinessService.doc(businessId)
      const snapshotApproved = await approved.get()
      if (snapshotApproved.exists) {
        getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotApproved)
        return
      } else {
        const pending = await businessPendingApprovalService.doc(businessId)
        const snapshotPending = await pending.get()
        if (snapshotPending.exists) {
          getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotPending)
        }
      }
      return
    }

    getTimesFromDB()
  }, [userObject])

  const allBookedTimes = allBookings.map(element => element.time)
  //the below used to be 'listOfTimesJSON.map(item => item.time)' and used to work as it was a static JSON file
  //(3) this is sometimes undefined... so all of the code below which relies on this will not run
  const listOfTimes = LIST_OF_TIMES_FROM_DATABASE.map(item => item.time)

  const [counts, setCounts] = useState({})
  useEffect(() => {
    const counts = {}
    //(4) below will not work as listOfTimes won't exist (because of 'LIST_OF_TIMES_FROM_DATABASE' being undefined)
    listOfTimes.forEach(x => {
      counts[x] = (counts[x] || 0) + 1 

      if (allBookedTimes.includes(x)) counts[x] = counts[x] - 1
    })
    setCounts(counts)
  }, [])

  const sortedListOfTimesAndCount = Object.keys(counts).sort() //(5) undefined, because 'counts' was undefined.
  const displayListOfTimes = sortedListOfTimesAndCount.map(
    time =>
      time > currentTime && (
        <>
          <li>
            {time}
          </li>
        </>
      ),
  )

  return (
    <div>
      <ul>{displayListOfTimes}</ul>
    </div>
  )
}

2 个答案:

答案 0 :(得分:1)

根据代码中的注释,您的步骤3,4,5取决于LIST_OF_TIMES_FROM_DATABASE 也是不按顺序

您期望它按顺序执行,

1)首先,它将执行useEffect之外的所有事物,并且state发生变化时都将依赖它,因此请记住这一点

2)useEffect(() => {...},[])将在组件安装后立即被调用,因此在大多数情况下,您会以listOfTimes的身份获得[],因为那时API调用可能正在请求服务器中的数据。

3)很少有const [counts, setCounts] = useState({})useEffect(() => {...} , [])之类的不必要的东西,这是对功能的过度使用。

有时候您可以简单地做这些事情,但随着这些事情变得复杂起来


解决方案:

因此,您可以做的就是将所有代码放入displayListOfTimes函数中,然后检查LIST_OF_TIMES_FROM_DATABASE是否可用,然后再继续执行步骤3、4、5。因此,您将永远不会得到LIST_OF_TIMES_FROM_DATABASE未定义

注意:并且根据代码LIST_OF_TIMES_FROM_DATABASE可以为空 array或带有data的数组,但永远不会包含undefined,如果您不确定,则可以在设置状态时进行检查

const weekday = moment().isoWeekday() 

export const TimeGrid = () => {
  const { userObject } = useContext(Context)

  //(1) I wish to use the below to generate a list of times
  const [LIST_OF_TIMES_FROM_DATABASE, SET_LIST_OF_TIMES_FROM_DATABASE] = useState([])

  let businessId

  function getBusinessId() {
    if (userObject) businessId = userObject.businessId
  }

  getBusinessId()

  //defines function that will run below inside of first useEffect
  //also where the list of times is supposed to be set
  function getWorkingHoursAccordingToDayOfTheWeek(day, snapshot) {
    const workingHours = []
    const ALL_AVAILABLE_TIMES = []

    workingHours.push(snapshot.data().beginWeek)
    workingHours.push(snapshot.data().endWeek)

    const [begin, ending] = workingHours
    let bgn = moment(begin, 'HH:mm')
    const end = moment(ending, 'HH:mm')
    let i = 0
    while (bgn <= end) {
      ALL_AVAILABLE_TIMES.push({
        id: i,
        type: 'time',
        day: 'today',
        time: bgn.format('HHmm'),
      })
      bgn = bgn.add(30, 'minutes')
      i++
    }
    SET_LIST_OF_TIMES_FROM_DATABASE(ALL_AVAILABLE_TIMES) //(2) This is where I set the list, which won't work on its 1st run
  }

  useEffect(() => {
    async function getTimesFromDB() {
      const approved = await approvedBusinessService.doc(businessId)
      const snapshotApproved = await approved.get()
      if (snapshotApproved.exists) {
        getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotApproved)
        return
      } else {
        const pending = await businessPendingApprovalService.doc(businessId)
        const snapshotPending = await pending.get()
        if (snapshotPending.exists) {
          getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotPending)
        }
      }
      return
    }

    getTimesFromDB()
  }, [userObject])


  const displayListOfTimes = () => { // <----- Start - HERE
    if(LIST_OF_TIMES_FROM_DATABASE && LIST_OF_TIMES_FROM_DATABASE.length) {
      const counts = {}
      const allBookedTimes = allBookings.map(element => element.time)

      //(3) ------ this will never be undefined
      const listOfTimes = LIST_OF_TIMES_FROM_DATABASE.map(item => item.time)

      //(4) ------ now it will work always
      listOfTimes.forEach(x => {
        counts[x] = (counts[x] || 0) + 1 

        if (allBookedTimes.includes(x)) counts[x] = counts[x] - 1
      })

      //(5) ------ now it will work 
      const sortedListOfTimesAndCount = Object.keys(counts).sort() 
      return sortedListOfTimesAndCount.map(
        time =>
          time > currentTime && (
            <>
              <li>
                {time}
              </li>
            </>
          ),
      )
    } else {
      return <li>Nothing to show for now</li>
    }
  }

  return (
    <div>
      <ul>{displayListOfTimes}</ul>
    </div>
  )
}

答案 1 :(得分:0)

问题是因为在获取请求后listOfTimes值更新时,未调用使用listOfTimes的useEffect。

您可以在useEffect中定义listOfTimes并添加对LIST_OF_TIMES_FROM_DATABASE的依赖项

  const [counts, setCounts] = useState({})
  useEffect(() => {
    // Defining it inside because if we don't and add allBookedTimes as a dependency then it will lead to an infinite look since references will change on each re-render

    const allBookedTimes = allBookings.map(element => element.time)
    const listOfTimes = LIST_OF_TIMES_FROM_DATABASE.map(item => item.time)
    const counts = {}
    listOfTimes.forEach(x => {
      counts[x] = (counts[x] || 0) + 1 

      if (allBookedTimes.includes(x)) counts[x] = counts[x] - 1
    })
    setCounts(counts)
  }, [LIST_OF_TIMES_FROM_DATABASE, allBookings]); // Add both dependencies. This will ensure that you update counts state when the state for times changes