MapMarker does not update description onPress immediately - reactjs

I am trying to learn how to use React Native maps and I am trying to add in custom Map Markers. For some reason, when I try to use the following code, the image updates properly but the description does not update properly until the second click. The first click will show "not selected" but clicking on the same marker will show the actual text I want. How can I fix this?
Since the image is updating to the newImage, I know
this.state.selectedMarkerIndex === i
but the same equality does not apply to description for some reason?
state = {busText:[]}
fetchData=(i, index)=>{
fetch('LINK TO GET BUSES'+ i.toString() + '/buses', {method:'GET'})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
selectedMarkerIndex: index,
busText: responseJson
},
()=>{
console.log("selected index: " + this.state.selectedMarkerIndex)
}
)
console.log(JSON.stringify(this.state.busText));
console.log("_______________________________________________________");
})
}
renderMarkers = ()=>{
return busStops.stops.map((stop, i) => {
return <Marker marker
key={ `${i}` }
coordinate={{latitude: stop.location.latitude, longitude: stop.location.longitude}}
title = {stop.stopName}
image = {this.state.selectedMarkerIndex === i ? newImage : busStopImage}
description={this.state.selectedMarkerIndex === i ? JSON.stringify(this.state.busText) : "not selected"}
onPress={() => {
this.fetchData(stop.stopID, i)
console.log(this.state.selectedMarkerIndex + "i :" + i)
}
}
/>
})
}
I expect the description of the MapMarker to be updated when I click on it with what is fetched but that is not occurring.

A couple of things: To guarantee execution after setState you'll need to put your
this.setState({busText: responseJson})
in the fetchData() callback. Even better, set busText state earlier, where you're setting selectedMarkerIndex state.
Also for what it's worth, if you're having response time issues, try dropping some of your console.log() calls. Native (especially iOS) gets bogged down by them.

Related

Displaying filtered data in React

Ok, so I recently had a take-home coding challenge which I failed. One of the reasons was I couldn't figure out how to implement filtering in React using checkboxes. So I resorted to doing the filtering using CSS, but they wanted me to do using react state.
const [data, setData] = useState([]);//api call data saved here
const [publisher, setPublisher] = useState({// this state checks which checkboxes are selected or not
Nin: false,
MS: false,
SN: false
});
So for the assignment, I had to make an API call, which would give me more than 1000 items in the response. I had to filter that object to only have 3 types of products saved up in State, which I did with no problem.
useEffect(() => {
fetchJsonp("apicall", {
jsonpCallback: "jsonp"
})
.then((response) => response.json())
.then((json) =>
setData(
json.filter(
(x) =>
x["Publisher"] === "Nintendo" ||
x["Publisher"] === "Sony" ||
x["Publisher"] === "Microsoft"
)
)
)
.catch((err) => console.log(err));
}, []);
The issue was that once the Cards were showing on the screen, which was absorbing data from the local state. The user had the option to filter via a checkbox to only see the type of products from Nintendo, Microsoft, or Sony, or all games would show if no checkbox was selected.
So my question is how would one go about implementing this feature in react?
Just add a filter function in your render. I'm assuming for the cards you were using map so just do something like
return (
<>
{data.filter((item)=>{
const {Nin, MS, SN} = publisher;
if(!Nin && !MS && !SN){ //return all if no boxes checked
return true;
}
else{
return (Nin && item.publisher === 'Ninetndo') ||
(SN && item.publisher === 'Sony') ||
(MS && item.publisher === 'Microsoft')
}
}).map((x) => <Card />)}
</>
)

How to replace a loading image in a chat with an image uploaded to Firebase Storage using ReactJS?

