Getting Reference Document in Firestore with React using react-redux-firebase - reactjs

I'm working with a collection which documents looks like these
Visit {
point: document-ref
}
the point contains the lat and long for the point.
As you can see, I'm retrieving the document for each visit. But I'm also having a huge problem: I'm rocketing my cloud usage.
Is there a way to do this without getting my usage up in the sky?
I'm using react with with react-redux-firebase
useSelector(({ firestore: { ordered: { visitas } } }) => {
return (
visitas &&
Promise.all(
visitas.map(async v => {
return { ...v, punto: (await v.puntoId.get()).data() };
})
).then(v => {setVisitas(v)})
);
});
I'm looking for a way to retrieve the documents, with the point already loaded in it.

Related

how can i get data of a specific id from backend in react js?

I am trying to get a specific id related data from backend instead of whole database and then comparing the id. i am passing id using state param. I am new to react so it will helpful to explain as simply as you can.
this is my service for getting data
import http from "./http";
const getBoxes = () => {
return http
.get("/box/getAllBox")
.then((result) =>
result.data.content.map((item, index) => ({ ...item, key: index }))
);
};
You can easily do that by using the filter() property
For example
import http from "./http";
const getBoxes = () => {
return http
.get("/box/getAllBox")
.then(result=>{
result.data.content.filter(item=> item.id=="your ID")
})
.then((result) =>
result.data.content.map((item, index) => ({ ...item, key: index }))
);
};
Replace your ID with your custom id
You should create a backend endpoint (if possible) where you can get one box. EG /box/{id}.
This means when you get this endpoint in react you can do result.data.{some property from the returning object} eg result.data.boxName
If you don't have the ability to change the backend, you could get the specific id from the array of boxes by looping through the results and finding where ID of the returned object matches the ID you're looking for - this requires the array of objects being returned to each contain an ID field.
import http from "./http";
const getBoxes = () => {
return http
.get("/box/getAllBox")
.then((result) =>
let itemFromID;
for(let i = 0; i < result.data.length; i++) {
let item = result.data[i];
if(item.id === 1) {
itemFromID = item;
break;
}
}
// now you have the item in itemFromID variable
console.log(itemFromID);
);
};

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!

Problem when adding a picture from flamelink

