Array pushes elements already in Array despite !arry.includes - reactjs

I am trying to build an app that requires a directory. Every time I update the component, it pushes the data into the array again creating multiple copies. I tried solving that by adding an !arry.includes, but that does not stop it for adding another occurrence of the data.
const Directory = () => {
const [searchResults, setSearchResults] = useState([]);
const [loading, setLoading] = useState(false);
const { currentUser } = useContext(AuthContext);
useEffect(() => {
setLoading(true);
const data = async () => {
const q = query(collection(db, "users"), where("type", "==", "doctor"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
const data = doc.data();
if (!searchResults.includes(data)) {
searchResults.push(data);
}
});
setLoading(false);
};
data();
},[]);
console.log(searchResults);

It appears that you are trying to compare objects here which doesn't work with the array.includes() method.
In javascript
const user1 = {name : "nerd", org: "dev"};
const user2 = {name : "nerd", org: "dev"};
user1 == user2; // is false
You would need to search for something more specific or try Comparing Objects. Either way what your trying to do is tricky and you may need to explore lodash or something to solve it

I found an easier way!
useEffect(() => {
setLoading(true);
const data = async () => {
const q = query(collection(db, "users"), where("type", "==", "doctor"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
const data = doc.data();
const notInArray = searchResults.some(u => u.uid === data.uid);
console.log(notInArray);
if (notInArray === false) {
searchResults.push(data)
}
});
setLoading(false);
};
return () => {
data();
};
}, []);

Related

Map data on runtime after post request

I have three apis in all. GetAssets is the first, followed by assetsOptionsList and getAssetsLibrary. The issue I'm having is that when I post the data on getAssetsLibrary, I want to be able to present it on get Assets at runtime.Everything is working fine but i want to show assets on runtime.
I'm setting the runTime state true on get request but the problem is it works only for one time.Second time, it does not map on runtime. Actually, i want to know is there any alternative so that i can achieve the goal.
In the below code the one function is getting the assets. And i want to run the one function when the post request successfully sent.
const [images, setImages] = useState([]);
const [assetOptions, setAssetOptions] = useState([]);
const [faqOpened, setToggleFaq] = useState(false);
const [runTime, setRunTime] = useState(false)
const [assetID, setAssetID] = useState()
const [isLoading, setIsLoading] = useState(false);
const handleForm = (e) => {
const index = e.target.selectedIndex;
const el = e.target.childNodes[index]
const option = el.getAttribute('id');
setAssetID(option)
}
const formHandler = (e) => {
e.preventDefault()
let formData = new FormData();
formData.append('media', e.target.media.files[0]);
formData.append('assetListId', assetID)
formData.append('name', e.target.name.value);
console.log(Object.fromEntries(formData))
const res = axios.post('api/asset-library',
formData
).then((response) => {
showSuccessToaster(response?.data?.message)
setRunTime(true)
setToggleFaq(false)
})
.catch((error) => {
showErrorToaster(error?.response?.data?.message)
})
}
const showSuccessToaster = (response) => {
return uploadToasterSuccess.show({ message: response });
}
const showErrorToaster = (error) => {
return uploadToasterError.show({ message: error });
}
const one = async () => {
setIsLoading(true)
const data = await axios.get('api/assets').then((res) => {
return res?.data?.data
})
setImages(data)
setIsLoading(false)
}
const two = async () => {
const data = await axios.get('/api/asset-list').then((res) => {
return res?.data?.data
})
setAssetOptions(data)
}
useEffect(() => {
one()
two()
}, [runTime]);

Setting state only working after rerender

I have this Firebase Firestore query set up in my useEffect hook:
const [favorites, setFavorites] = useState([]);
const { user, setUser } = useContext(AuthenticatedUserContext);
const getUserFavorites = async () => {
const favoritesRef = collection(db, "favorites");
const q = query(favoritesRef, where("userId", "==", user.uid));
const querySnapshot = await getDocs(q);
const fetchedFavorites = [];
querySnapshot.forEach(async (document) => {
const docRef = doc(db, "receipes", document.data().recipeId);
const docSnap = await getDoc(docRef);
const data = docSnap.data();
fetchedFavorites.push(data);
});
setFavorites(fetchedFavorites);
console.log("favorites " + favorites);
};
useEffect(() => {
getUserFavorites();
}, []);
Upon first render of the page the favorites will be [] and after a re-render it will be populated. When logging within the forEach I can see that the query is working, so I suspect the async forEach being the culprit here. How can I fix this?
Yes, you should not use async function with a forEach() loop. You can use a for-of loop with the same code or use Promise.all() as shown below:
const getUserFavorites = async () => {
const favoritesRef = collection(db, "favorites");
const q = query(favoritesRef, where("userId", "==", user.uid));
const querySnapshot = await getDocs(q);
const favPromises = querySnapshot.docs.map((d) => getDoc(doc(db, "receipes", d.data().recipeId)))
const fetchedFavs = (await Promise.all(favPromises)).map((fav) => fav.data());
setFavorites(fetchedFavs);
console.log("favorites " + fetchedFavs);
};
Also checkout: Using async/await with a forEach loop for more information.

How to clone a collection and transform it into one file?

I have structure collections
users
|_DC
|_OfertDC
|_{documentID}
|_SC
|_OfertaSC
|_{documentID}
|
|_{documentID}
I want to create clone, marge of this structure in this way
const data = [
...{documentID}
ofertaSD: OfertaSC/{documentID},
ofertaDC: OfertaDC/{documentID}]
all documentsID have {userID} and this is way I want
My idea is to display it on front-end with no possibility to write; just one time in day my next's app would fetch the date.
const [user, loading, error] = useAuthState(auth);
const URC = collection(db, "Users")
const [dataUsers, setDataUsers] = useState([])
const [ofertyDC, setOfertyDC] = useState([])
useEffect(() => {
const getUsers = async (users) => {
const data = await getDocs(URC)
setDataUsers(data.docs.map((doc) => ({...doc.data(), id: doc.id})))
}
const getDC = async (users) => {
const ofertasDC = query(collectionGroup(db, 'OfertaDC'));
const data = await getDocs(ofertasDC)
data.forEach(doc =>{
const datas = data.docs.map((doc) => ({...doc.data(), id: doc.id}))
setOfertyDC(datas[0].modulyPV)
})}
getUsers();
getDC();
},[])
const [complexDatas, setComplexDatas] = useState([])
useEffect(() => {
var i;
const complexData = []
for(i=0; i = dataUsers.length; i++) {
if(dataUsers.uid === ofertyDC.uid){
complexData.push({...dataUsers[i],
ofertaDC: ofertyDC[i]})
}}
console.log("complexData", complexData)
}, [dataUsers, setDataUsers, ofertyDC, setOfertyDC])
I do not know where is an error. I can load the page but no move and, there is no any error on a console.

Getting a undefined value when trying to match fetch results to people objects

Im working on a star wars api app. I am getting an array of people objects, 10 characters. Who all are their own object with different values. However homeworld, and species are urls. So I have to fetch them and store that data to the correct place. I figured out a way to get the homeworld values to each character. However when I try to do it with species I receive undefined. Would appreciate any help this has been kind of a pain thanks ahead of time !
const [people, setPeople] = useState([]);
const [homeWorld, setHomeWorld] = useState([]);
const [species, setSpecies] = useState([]);
const [nextPageUrl, setNextPageUrl] = useState("https://swapi.dev/api/people/");
const [backPageUrl, setBackPageUrl] = useState('');
const [test, setTest] = useState([]);
const fetchPeople = async () => {
const { data } = await axios.get(nextPageUrl);
setNextPageUrl(data.next);
setBackPageUrl(data.previous);
return data.results;
}
const backPage = async () => {
const { data } = await axios.get(backPageUrl);
setCharacters(data.results);
setNextPageUrl(data.next);
setBackPageUrl(data.previous);
}
// Get People
async function getPeople() {
const persons = await fetchPeople();
const homeWorldUrl= await Promise.all(
persons.map((thing) => axios.get(thing.homeworld)),
);
const newPersons = persons.map((person) => {
return {
...person,
homeworld: homeWorldUrl.find((url) => url.config.url === person.homeworld)
};
});
const newPersons2 = newPersons.map((person) => {
return {
...person,
homeWorld: person.homeworld.data.name
};
});
setPeople(newPersons2);
}
// Get Species
async function getSpecies() {
const persons = await fetchPeople();
const speciesUrl = await Promise.all(
persons.map((thing) => axios.get(thing.species)),
);
const newSwapi = persons.map((person) => {
return {
...person,
species: speciesUrl.find((info) => info.data.url === person.species)
};
});
setTest(newSwapi);
// const newPersons2 = newPersons.map((person) => {
// return {
// ...person,
// homeWorld: person.homeworld.data.name
// };
// });
}
useEffect(() => {
getPeople();
getSpecies();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); ```
Species property of person is a array, so your getSpecies() should be like
async function getSpecies() {
const persons = await fetchPeople();
const speciesUrl = await Promise.all(
persons
.filter((thing) => thing.species.length)
.map((thing) => axios.get(thing.species[0]))
);
const newSwapi = persons.map((person) => {
return {
...person,
species: speciesUrl.find((info) => info.data.url === person.species[0])
};
});
setTest(newSwapi);
}

How to combine two json apis in react based on id

I am trying to combine two json apis based on the id value. Is there a way I could achieve that?
Thanks. Below is my section of the code I have attempted so far:
const [data, setdata] = useState([])
const [runs, setruns] = useState([])
//get data from the first api
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('http://localhost:8000/tasks?format=json');
setdata(res.data['results']);
} catch (e) {
console.log(e)
}
}
fetchData();
}, []);
//map the rows of data from the api above to obtain values based on id value
useEffect(() => {
data.map(row => {
console.log(row.id)
const fetchRuns = async () => {
const res2 = await axios.get(`http://localhost:8000/task/${row.id}/runs`);
setruns(res2.data)
}
fetchRuns();
row.rundata = runs
console.log('row:', row)
})
}, []);
You can make the second request in the first useEffect as well and then store everything together
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('http://localhost:8000/tasks?format=json');
const arr = [];
res.data.result.map(row => {
arr.push(axios.get(`http://localhost:8000/task/${row.id}/runs`));
}
const res2 = await axios.all(arr);
setdata(); // here you will need to join both results, but to help you better we will need the structure of both
} catch (e) {
console.log(e)
}
}
fetchData();
}, []);
So if I understand correctly, you have first an API call that will provide you with a list of IDs and you need to populate (get the data) from those IDS based on a second API call.
You need to pass "data" in the dependencies of your second useEffect. This tells React "whenever 'data' changes, please do the following".
Also, you should set the data at the end of your loop or you'll end up changing it every iteration with 1 value!
Anyway, you should probably use the "for await" syntax as async logic is not easily compatible with .map.
const [data, setdata] = useState([])
const [runs, setruns] = useState([])
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('http://localhost:8000/tasks?format=json');
setdata(res.data['results']);
} catch (e) {
console.log(e)
}
}
fetchData();
}, []);
async function populate(data){
let populatedData = []
for await (let row of rows){
const res2 = await axios.get(`http://localhost:8000/task/${row.id}/runs`)
populatedData.push(res2.data)
}
setruns(populatedData)
}
useEffect(() => {
if (data.length === 0) return
populate(data)
},[data])
Let me know if it works!

Resources