after updated array list not index instantly -react.js - javascript

i need a filter with time "old to new" and "new to old"
here is my code template:
const timeNewToOld = () => {
const [paginationUsers,setPaginationUsers] = useState([])
const newToOld = users.sort((a, b) => {
return b.Time.localeCompare(a.Time)
})
setPaginationUsers(newToOld)
}
const timeOldToNew = () => {
const oldToNew = users.sort((a, b) => {
return a.Time.localeCompare(b.Time)
})
setPaginationUsers(oldToNew)
}
this functions working but, not responding instantly on web browser.
i hope i can explain with these images:
i click on the "newtoold" function and nothing changes:
i move to the next page and i'm back to the 1st page:
everything is fine. only the first time I click on the function, it doesn't get instant updates, when I change the page, the index returns to normal.
paginationUsers created here:
useEffect(() => {
const getAllData = async () => {
onSnapshot(_dbRef, (snapshot) => {
const data = snapshot.docs.map((doc) => {
return {
id: doc.id,
...doc.data(),
}
})
setUsers(data)
setUserPageCount(Math.ceil(data.length / 20))
})
}
getAllData()
}, [])
useEffect(() => {
displayUsers(users, setPaginationUsers, userCurrentPage)
}, [users, setPaginationUsers, userCurrentPage])
i hope i could explain,
happy coding..

Array.prototype.sort doesn't create a new array, so react can't know that it changed. Creating a new array should help.
const timeOldToNew = () => {
const oldToNew = [...users].sort((a, b) => {
return a.Time.localeCompare(b.Time)
})
setPaginationUsers(oldToNew)
}

Related

Adding duplicates into array

I would like to be able to sort my array so I don't have duplicates into my "Recently-Viewed" section. The recently viewed section works fine except that it breaks when I add a duplicate. So I want to be able to sort my array so it doesn't break. I'm not really sure how to implement a sort function. Do I use filter or what do I do? I'm really confused.
My code:
const [tvShow, setTVShow] = useState([]);
const [recentlyViewed, setRecentlyViewed] = useState([]);
const getMovieRequest = async () => {
const url = `https://api.themoviedb.org/3/movie/top_rated?api_key=1e08baad3bc3eca3efdd54a0c80111b9&language=en-US&page=1`;
const response = await fetch(url);
const responseJson = await response.json();
setTVShow(responseJson.results)
};
useEffect(() => {
getMovieRequest();
},[]);
useEffect(() => {
const recentlyMovies = JSON.parse(localStorage.getItem('react-movie-app-favourites')
);
if (recentlyMovies) {
setRecentlyViewed(recentlyMovies.slice(0,5));
}
}, []);
const saveToLocalStorage = (items) => {
localStorage.setItem('react-movie-app-favourites', JSON.stringify(items))
};
const addRecentlyViewed = (movie) => {
const newRecentlyViewed = [movie, ...recentlyViewed]
setRecentlyViewed(newRecentlyViewed.slice(0,5));
saveToLocalStorage(newRecentlyViewed);
if (newRecentlyViewed > 5) {
newRecentlyViewed.pop();
}
};
Thank you guys in advance. I'm new to React and I find this very confusing.
Using the Set constructor and the spread syntax:
uniq = [...new Set(array)];
useEffect(() => {
const recentlyMovies = [...new Set(JSON.parse(localStorage.getItem('react-movie-app-favourites')))];
if (recentlyMovies) {
setRecentlyViewed(recentlyMovies.slice(0,5));
}
}, []);

How do I merge the results of two Firestore Realtime listeners into one array of objects using React

