Reputation: 5073
Basically what I'm trying to achieve is to run some code only after a variable is not undefined anymore. However, I'm feeling very confused with the use of promises to await firebase responses, as well as with the use of React's useEffect hook.
I did some research that perhaps listeners are my answer to this? I couldn't really figure out how to implement them, though.
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>
)
}
Upvotes: 2
Views: 332
Reputation: 281646
The problem is because you useEffect which uses listOfTimes
isn't being called when the listOfTimes value updates after a fetch request.
You can define listOfTimes
within the useEffect and add a dependency on 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
Upvotes: 0
Reputation: 58543
as per the comments in your code your steps 3 ,4 ,5 are dependent on LIST_OF_TIMES_FROM_DATABASE
and it's also not in sequence,
And you are expecting it to execute in the sequence,
1) First, it will execute all the thing which is outside the useEffect
and it's dependency whenever there is change in state
, so keep that in mind
2) useEffect(() => {...},[])
this will get called as soon as component mounts, so in most cases, you will get listOfTimes
as []
, because at that time API call might be in requesting data from server.
3) There are few unnecessary things like const [counts, setCounts] = useState({})
and useEffect(() => {...} , [])
, this is kind of overuse of functionality.
Sometimes you can do the things simply but with all these it becomes complicated
Solution :
So what you can do is just put all the code inside displayListOfTimes
function and check for LIST_OF_TIMES_FROM_DATABASE
if available then and only then proceed with steps 3,4,5. So you will never get LIST_OF_TIMES_FROM_DATABASE
undefined
NOTE : And as per the code
LIST_OF_TIMES_FROM_DATABASE
either be blankarray
or array withdata
but neverundefined
, and if you are getting undefined then you can check that while setting up the state
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>
)
}
Upvotes: 1