Concatenate an array with 2 different useEffect calls react - reactjs

I am trying to make two different api calls -- get the array results and then concatenate them together into 1 array, which I then present in the return.
I am having difficulty with :
a) There are two calls so the array is populated 2x or
b) The data is not persisted so I lose it between calls.
The goal is to make 2 calls to the api (each once) and concatenate the arrays together into 1 result -- which is cleared or emptied between page refresh. So it is not called more than once ... or is not continuously lengthened.
My code is as follows:
const [results, setResults] = useState([]);
useEffect(() => {
getStories();
getContributions();
}, []);
const getStories = async () => {
const { data } = await axios.get(`/api/stories/admin`); // get the data
setResults((currentArray) => [...currentArray, ...data]);
};
const getContributions = async () => {
const { data } = await axios.get(`/api/contributions/admin`);
setResults((currentArray) => [...currentArray, ...data]);
};
useEffect(() => {
async function loadBootstrap() {
const bootstrap = await import(
"../../../node_modules/bootstrap/dist/js/bootstrap"
);
var tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
loadBootstrap();
}, []);
return (<> {JSON.stringify(results)}
</>);

You should fetch them both and then set the result once.
Something like the following:
useEffect(() => {
getStoriesAndContributions();
}, []);
const getStoriesAndContributions = async () => {
const [{ data: stories }, { data: contributions }] = await Promise.all([
axios.get(`/api/stories/admin`),
axios.get(`/api/contributions/admin`),
]);
setResults([...stories, ...contributions]);
};
The Promise.all() is not really needed, just in order to make it parallel.

I'm not sure I quite get what you mean when listing the problems, but why not simply do:
const loadArrayElements = async () => {
const { data: stories } = await axios.get(`/api/stories/admin`);
const { data: contributions } = await axios.get(`/api/contributions/admin`);
setState([...stories, ...contributions]);
}
useEffect(() => void loadArrayElements(), []);

What you can do is fetch and store the two data sources in javascript variables and call the setState one time in the useEffect :
useEffect( async () => {
fetchData()
}, []);
const fetchData = async () => {
const { data:stories } = await axios.get(`/api/stories/admin`);
const { data:contributions } = await axios.get(`/api/contributions/admin`);
setResults([...stories, ...contributions])
}

Related

onSnapShot vs Classic fetch when using useEffect?

I am trying to refactor my code to work solo with firebase the issue that I am facing is firebase methods for example onSnapshot, could this method totally replace the need for the classic first data fetch ? I am ruining in to bunch of run time errors since many data are not available at start and I am starting to doubt my onSnapShot, below is the code to my entry component where most of the data fetching using onSnapShot happens but still many values at start are undefined any idea how to tackle such issue ? the data are there but at start they are not,
useEffect(() => {
context.SetUser('trainer');
const fetchClient = () => {
const colReClients = collection(db, 'Clients');
const id: string = _authContext?.currentUser?.uid;
const unsub = onSnapshot(doc(colReClients, id), (doc) => {
console.log(doc.data(), 'snapShot triggered');
///////////// Refactor to foreach
context.SetExerciseStateBackEnd('dataCenter', doc.data()?.clients);
//context.SetExerciseStateBackEnd('focus', doc.data()?.focus);
});
return () => unsub();
};
if (_authContext?.currentUser) {
fetchClient();
}
}, [_authContext?.currentUser?.uid]);
useEffect(() => {
const fetchWeeks = async () => {
// ADD WEEKS
// Weeks Collection Reference
const colRefWeeks = collection(db, 'Weeks');
const docRefWeeks = doc(colRefWeeks, `${context.focus}`);
const unsub = onSnapshot(docRefWeeks, (doc) => {
console.log(doc.data(), 'snapShot triggered');
context.SetExerciseStateBackEnd('weeks', { [`${context.focus}`]: doc.data()?.clientWeeks });
const arr = doc.data()?.clientWeeks.find((el: any) => el.weekId === context.weekFocus).routines;
if (arr) {
context.SetExerciseStateBackEnd('routines', arr);
}
});
return () => unsub();
};
fetchWeeks();
}, [context.focus]);

Get an empty array when use array of objects in filter function React

I am new to react and try to get data from the database and view data in frontend. This is the code I tried.
function ViewPost() {
const { postId } = useParams();
console.log(postId);
const [post, setPost] = useState({});
useEffect(()=>{
getOnePost();
}, []);
useEffect(()=>{
if (post && post.location) {
console.log(post.location);
console.log(post.location.longitude);
console.log(post.location.latitude);
}
}, [post]);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${postId}`)
console.log(response);
const allPost=response.data.onePost;
setPost(allPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(post);
console.log(post.wasteItemList);
const [offers, setOffers] = useState([]);
useEffect(()=>{
getAllOffers();
}, []);
const getAllOffers = async () => {
await axios.get(`/viewPendingSellerOffers`)
.then ((response)=>{
const allNotes=response.data.existingOffers;
setOffers(allNotes);
})
.catch(error=>console.error(`Error: ${error}`));
}
console.log(offers);
const wasteItem = offers?.filter(wasteItems => wasteItems.status==='accepted' && wasteItems.wasteItemsListId===post?.wasteItemList?._id);
console.log(wasteItem);
}
When I call the first API I get these results. This is an image of results.
In the above image, there is a length 2 array of objects called as wasteItemList. Then I call the second API and get these results.
This image shows length 8 array of objects. Then I try to filter the data of these two arrays using this const wasteItem = offers?.filter(wasteItems => wasteItems.status === 'accepted' && wasteItems.wasteItemsListId === post?.wasteItemList?._id); code. But I get a length 0 empty array as the results of this filter function. But when I try an ID of a wasteItemList array
6112679258125b0418844368 instead of using this post?.wasteItemList?._id code I get the correct result. What is the problem here? How do I solve this problem?
Edited code:
function ViewPost() {
const { postId } = useParams();
const [post, setPost] = useState(undefined);
const [offers, setOffers] = useState(undefined);
useEffect(() => {
setPost(undefined);
axios
.get(`/buyerGetOnePost/${postId}`)
.then((resp) => setPost(resp.data.onePost))
.catch((err) => console.error(err));
}, [postId]);
useEffect(() => {
axios
.get(`/viewPendingSellerOffers`)
.then((response) => setOffers(response.data.existingOffers))
.catch((err) => console.error(err));
}, []);
useEffect(()=>{
if (post && post.location) {
console.log(post.location);
console.log(post.location.longitude);
console.log(post.location.latitude);
}
}, [post]);
console.log(post);
console.log(post?.wasteItemList);
console.log(offers);
const wasteItem = offers?.filter(wasteItems => wasteItems.status==='accepted' && wasteItems.wasteItemsListId===post?.wasteItemList?._id);
console.log(wasteItem);
}
useEffect runs asynchronously so your post will not be available
on your getAllOffers function which is located in your second
useEffect.
You will need to make your getOnePost() and getAllOffers() to
run synchronously within a single useEffect.
Or the problem is in your condition checks as I can't tell much only
by your given array picture.

how to refactor duplicate API calls into a single API call?

I am pretty new to building full-stack applications, and I could like to avoid duplicating code in order to build the following to perform the calls in react my endpoints can be called like the following /api/v1/feeds/list/?page=${page} or api/v1/feeds/list/?search=${query} , but I would like to joing ?page=${page}&?search=${query} since search param is optional . I just want to make a single api call
async function fetchFeed(page) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}`);
}
async function searchQuery(query) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?search=${query}`);
}
const Main = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
const [feedCount, setfeedCount] = useState(0);
const [visible, setVisible] = useState(3)
const showMoreItems = () => {
setVisible(prevValue => prevValue + 3);
}
const browse = (page) => {
fetchFeed(page)
.then(function(response){
setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
// fetches data
const fetchData = (search) => {
searchQuery(search)
.then((response) => {
setFeed(response.data.results)
})
.catch((error) => {
console.log(error);
});
};
const handleSearch = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
browse(currentPage)
fetchData(feed);
}, [currentPage]);
}
I'd pass an object with both page and query, which both default to the empty string - and if empty, don't include them in the fetched URL:
async function fetchFeed({ page = '', query = '' }) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?${page ? `page=${page}&` : ''}${query ? `search=${query}` : ''}`);
}
If possible, make your API accept empty query parameters too, allowing you to simplify to
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}&query=${query}`);
Something like this should work for you
const fetchFeed = async (page, query) => {
let url =`http://localhost:8001/api/v1/feeds/list/?page=${page}`
if(query) url += `?search=${query}`
return api.get(url)
}
const browse = (page search) => {
await fetchFeed(page search)
.then(function(response){
!search && setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
useEffect(() => {
browse(currentPage) // just pass page
browse(currentPage, searchQuery); // pass both page and search query
}, [currentPage]);

Which is the best use of useEffect to fetch multiple data?

I'm some confused about some different ways to use the useEffect hook to fetch API data. I want to know if there is a "best way" to do this, performance related, or if it really doesn't matter how to do it.
Consider the following ways:
Mutiple function calls to fetch API data inside a single useEffect:
useEffect(() => {
const fetchStudents = async () => {
const students = await studentService.getAll()
setStudents(students)
}
const fetchCourses = async () => {
const courses = await courseService.getAll()
setCourses(courses)
}
const fetchSchedules = async () => {
const schedules = await scheduleService.getAll()
setSchedules(schedules)
}
fetchStudents()
fetchCourses()
fetchSchedules()
}, [])
A single function call to fetch all the api data inside a single useEffect:
useEffect(() => {
const fetchAllData = async () => {
const students = await studentService.getAll()
const courses = await courseService.getAll()
const schedules= await scheduleService.getAll()
setStudents(students)
setCourses(courses)
setSchedules(schedules)
}
fetchAllData()
}, [])
Maybe, multiple useEffects for each api call:
useEffect(() => {
const fetchStudents= async () => {
const students = await studentService.getAll()
setStudents(students)
}
fetchStudents()
}, [])
useEffect(() => {
const fetchCourses = async () => {
const courses = await courseService.getAll()
setCourses(courses)
}
fetchCourses()
}, [])
useEffect(() => {
const fetchSchedules = async () => {
const schedules= await scheduleService.getAll()
setSchedules(schedules)
}
fetchSchedules()
}, [])
Is there another way to consider? Let it be known.
In your second example you wait for each promise to resolve before executing the next one, this will hurt performance, the other examples are all running in parallel.
I would go with Promise.all inside a single useEffect because i think its more readable then 3 useEffect or 3 functions, and this will also make sure all of our promises are executing in parallel.
Note that if one of the promises inside Promise.all reject, the function is going to throw and you won't have any access to the resolved promises
useEffect(() => {
Promise.all([
studentService.getAll().then(setStudents),
courseService.getAll().then(setCourses),
scheduleService.getAll().then(schedules)
])
}, [])

Promise not working in React useEffect in combination with Firebase

I want to get data from firebase inside a useEffect function like this:
useEffect(() => {
/** nope */
async function fetchData() {
let dataObject = {};
let dataArray = [];
setAttendees({});
// You can await here
if (newData[listRedux]) {
const request = await Object.keys(newData[listRedux] .
[1].attendees).map(
user => {
usersRef.child(user).on('value', snap => {
dataObject[snap.key] = snap.val();
setAttendees(dataObject);
console.log(dataObject);
let comp = (
<Avatar
key={snap.key}
size="small"
source={snap.val().avatar}
alt={snap.val().name}
/>
);
dataArray.push(comp);
setAttendeesComp(dataArray);
});
}
);
// Wait for all requests, and then setState
await Promise.all(request).then(() => {
console.log('done');
});
}
}
fetchData();
}, [newData, listRedux]);
Now the second console.log inside the promise all will first show then the first console.log, meaning the request was not done yet.
How can i improve my code so the request and the states are first being set and then continue with the rest?
export default function Example() {
const [data, dataSet] = useState(false)
const [attendees, setAttendees] = useState(false)
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await res.json()
console.log(response);
dataSet(response)
}
useEffect(() => {
if (!attendees) return
fetchMyAPI();
}, [attendees, newData, listRedux]);
useEffect(() => {
setAttendees({})
}, [])
More examples here:

Resources