I am trying to merge the data received from two real-time listeners into one requests array. The array contains objects as its elements. This is so I can pass one single requests prop and manipulate that in my Dashboard component.
In Firestore, my documents in my Requests collection contain employeeUser and managerUser as references to another Users collection which contains information about the user such as their username. So I am also trying to receive their username within my snapshot as well.
Here is an example of the fields in the document of the Requests collection:
I am having issues merging the data from both listeners into one requests array using useEffect and useState hooks. Also, when I try to use the spread syntax to merge arrays it doesn't seem to actually have an effect on setRequests. When I use a callback setRequests((prev) => [...prev, ...employeeRequests]) it doesn't seem to do anything either.
In a nutshell, with the current code I have below, I seem to be receiving the correct data in the console (only in managerRequests & employeeRequests arrays) but I cannot merge the two arrays together and it is not displaying properly after all renders have been completed.
I did also notice that while I am editing the code and make a change right after that involves requests, the hot reload re-renders the page once more and then everything displays correctly. Once I refresh the page, nothing displays again until I have another hot reload that affects setRequests. I am wondering if I have to trigger some more renders somehow to get all the updates in the useEffect dependency array.
Here is my code so far:
const [requests, setRequests] = useState([]); // the array where I am trying to merge both managerRequests and employeeRequests into (cause its the only way I've been able to get it to kinda work)
const [managerRequests, setManagerRequests] = useState([]);
const [employeeRequests, setEmployeeRequests] = useState([]);
useEffect(() => {
if (firebase) {
//Set up first listener
const unsubscribe = firebase.getManagerRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const managerRequests = [];
snapshot.forEach(async doc => {
const [managerPromise, employeePromise] = await Promise.all([doc.data().managerUser.get(), doc.data().employeeUser.get()]); // single get() queries to retrieve employee and manager usernames from referenced User collection
var managerUsername = managerPromise.data().username;
var employeeUsername = employeePromise.data().username;
managerRequests.push({
id: doc.id,
managerUsername: managerUsername,
employeeUsername: employeeUsername,
...doc.data()
})
})
// console.log(managerRequests) -> managerRequests contains the correct set of data in an array of objects ex: [{id:1, ...}, {id:2, ...}]
setManagerRequests(managerRequests);
setLoadingElements((vals) => ({ ...vals, managerRequestsLoading: false })) // Currently not using these as barrier flags
}
})
//Set up second listener
const unsubscribetwo = firebase.getEmployeeRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const employeeRequests = [];
snapshot.forEach(async doc => {
const [managerPromise, employeePromise] = await Promise.all([doc.data().managerUser.get(), doc.data().employeeUser.get()]);
var managerUsername = managerPromise.data().username;
var employeeUsername = employeePromise.data().username;
employeeRequests.push({
id: doc.id,
managerUsername: managerUsername,
employeeUsername: employeeUsername,
...doc.data()
})
})
// console.log(employeeRequests) > employeeRequests contains the correct set of data in an array of objects ex: [{id:1, ...}, {id:2, ...}]
setEmployeeRequests(employeeRequests);
setLoadingElements((vals) => ({ ...vals, employeeRequestsLoading: false })) // Currently not using these as barrier flags
}
})
setRequests([...managerRequests, ...employeeRequests]); // This does not seem to work. requests has nothing in it in the last render
return () => {
if (unsubscribe) {
unsubscribe();
}
if (unsubscribetwo) {
unsubscribetwo();
}
}
}
}, [firebase, user])
console.log(requests) // contains nothing in requests
return (
<>
<Navbar />
<Dashboard requests={requests} /> // This is where I want to pass an array of the merged employee and manager requests as a prop to my Dashboard component
<Footer />
</>
)
Edit: Here is how I fixed it with the help of the first answer:
const [requests, setRequests] = useState([]);
const [managerRequests, setManagerRequests] = useState([]);
const [employeeRequests, setEmployeeRequests] = useState([]);
const [managerUsername, setManagerUsername] = useState('');
const [employeeUsername, setEmployeeUsername] = useState('');
const [managerUsernameEmployeeSide, setManagerUsernameEmployeeSide] = useState('');
const [employeeUsernameEmployeeSide, setEmployeeUsernameEmployeeSide] = useState('');
useEffect(() => {
if (firebase) {
//Set up first listener
const unsubscribe = firebase.getManagerRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const managerRequests = [];
snapshot.forEach(doc => {
// Had to remove await Promise.all(...) to get it to work
doc.data().managerUser.get()
.then(res => {
setManagerUsername(res.data().username);
})
doc.data().employeeUser.get()
.then(res => {
setEmployeeUsername(res.data().username);
})
managerRequests.push({
id: doc.id,
managerUsername: managerUsername,
employeeUsername: employeeUsername,
...doc.data()
})
})
setManagerRequests(managerRequests);
}
})
return () => {
if (unsubscribe) {
unsubscribe();
}
}
}
}, [firebase, user, managerUsername, employeeUsername])
useEffect(() => {
if (firebase) {
//Set up second listener
const unsubscribetwo = firebase.getEmployeeRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const employeeRequests = [];
snapshot.forEach(doc => {
const [managerPromise, employeePromise] = await Promise.all([doc.data().managerUser.get(), doc.data().employeeUser.get()]);
var managerUsername = managerPromise.data().username;
var employeeUsername = employeePromise.data().username;
doc.data().managerUser.get()
.then(res => {
setManagerUsernameEmployeeSide(res.data().username);
})
doc.data().employeeUser.get()
.then(res => {
setEmployeeUsernameEmployeeSide(res.data().username);
})
employeeRequests.push({
id: doc.id,
managerUsername: managerUsernameEmployeeSide,
employeeUsername: employeeUsernameEmployeeSide,
...doc.data()
})
})
setEmployeeRequests(employeeRequests);
}
})
}
return () => {
if (unsubscribetwo) {
unsubscribetwo();
}
}
}, [firebase, user, managerUsernameEmployeeSide, employeeUsernameEmployeeSide]
useEffect(() => {
if (managerRequests.length > 0 && employeeRequests.length > 0) {
setTransactions([...managerRequests, ...employeeRequests]);
}
}, [managerRequests, employeeRequests])
return (
<>
<Navbar />
<Dashboard requests={requests} /> // Requests finally receives one array with all values from managerRequests and employeeRequests combined
<Footer />
</>
)
You subscribe to firebase.getManagerRequests and firebase.getEmployeeRequests with callbacks (snapshot) => { // ... }. Those callbacks will not be executed synchronously. They will be executed when the responses of the requests come back. So there are no managerRequests and employeeRequests when your setRequests([...managerRequests, ...employeeRequests]).
Here is a simple example solution
const MyComponent = () => {
const [state1, setState1] = React.useState([])
const [state2, setState2] = React.useState([])
React.useEffect(() => {
// Do the first subscribe
// state1 will be updated in the subscription's callback
}, [
// Dependencies of this effect
])
React.useEffect(() => {
// Do the second subscribe
// state2 will be updated in the subscription's callback
}, [
// Dependencies of this effect
])
React.useEffect(() => {
// Merge state1 and state2 here
}, [
state1,
state2
])
}
---- updated#20211203 ----
setState is asynchronous. You can checkout https://reactjs.org/docs/react-component.html#setstate for more information.
useEffect(() => {
// for example
console.log(state1) // [1, 2, 3]
console.log(state2) // [7, 8, 9]
setRequests([...state1, ...state2])
console.log(requests) // this is not guaranteed to be [1, 2, 3, 7, 8, 9]
}. [state1, state2])
If you want to do something based on the merged requests in the same component. You can write another useEffect:
useEffect(() => {
console.log(requests) // [1, 2, 3, 7, 8, 9]
}, [requests])

useState is not updating state immediately

here userlist is updating immediately what can be correct code for above logic
I am trying fetch userlist from firestore than traversing that list to find user details from different collection
useEffect(() => {
db.collection("following/" + Credens.uid + "/userFollowing")
.get()
.then((snapshot) => {
followingList = snapshot.docs.map((value, ind) => value.data());
})
.then(() => {
if (followingList.length > 0) {
followingList.map((value, index) => {
db.collection("Postss")
.doc(value.useruid)
.collection("MovieWatched")
.get()
.then((snaps) => {
// let Detail = snap.data()
let movieidList = snaps.docs.map(
(value) => value.data().postMovieId
);
if (movieidList.includes(MovieId) === true) {
setuserList((prev) => [...prev, value.useruid]);
}
});
});
}
})
.then(() => {
console.log(userList);
userList.map((value, index) => {
db.collection("users")
.doc(value)
.get()
.then((snapshot) => {
setfriendsWatchedData((prev) => [
...prev,
{
usersID: value,
userData: snapshot.data(),
},
]);
});
});
});
// return () => {
// cleanup
// }
}, []);
To be sure the state did change, you can use the useEffect() to monitor the changing of that state like:
useEffect(() => {
if (userList) {
// userList.map....
}
}, [userList])
Additional conditions can be specified in the if statement. The hook will run whenever the state changes.

RXJS + Axios stagger network requests

I'm working with an API that has very strict rate limits and I need to send a number of requests to the same endpoint from names in an array. I set up a simple demo project and I tried this (and may variants of):
const pokemon = ['ditto', 'bulbasaur', 'charizard', 'pikachu'];
const obs = pokemon.map((pk, index) => {
return from(axios.get(`https://pokeapi.co/api/v2/pokemon/${pk}`)).pipe(delay(1000),map(res => {
return {id: res.data.id, name: res.data.name, height: res.data.height};
}));
});
concat(obs).subscribe(data => {
console.log(data);
});
but the Axios.get()'s all fire off when they are created and the concat().subscribe() just logs 4 observables. If I subscribe to the from().pipe() then after a second all 4 logout at once but then I'm subscribing in a subscribe which is poor.
The solution I settled on feels so cumbersome I have to believe there is a better way:
const axios = require('axios');
const { forkJoin, from } = require('rxjs');
const { map } = require('rxjs/operators');
const pokemon = ['ditto', 'bulbasaur', 'charizard', 'pikachu'];
const obs = pokemon.map((pk, index) => {
return from(new Promise(resolve => setTimeout(async () => {
const prom = await axios.get(`https://pokeapi.co/api/v2/pokemon/${pk}`);
resolve(prom);
}, index*1000))).pipe(map(res => {
console.log('fetched: ', pk);
return {id: res.data.id, name: res.data.name, height: res.data.height};
}))
})
forkJoin(obs).subscribe(data => {
console.log(data);
});
This delays the creation of the axios.get(), if I run with node --require debugging-aid/network rxjs_axios_delay.js I can see the delayed network requests and the real API I am hitting is happy, but this feels complicated and not very "RXy".
Anyone got anything better?
but the Axios.get()'s all fire off when they are created
this highlights a very interesting trait of Promises: they are eager. I think the defer operator can come in handy:
const pokemon = ['ditto', 'bulbasaur', 'charizard', 'pikachu'];
const obs = pokemon.map((pk, index) => {
return defer(() => axios.get(`https://pokeapi.co/api/v2/pokemon/${pk}`)).pipe(delay(1000),map(res => {
return {id: res.data.id, name: res.data.name, height: res.data.height};
}));
});
concat(...obs).subscribe(data => {
console.log(data);
});
StackBlitz demo.
The cool thing about defer is that it evaluates the given expression(i.e invokes the callback function) when it is being subscribed to.
This means you could also do things like these:
let dep$ = of('A');
const src$ = defer(() => dep$);
if (someCondition) {
dep$ = of('B')
}
// if `someCondition` is true, `dep$` will be `of('B')`
src$.pipe(...).subscribe()

React script stop working after changing API call

I have a script which calls API from React and then triggers email notification function.
I was changing one part of it to call whole array of parameters instead of calling one parameter after another.
Here is part before change(working one). Console log shows correct response and I receive email notification as well.
const getApiData = () => {
const apiCall = (symbol) => {
return `https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD&api_key=API-KEY-HERE`
}
const MAX_CHARACKTERS = 300
let bucketArray = ['']
for (let i=0; i < assets.length - 1; i += 1) {
const symbol = `${bucketArray[bucketArray.length - 1]},${assets[i]}`
if (i === 0) {
bucketArray[0] = assets[i]
continue
}
if (symbol.length < MAX_CHARACKTERS) {
bucketArray[bucketArray.length - 1] = symbol
} else {
bucketArray[bucketArray.length] = assets[i]
}
}
const getData = () => {
Promise.all(
bucketArray.map(req => {
return axios(apiCall(req))
.then(({ data }) => data)
})
).then((data) => setDataApi(data))
}
getData()
};
Here is problematic one.
const getApiData = () => {
const getString = symbol =>
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD&api_key=API-KEY-HERE`;
function getAxious(id) {
const url = getString(id);
return axios.get(url);
}
const BUCKET_SIZE = 150;
const bucketArray = assets.reduce(
(arr, rec) => {
if (arr[arr.length - 1].length < BUCKET_SIZE) {
arr[arr.length - 1] = [...arr[arr.length - 1], rec];
return arr;
}
return [...arr, [rec]];
},
[[]]
);
bucketArray
.reduce((acc, rec) => {
return acc.then(results => {
return Promise.all(
rec.map(item =>
getAxious(item).then(({ data }) => {
return {
Symbol: item,
Open: data
};
})
)
).then(x => {
return [...x, ...results];
});
});
},
Promise.resolve([]))
.then(res => {
setDataApi(res);
});
};
Here in console I receive empty array - [] no errors showed, but email notification also stops from working.
I'm changing the code since I need to call whole array from API in one call. Before I was calling one symbol after another.
What I did wrong that console doesn't show the correct response?
EDIT1
Here is bucketArray value
const assets = ['ADA','KAVA','DOGE'];
I was not able to understand completely, but I think you want to collect all the results together and set it to the data using setDataApi.
Check the below code and let me know if it helps:
async function getApiData() {
const getString = (arr) =>
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${arr.join(
","
)}&tsyms=USD&api_key=API_KEY`;
function getAxious(arr) {
const url = getString(arr);
return axios.get(url);
}
const BUCKET_SIZE = 150;
const bucketArray = assets.reduce(
(arr, rec) => {
if (arr[arr.length - 1].length < BUCKET_SIZE) {
arr[arr.length - 1] = [...arr[arr.length - 1], rec];
return arr;
}
return [...arr, [rec]];
},
[[]]
);
const res = await getAxious(bucketArray);
console.log("res", res);
return res;
// after this you can set setDataApi(res);
}
// keep this useEffect sepearate
const [timer, setTimer] = useState(null);
useEffect(() => {
async function getApiDatahandler() {
const res = await getApiData();
console.log(res);
const timerId = setTimeout(() => {
getApiDatahandler();
}, 1000 * 60);
setTimer(timerId);
setDataApi(res)
// set the data setDataApi(res);
}
getApiDatahandler();
return () => {
window.clearTimeout(timer);
};
}, []);
// useEffect(() => {
// const timerId = setTimeout(() => {
// getApiData();
// }, 1000 * 60);
// }, [])
Checkout this codepen for a possible solution.
https://codepen.io/bcaure/pen/RwapqZW?editors=1011
In short, I don't know how to fix your code because it's quite a callback hell.
// Mock API and data
const bucketArray = [[{item: 'item1'}], [{item: 'item2'}], [{item: 'item3'}]];
const getAxious = item => {
return new Promise((resolve, reject) => resolve({data: 'API data'}));
}
// Return promise that combines API data + input item
const recToPromise = rec => rec.map(item => {
return new Promise((resolve, reject) => getAxious(item)
.then(data => resolve({item, data})));
});
// Flatten array
const recPromisesFlatten = bucketArray.flatMap(recToPromise);
Promise.all(recPromisesFlatten)
.then(res => {
const flattenRes = res.flatMap(({item, data}) => ({ Symbol: item, Open: data }));
console.log(JSON.Stringify(flattenRes))
});
What I'm suggesting to debug errors:
build your promise array first
then run Promise.all
then combine your data
Bonus: you can see flatMap instead of reduce for better readability.

Categories