I have a problem when I'm trying to load a picture from my Flamelink schema that is connected to my Firebase DB.
I have a simple component that displays a puppy for sale:
const PuppyCard = (props) => {
return (
<div className='PuppyCard'>
<div className='PuppyCard__img'>
<img src={props.data.picture} alt={props.data.name} />
</div>
<div className='PuppyCard__text'>
<h2>{props.data.name}</h2>
<h3>{props.data.sex}</h3>
<h4>{props.data.age}</h4>
<h4>{props.data.price}</h4>
</div>
</div>
)
}
And as you can see there is a props.data.picture as my src for the image, but when i try to load it on the website i get everything but the picture. After looking into my DB i saw that the picture is not a file but a reference to the other folder created by flamelink:
1
This is what i am fetching from the DB right now.
getPuppies = () => {
db
.collection('fl_content')
.get()
.then(docs => {
if (!docs.empty) {
let allPuppies = []
docs.forEach(function (doc) {
const puppy = {
id: doc,
...doc.data()
}
allPuppies.push(puppy)
})
this.setState({
puppies: allPuppies
}, () => {
this.setState({
isLoaded: true
})
})
}
})
}
Any help is appreciated!
I've managed to solve this problem, although now i realize that the question is not accurate.
What I was trying to do was to get the picture that was uploaded in the Flamelink CMS to display on the website, however i was only getting a file ID when i tapped into the property of the props.
My solution to this problem was:
store the fileID in a variable
Query the folder created by the flamelink CMS inside the Cloud Firestore called "fl_files" =>
db.collection('fl_files').doc(fileID).get().then(function (doc) {
var fileName = doc.Nf.nn.proto.mapValue.fields.file.stringValue;
console.log(fileName);
storageRef.child('flamelink/media/' + fileName).getDownloadURL().then(function (url) {
console.log(url);
var img = document.getElementById('an id for the image inside a react child component');
img.src = url
})
Please note that 'flamelink/media/' is inside the Storage, not inside Cloud Firestore.
Hope this helps somebody with the same problem I've had!
Issue closed :)

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.

GatsbyJS getting data from Restful API

I am new in both React and GatsbyJS. I am confused and could not make figuring out in a simple way to load data from third-party Restful API.
For example, I would like to fetch data from randomuser.me/API and then be able to use the data in pages.
Let’s say something like this :
import React from 'react'
import Link from 'gatsby-link'
class User extends React.Component {
constructor(){
super();
this.state = {
pictures:[],
};
}
componentDidMount(){
fetch('https://randomuser.me/api/?results=500')
.then(results=>{
return results.json();
})
.then(data=>{
let pictures = data.results.map((pic,i)=>{
return(
<div key={i} >
<img key={i} src={pic.picture.medium}/>
</div>
)
})
this.setState({pictures:pictures})
})
}
render() {
return (<div>{this.state.pictures}</div>)
}
}
export default User;
But I would like to get the help of GraphQL in order to filter & sort users and etc…..
Could you please help me to find the sample to how I can fetch data and insert them into GraphQL on gatsby-node.js?
If you want to use GraphQL to fetch your data, you have to create a sourceNode. The doc about creating a source plugin could help you.
Follow these steps to be able to query randomuser data with GraphQL in your Gatsby project.
1) Create nodes in gatsby-node.js
In your root project folder, add this code to gatsby-node.js:
const axios = require('axios');
const crypto = require('crypto');
exports.sourceNodes = async ({ actions }) => {
const { createNode } = actions;
// fetch raw data from the randomuser api
const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
const res = await fetchRandomUser();
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last,
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail,
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(userNode))
.digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
return;
}
I used axios to fetch data so you will need to install it: npm install --save axios
Explanation:
The goal is to create each node for each piece of data you want to use.
According to the createNode documentation, you have to provide an object with few required fields (id, parent, internal, children).
Once you get the results data from the randomuser API, you just need to create this node object and pass it to the createNode() function.
Here we map to the results as you wanted to get 500 random users https://randomuser.me/api/?results=500.
Create the userNode object with the required and wanted fields.
You can add more fields depending on what data you will want to use in your app.
Just create the node with the createNode() function of the Gatsby API.
2) Query your data with GraphQL
Once you did that, run gatsby develop and go to http://localhost:8000/___graphql.
You can play with GraphQL to create your perfect query. As we named the internal.type of our node object 'RandomUser', we can query allRandomUser to get our data.
{
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
3) Use this query in your Gatsby page
In your page, for instance src/pages/index.js, use the query and display your data:
import React from 'react'
import Link from 'gatsby-link'
const IndexPage = (props) => {
const users = props.data.allRandomUser.edges;
return (
<div>
{users.map((user, i) => {
const userData = user.node;
return (
<div key={i}>
<p>Name: {userData.name.first}</p>
<img src={userData.picture.medium} />
</div>
)
})}
</div>
);
};
export default IndexPage
export const query = graphql`
query RandomUserQuery {
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
`;
That is it!
Many thanks, this is working fine for me, I only change small parts of the gastbyjs-node.js because it makes an error when use sync & await, I think I need change some section of a build process to use babel to allow me to use sync or await.
Here is the code which works for me.
const axios = require('axios');
const crypto = require('crypto');
// exports.sourceNodes = async ({ boundActionCreators }) => {
exports.sourceNodes = ({boundActionCreators}) => {
const {createNode} = boundActionCreators;
return new Promise((resolve, reject) => {
// fetch raw data from the randomuser api
// const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
// const res = await fetchRandomUser();
axios.get(`https://randomuser.me/api/?results=500`).then(res => {
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto.createHash(`md5`).update(JSON.stringify(userNode)).digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
resolve();
});
});
}
The accepted answer for this works great, just to note that there's a deprecation warning if you use boundActionCreators. This has to be renamed to actions to avoid this warning.
You can get data at the frontend from APIs using react useEffect. It works perfectly and you will no longer see any error at builtime
const [starsCount, setStarsCount] = useState(0)
useEffect(() => {
// get data from GitHub api
fetch(`https://api.github.com/repos/gatsbyjs/gatsby`)
.then(response => response.json()) // parse JSON from request
.then(resultData => {
setStarsCount(resultData.stargazers_count)
}) // set data for the number of stars
}, [])
The answers given above work, except the query in step 2 seems to only return one node for me. I can return all nodes by adding totalCount as a sibling of edges. I.e.
{
allRandomUser {
totalCount
edges {
node {
id
gender
name {
first
last
}
}
}
}
}

Resources