I have built a chat using Firebase and ReactJS. I mainly followed their Firebase's web codelab at https://firebase.google.com/codelabs/firebase-web#1. However, I have gotten stuck on the image uploading functionality. Since I am using ReactJS, I have had to modify their plain JS code to match mine. I am able to save a message with a "loading" image url in Firestore, then, I successfully save the image that I want to ultimately show in the chat in Firebase Storage, and finally then I successfully retrieve its url from Storage and replace it with the url of the loading image in Firestore. The image does show in the chat, however, the loading image is not actually replaced but, instead, it remains in the chat when I want it to be completely replaced, obviously, so that the loading image is no longer there. Here's what I mean, in this image:
As you can see the loading image on top stayed on instead of being replaced by the image underneath it. I think it should be filtered out somehow before I save the new snapshot with the new image url. However, I can not figure out how to do it correctly. I tried to filter it out based on the url of the loading image which is saved locally but since it is saved as a base64 in Storage, it did not work. Neither did using the actual Base64 code as a way to filter it out. So, I need help to solve this issue. The codelab does not really specify this nor is it clear how they do it in their code which is in plain Javascript anyways and I use ReactJS so it may not be 100% suitable.
Here's, I believe, enough code to see what is going on. Let me know if you need more of it.
Here's how I send images to the Chat: (modeled on the Firebase codelab)
sendImageToChat () {
this.state.chatFiles.forEach((file) => {
firebase.firestore().collection('Chats')
.doc(this.state.uid)
.collection('Messages')
.add({
docId: this.state.docId,
imageUrl: loadingLogo,
timestamp: new Date(),
uid: this.state.uid,
name: this.state.displayName,
email: this.state.email
})
.catch((error) => {
this.setState({ writeError: error.message });
})
.then((messageRef) => {
// 2 - Upload the image to Cloud Storage.
const filePath = `users/${this.state.displayName}/${this.state.uid}/${moment().format("MMM Do YY")}/${uuidv4()}/${file.name}`
return firebase.storage().ref(filePath).put(file).then((fileSnapshot) => {
// 3 - Generate a public URL for the file.
return fileSnapshot.ref.getDownloadURL().then((url) => {
// 4 - Update the chat message placeholder with the image's URL.
return messageRef.update({
imageUrl: url,
storageUri: fileSnapshot.metadata.fullPath
});
});
});
}).catch(function(error) {
console.error('There was an error uploading a file to Cloud Storage:', error);
});
})
this.setState({
chatFiles: []
})
document.getElementById('file-1').value = "";
}
Here's how I, then, setState when the loading image is added and then when its url is modified: (Notice how I try to filter out the loadingLogo which is the loading image out of the state but it does not obviously work for the reason explained above).
startChat () {
document.getElementById("myForm").style.display = "block";
const ref = firebase.firestore().collection('Chats').doc(this.state.uid).collection('Messages');
const query = ref.orderBy('timestamp', 'desc').limit(10)
this.unsubFromMessages = query.onSnapshot((snapshot) => {
if (snapshot.empty) {
console.log('No matching documents.');
firebase.firestore().collection('Chats').doc(this.state.uid).
set({
name: this.state.displayName,
uid: this.state.uid,
email: this.state.email
}).then(console.log("info saved"))
.catch((error) => {
console.log("Error saving info to document: ", error);
});
}
snapshot.docChanges().reverse().forEach((change) => {
if (change.type === 'removed') {
console.log(change.doc.data().content)
} else if (change.type === 'added') {
this.setState(state => {
const messages = [...state.messages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
} else if (change.type === 'modified') {
const filteredMessages = this.state.messages.filter(message => message.imageUrl !== loadingLogo)
console.log(filteredMessages)
this.setState(state => {
const messages = [...filteredMessages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
});
}, (error) => {console.log(error)});
}
This is part of the Chat's JSX:
<div className="chatArea" id='messages'>
{
this.state.messages.map((message, index) => {
return message.body.uid === this.state.uid
?
<div>
{
message.body.imageUrl ?
<img src={message.body.imageUrl} className="message-sent"></img>
:
<p className="message-sent" key={index}>{message.body.content}</p>
}
</div>
:
<p className="message-received" key={index}>{message.body.content}</p>
})
}
<div style={{ float:"left", clear: "both" }}
ref={(el) => { this.myRef = el; }}>
</div>
</div>
I know the issue is not with Firebase but rather with ReactJS. I know I need to remove, filter out, replace or delete that loading image before or after the modified message with the new url is saved to the state. So, please help me figure this out. I am sure many people may encounter this problem.
Thank you!
I figured it out. I might as well delete this question but it may help someone build a chat with ReactJS and Firebase. Anyways, my approach to filter out based on the object property, imageUrl is a viable option. It works! My silly oversight was that I did not add the parent property or object, "body", after the object "message". More specifically, instead of const filteredMessages = this.state.messages.filter(message => message.imageUrl !== loadingLogo), it should be const filteredMessages = this.state.messages.filter(message => message.body.imageUrl !== loadingLogo). You can also try to add an object property that you can use to filter out messages with, for example, allowed: yes or no. If you need more clarification, just ask me, I am glad to help. Happy coding!

Need to render a list of items/tasks. How can I do it so old items don't get re-rendered?

I am currently pulling from a custom API every second and rendering the items I receieve onto the screen.
Some code of what that looks like:
async componentDidMount() {
await this.grab_slack_user_data()
await this.grab_items()
setInterval(() => {
this.grab_items()
}, this.state.settings.seconds_per_slack_messages_pull * 1000 )
}
grab_items() {
this.setState( { isLoading: true } )
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise( resolve => {
axios.get( url, { headers } )
.then( res => {
resolve()
} )
})
}
And in my render function, I have this:
this.state.items.map(( t, i ) => {
return <Item task={ t } key={ i } user={ this.state.slack_users[ t.usr ] } settings={ this.state.settings } />
) }
I am hitting some issues with some image flickering which would be solved if I didn't re-render items that were already on the screen.
Is there a way to detect already displayed items?
You are using index as a key. Use a unique id for each item in a list to prevent re-renders
this.state.items.map((t) => (
<Item task={t} key={t.id} user={this.state.slack_users[t.usr]} settings={this.state.settings} />
))
React needs this key to perform reconciliation - determining if an element should be reused or created essentially
Take a look at react-virtualized or the more light-weight version react-window. It's a component for efficiently rendering large lists. Specifically the lazy loading packages based off them.

How to load more content from server on Scroll in reactjs

I want to call API and load more item from database using scroll, initially, I want to display five record only, then if user scroll and reach bottom of scroll then I want to display five more record. it is about to lazy loading.
Please I am new to reactjs how can I achieve it.
this is the my code.
{this.state.selected ==='text' && <div style={{overflow:'auto', height:'200px'}} data-tab-content="tab2">
{this.state.textList.length>0 ? this.state.textList.map(text=>
<p >{text.text} </p>
):<p>no record found</p>} */}
</div>}
Here I am making
// get Text List
getTextList(){
debugger
fetch(baseUrl +`/language/listtext/${'1'}/${'5'}/${this.state.lesson_id}/${this.state.premiumprice_id}`)
.then(response => response.json())
.then(data => {
debugger
if(data.status ===200){
debugger
this.setState({
textList: data.text.docs,
})
}else{
this.setState({textList : []})
}
})
}
Thank in advance
Add onScroll event on div like
// for example <div onScroll={this.handleScroll}/>
handleScroll = (e) => {
const bottom = Number((e.target.scrollHeight - e.target.scrollTop).toFixed(0)) - e.target.clientHeight < 50;
let page = this.state.page;
if (bottom) {
// write fetching logic here...
}
};

How to update images while I change the page?

I have displayed some objects which contains pictures in it. When I try to change the page the written data like the name for example updates correctly on the other hand the picture stays the same
I tried placing an imageHash into the url so it updates the state.
state =
{
imageHash = Date.now()
}
return (
<div>
{mediaId.media && mediaId.media.map(photo => (
<img src={`http://127.0.0.1:8000/api/media/${photo.id}?${imageHash}`} alt={`media${photo.id}`}/>
))}
</div>
);
I need the corresponding photo to show.
I found the solution on my own.
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
this.setState({
id: nextProps.id,
imageHash: nextProps.imageHash
});
}
setImmediate(() => this.loadData());
}
It checks for props changes and if there are any it immediately loads the data with the new props.

Resources