Reputation: 432
PROBLEM: I have a MERN application that is has a model with a couple of other models in it. The problem that I figured out later is that it saves the _id of the object and not the actual object in the model when you do this
const checkoutHistory = new Schema({
book: { type: mongoose.Schema.Types.ObjectId, ref: 'books',required: true },
checkoutCopiesNum: {type: Number, required: true},
profChosen: { type: mongoose.Schema.Types.ObjectId, ref: 'prof', required: true },
dueDate: {type: String, required: true}
})
The book: part of the object when retreived will be an id some string like "DKKLDFJhdkghhe839kdd" whatever. This is fine because then I guess I can make an API call in the react app later to search for this book. Is this the correct way to do it though?
The other way that I thought of was in the actual endpoint that retrieves the data was to call the findByID functions and set that data. It didn't work though here is the code for that:
const checkoutHistoryMiddle = async (req, res, next) => {
try {
//get the body of the request
const body = req.body
//check for data
if(!body){
return res.status(400).json({
success: false,
error: 'no body given'
})
}
const history = new CheckoutHist(body)
console.log(history)
// await Book.findById({_id: history.book}, (err, book) => {
// history.book = book
// })
// await Prof.findById({_id: history.profChosen}, (err, prof) => history.profChosen = prof)
console.log(history)
history.save().then(() => next()).catch(error => {
return res.status(400).json({
success: false,
message: error,
msg: "checkout save failed"
})
})
} catch (error) {
res.status(400).json({
success: false,
message: error,
msg: "checkoutHist failed"
})
}
}
I commented out the part I was talking about because well, it didn't work. It still saved the id instead of the object. Which like I said is fine. I gave my other idea a go and decided to do the calls inside the react app.
So I first got the array of objects from the schema provided above like this:
const [bookHist, setBookHist] = useState()
useEffect( () => {
const getHistory = async () => {
api.getCheckoutHist().then(hist => {
setBookHist(hist.data.data.filter((data) => data.book === props.book_id))
})
}
getHistory()
}, [])
This will create an array of objects in bookHist that looks like this
[{_id: "DKJFDKJDKLFJSL", book: "LDKhgajgahgelkji8440skg", checkoutCopiesNum: 3, profChosen: "gjellkdh39gh39kal930alkdfj", dueDate: "11/11/11"}, {...}]
so the next step would be to take each item in the array and get the id to search the database with so api.findProfByID(bookHist[0].profChosen)
then I would need to update the state of bookHist somehow only that item without effect the other items in the array.
The questions I have are what is the best way to update one item in the array state? How do I make so many api calls? how do I make sure that they are waited on so that the state actually changes once the calls complete?
Here are things I have tried so far:
useEffect(() => {
bookHist.map(async bHist => {
await Axios.get("http://localhost:8174/user/professor/" + bHist.profChosen).then(async prof => {
// console.log(prof)
// console.log(prof)
bHist.profChosen = prof.data.data
// setBookHist(prevStat => ({}))
// setBookHist(...bookHist, [bookHist.])
})
setBookHist(bHist)
})
}, [])
this didn't work I assume because it would not update the state because it is not waiting on the map to finish before it sets the state of bookHist
So then I searched on the internet and found a promisAll method in react like this:
useEffect(() => {
const change = async () => {
if(bookHist){
console.log("prof")
//get the prof data
// const galleries = []
await Promise.all(bookHist.map( (bHist, index) => {
return await Axios.get("http://localhost:8174/user/professor/" + bHist.profChosen);
})).then(someData => {
console.log(someData)
});
}
change()
}, [])
This also does not work for unknown reasons. It only works if it hot reloads and does not refresh. The logging actually logs something when it hot refreshes.
here is the entirety of the funcitional component:
import React, {useState, useEffect} from 'react'
import api from '../../api/index'
import Axios from 'axios'
export default function CheckoutBookHistroy(props){
const [bookHist, setBookHist] = useState()
const [histData, setHistData] = useState([{
book: {},
prof: {}
}])
useEffect( () => {
const getHistory = async () => {
api.getCheckoutHist().then(hist => {
setBookHist(hist.data.data.filter((data) => data.book === props.book_id))
})
}
getHistory()
}, [])
//i also tried this way but this resulted in an infinite loop
const [profChosen, setProfChosen] = useState()
const handleProfFind = async (id) => {
await Axios.get("http://localhost:8174/user/professor/" + id).then(prof => {
setProfChosen(prof.data.data)
})
}
return (
<div>
{
bookHist ?
bookHist.map(data => {
//need to present the prof data here for each data obj
return (
<div>Checked out {data.checkoutCopiesNum}</div>
)}) : <div>no data</div>
}
</div>
)
}
I really hope I can gain some insight into the correct way to do all of this. I must be either really close or awfully wrong. Thank you in advance!
Upvotes: 1
Views: 163
Reputation: 7670
just by looking at your code, i don't see too much issue, although your code is a bit convoluted.
some functions has no caller, ex. handleProfFind
. One suggestion, if you want to do something, just do it, no need that many functions, ex.
// assume you only want to do it once after mounting
useEffect( () => {
if (!data) {
api.getCheckoutHist().then(hist => {
// you can set your data state here
// or you can get the id inside each item, and then call more APIs
// whatever you want to do, please finish it here
}
}
}, [])
Upvotes: 2