I'm trying to learn React as part of a course I'm doing with Udacity. I've been battling this issue for a couple of days and can't seem to figure a way out of it.
Basically, I have an API call that returns an array of objects as a promise, I map or filter through these (I've tried both) and need to find the elements that already exist in an array booksOnShelves. I do this by comparing the .id property of each object. Once we find a match I need to set the .shelf property to the same value as the one in the existing Array and if the book doesn't exist I need to set its value to 'none'. All is good here but the problem is when I find a match the property updates the shelf to the correct one, but the iteration continues with the next book that obviously doesn't match and overwrites the .shelf value with none.
Here's the code for this particular method:
class SearchBar extends Component {
state = {
query: '',
result: []
}
getBooks = (query) => {
const booksOnShelves = this.props.books;
BooksAPI.search(query)
.then(res => {
if (res instanceof Array && res.length > 0) {
res.filter( searchedBooks => {
return booksOnShelves.filter( onShelves => {
if ( searchedBooks.id === onShelves.id) {
return searchedBooks.shelf = onShelves.shelf
} else {
return searchedBooks.shelf = 'none
}
})
})
this.setState({result: res.sort(sortBy('title'})
} else {
this.setState({result: []})
}
})
.catch( err => { console.log('ERROR: ', err)})
}
I've tried so many things but none was able to maintain the value of the shelf property.
Is there any way to stop the iteration from overwriting the matched value?
Any ideas how to achieve the intended outcome?
Thank you in advance for the help.
Should work fine in your case:
BooksAPI.search(query).then(res => {
if (res instanceof Array && res.length > 0) {
booksOnShelves.forEach((book) => {
const correspondingRes = res.find((item) => { return (item.id === book.id) });
if (correspondingRes) {
book.shelf = correspondingRes.shelf;
} else {
book.shelf = “none”;
}
});
}
Problem sorted with the help of all you guys I've made it work. Thanks a lot.
Just for reference here's the code that worked.
BooksAPI.search( query )
.then( res => {
if (res instanceof Array && res.length > 0) {
res.map( booksFromSearch => {
booksOnShelves.find( book => {
if( booksFromSearch.id === book.id) {
booksFromSearch.shelf = book.shelf
return booksFromSearch;
} else {
booksFromSearch.shelf = 'none'
}
})
})
Related
I'm trying to do something that in my mind is very simple.
I have an array of documents (firebase firestore) and I have a method to fetch From Document with timeStamp A to docuemnt with timeStamp B
In the fetch function that tries to see if the ref id has already been fetched, but the messages inside the fetchUpTo function never updates. While the one I log in the effect hook, updates as expected.
The top Log is the Inside fetchUpTo and the bottom one is the effect one.
The logs are from trying to refetch one of the documents present in the bottom log.
const fetchUpTo = (ref: any) => {
if (isFetching || isAtEnd) {
return
};
if (!messagesSnapShot) {
return;
}
if (messagesSnapShot.size < queryLimit) {
return;
}
let index = messages.findIndex(d => d.id === ref.id)
if (index !== -1) {
if (messageRefs.current[index] !== null) {
scrollToMessage(messageRefs.current[index])
return;
}
}
setIsFetching(true)
const lastVisible = messages[0]
const cQuery = query(collection(fireStore, "scab-chat", chatId, "messages"), orderBy('time', 'desc'), startAfter(lastVisible.data.time), endAt(ref.data.time));
getDocs(cQuery).then(newDocs => {
if (newDocs.size < queryLimit) {
setIsAtEnd(true)
}
const newD = newDocs.docs.map(doc => ({
data: doc.data(),
id: doc.id,
ref: doc
}));
setMessages(s => {
const f = newD.filter(doc => s.findIndex(d => d.id === doc.id) === -1)
return [...s, ...f]
})
})
}
After doing this, I "wait" for the state to update with an Effect hook
useEffect(() => {
if (messages) {
setIsFetching(false)
}
}, [messages])
The problem is I have this small part of the code
let index = messages.findIndex(d => d.id === ref.id)
if (index !== -1) {
if (messageRefs.current[index] !== null) {
scrollToMessage(messageRefs.current[index])
return;
}
}
React state will only rerender you app when the function finishes, so you will only check for the updated messages when you call fetchUpTo again. If you need the updated value on the same function call, try using flushSync.
There is a nice video with Dan Abramov trying to achieve the same as you, I will leave it here for reference: https://youtu.be/uqII0AOW1NM?t=2102
Okay so I fixed it kinda, I had to render a diferent component while the isFetchingState was true so
if(isFetching){return <Loader/>}
and then it worked. I still don't really understand why It didn't work in the first place.
I find names for some objects and have following source code:
const findAndRenderName = (projectId: number| undefined) => {
//i want to render here something when the condtion will pass
projectList?.map(project => projectId = project.id)
}
return (
<DetailsBox title={t('catalogPage.componentDetails.specs.used')}>
{
component?.projects.map(project => findAndRenderName(project.id))
}
</DetailsBox>
);
How to make kind of if from the map function, any idea?
I think what you're trying to accomplish is
projectList?.map(project => {
if(projectId === project.id){
// do something
}
})
of if you refer to the second map
component?.projects.map(project => {
if(findAndRenderName(project.id)){
// do something
}
})
I have a map loop inside another loop like this:
{"undefined" !== typeof this.props.categoryObject['products'] && Object.keys(this.props.categoryObject['products']).length > 0 && Object.keys(this.props.categoryObject['products']).map(keyProd => {
const product = this.props.categoryObject['products'][keyProd];
if("undefined" !== typeof product) {
Object.keys(this.props.activeFilters).map(keyFilter => {
console.warn(this.props.activeFilters[keyFilter]);
return (<div>test</div>)
})
}
})}
The console works, but not the render. Any idea why ?
Thank you
The problem here that outer .map doesn't have return statement and your outer code doesn't have return statement too.
So you have to change your code to the following
return ("undefined" !== typeof this.props.categoryObject['products'] && Object.keys(this.props.categoryObject['products']).length > 0 && Object.keys(this.props.categoryObject['products']).map(keyProd => {
const product = this.props.categoryObject['products'][keyProd];
if("undefined" !== typeof product) {
return Object.keys(this.props.activeFilters).map(keyFilter => {
console.warn(this.props.activeFilters[keyFilter]);
return (<div>test</div>)
})
}
}))
Also some notes on how you can make your code more readable with new es6 features. Commented version
// It's better to extract products into separate variable
// as there are a lot of copy paste code for this action
const { products } = this.props.categoryObject;
// `undefined` is falsy value so you can just test next condition for early exit
if (!products) { return null; }
// 1. no need to test for `.length` as iterating empty array will make 0 iterations
// 2. use Object.entries you can immediately get key and value
return Object.entries(products).map(([key, product]) => {
// Same things as with `products` variable
if (product) {
// I think for your future code you can use `Object.entries` here too
return Object.keys(this.props.activeFilters).map(keyFilter => {
return (<div>test</div>)
})
}
})
Final version:
const { products } = this.props.categoryObject;
if (!products) { return null; }
return Object.entries(products).map(([key, product]) => {
if (product) {
return Object.keys(this.props.activeFilters).map(keyFilter => {
return (<div>test</div>)
})
}
})
NOTE to use all of them in all common browser you have to configure your babel properly
I have a group of checkboxes, whenever I select a checkbox I need to push an array of data, like { 'index':1, 'photo':'sample.png' } to state, and whenever I unselecting the checkbox, I need to remove it from the state. after I need to loop through the state to get index and photo to be used
handleSelection = async (media, index, isSelected) => {
alert(index);
if (isSelected == true) {
this.state.mediaSelected.push(media.photo);
} else {
this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);
}
console.warn(this.state.mediaSelected);
}
this is working for single value without the key, is there any way to push it with key and value?
You should always update state with this.setState in your case would be something like this:
handleSelection = async (media, index, isSelected) => {
alert(index);
if (isSelected == true) {
this.setState({
mediaSelected: this.state.mediaSelected.push({
index,
photo: media.photo
})
});
} else {
this.setState({
mediaSelected: this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1)
});
}
console.warn(this.state.mediaSelected);
}
Try this:
Sorry I am working as well as answering your question so it is taking time.
handleSelection = async (media, index, isSelected) => {
let selectPhotosObj = this.state.selectPhotosObj || [];
if (isSelected == true) {
const data = { index, photo: media.photo };
//this.state.selectedPhotoObj will be the container for your object.
selectPhotosObj.push(data)
//need to set the new Array of Object to the state.
this.setState({ mediaSelected: media.photo, selectPhotosObj });
} else {
const removedPhoto = this.state.mediaSelected.filter(value => value !== media.photo);
selectPhotosObj = this.state.selectedPhotosObj.filter(value => value.index !== index);
this.setState({
mediaSelected: removedPhoto,
selectPhotosObj
})
}
console.warn(selectPhotosObj);
}
I'm trying to check if a JSON response contains a value already inside an array and if it doesn't add it in. The problem I'm having is understanding how to approach this in reactjs. I'm checking before I append it but it doesn't want to work. I've tried passing in user object & user.id but these fail. The attempt below fails to compile but it should help understand what I'm trying to achieve.
Code:
componentWillMount() {
fetch('http://localhost:8090/v1/users')
.then(results => {
return results.json();
})
.then(data => {
data.map((user) => (
if(userList.hasOwnProperty(user.id)) {
userList.push({label: user.title, value: user.id})))
}
})
}
map return the resultant array, but you are not returning anything from it, you should instead use forEach Also you need to check if the userList array contains the id, for that you can use findIndex
What you need is
state = {
userList: [];
}
componentDidMount() {
fetch('http://localhost:8090/v1/users')
.then(results => {
return results.json();
})
.then(data => {
const newUserList = [...this.state.userList];
data.forEach((user) => { // use { here instead of
if(userList.findIndex(item => item.value === user.id) < 0) {
newData.push({label: user.title, value: user.id})
}
})
this.setState({userList: newUserList});
});
}
render() {
return (
{/* map over userList state and render it here */}
)
}
I'd recommend using reduce to turn the returned data into an array you'd like, then adding those values to your existing user list:
fetch('http://localhost:8090/v1/users')
.then(res => res.json())
.then(data => data.reduce((acc, user) => {
const idList = userList.map(user => user.id);
if (idList.indexOf(user.id) === -1) {
acc.push({label: user.title, value: user.id})
}
return acc;
},[]))
.then(newList => userList = [...userList, ...newList]);