Capturing data out of a firebase collection, and actually making it usable? - javascript

I have a collection in firebase called "polls". The collection is an array, and within the array I have 4 urls (url_a, url_b, url_c, and url_d). I want to pull these urls out of the array in my react app so I can use them as the src for 4 different images.
I know since "polls" is an array, I can't just pull the urls directly by setting some variable to "polls.data.url_a". With the 2nd useEffect function in the code below, I'm able to console.log the array
and the data within it using forEach. I can console.log poll.data, and even poll.data.url_a and it returns the proper result.
But I am stuck on how to get outside of that forEach and actually capture the individual urls to where I can assign them to a variable so I can make use of them.
import { useEffect, useState } from "react"
import { collection, getDocs } from "firebase/firestore"
import { db } from '../firebase.config'
function Food() {
const [polls, setPolls] = useState([])
useEffect(() => {
getPolls()
}, [])
** useEffect(() => {
polls.forEach((poll) =>
console.log(poll.data.url_a))
}, [polls])
**
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const poll = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
setPolls(poll)
}).catch(error => console.log(error.message))
}
return (
<div>
Food
</div>
)
}
export default Food
I've tried setting a variable within the forEach and calling it later, and running a forEach later on in the function. But I just don't know how to get to the URL in a usable way, outside of just console logging it. Any help would be greatly appreciated, thanks!

Check out what Renaud suggested here to see how to assign variables: Get Firebase Collection into simple array
If I understand what you're asking, you'll need the poll const to instead point towards a global store variable or to reference it within the template section.

If the data your fetching is structured like this:
const polls = [{
data: {
url_a: "url a",
url_b: "url b"
}
}]
It's easier to just assign it to an object state variable instead of an array.
const [polls, setPolls] = useState({})
// ...
useEffect(() => {
getPolls()
}, [polls])
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const polls = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
polls.map(poll => setPolls(poll.data))
}).catch(error => console.log(error.message))
}
// ...
return <img src={polls.url_a} alt="" />

Related

Why changes made in database not updates in realtime?

I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.
Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId

how to use useState inside inner function scope in react.js hooks

I am completely new to react.js.
I am fetching async data from my server that uses express.js. After I get that data I want to set my house parameters when I open that page for the first time.
const Houses = () => {
const [house, setHouse] = useState({});
...
useEffect(() => {
const fetchData = () => {
fetch('http://localhost:9000/houses')
.then(res => res.text())
.then(res => {
if (res) {
let houseData = JSON.parse(res);
console.log(houseData); // server res prints correctly - prints: { name: 'sweet house', address: 'fukuoka hakata' }
setHouse({...house, houseData});
console.log(house); // doesnt change after sethouse - prints : {}
}
});
}
fetchData();
}, []);
...
}
Im getting the data from my server without any problem.
The problem is that the house parameters dont get updated. I know its a different scope, what I need to know is how I do it in this case. Thanks
You cannot access the house state immediately after setting it, setHouse may be batched.
See more of State: https://reactjs.org/docs/state-and-lifecycle.html
You are trying to do
let houseData = JSON.parse(res);
// houseData = { name: 'sweet house', address: 'fukuoka hakata' }
setHouse({ ...house, houseData: houseData });
instead of setHouse(houseData). Since houseData is an Object, you can directly set it.
Replace
setHouse({...house, houseData})
with
setHouse(houseData)

Trying to store FireStore array in React Native?

I have been trying to push or store a FireStore array in one of my own arrays. I have tried a few versions of code, the first being this:
var data = [];
db.collection('Rooms')
.doc(code)
.get()
.then((docs) => data.push(docs.data()));
However, when I log the data variable, it comes out as an empty array. The second method I have tried is this:
var [data, setData] = useState([]);
db.collection("Rooms")
.doc(code)
.get()
.then((docs) => setData(docs.data()));
However this method seems to setData infinitely, so it is reading into my API infinitely, which I would like to avoid. The last method I tried was this:
var data = db.collection("Rooms").doc(code).get();
console.log(data);
But this just returns
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
}
Could anyone help me with this, ideally I'd like to store the data of an array called "MovieArray" inside the document, but I can't even access the document, so even if you can just help me store the data of the whole document, it would be very helpful.
If you are using react, I would suggest using the hook. You also, don't really need to push objects to an array like that.
Here is an example of how to get some data and store the collection of data.
const Forum = () => {
const [posts, setPosts] = useState(null);
const collectIdsAndDocs = (doc) => {
return { id: doc.id, ...doc.data() };
};
useEffect(() => {
const getPost = async () => {
const snapshot = await firestore.collection('Posts').get();
const myPosts = snapshot.docs.map(collectIdsAndDocs);
console.log(myPosts);
setPosts({ myPosts });
};
const createPost = async (post) => {
const docRef = await firestore.collection('Posts').add(post);
const doc = await docRef.get();
console.log(doc);
};
createPost({ Title: 'My First Post', Content: 'My content' });
getPost();
}, []);
return (
// return some JSX
);
};
Why does this work?
When you get a collection, Firebase returns a snapshot of the collection.
This snapshot has a list of docs or an array if you will.
We then want to map over those docs constructing a new object that contains just the document data and the ID of individual doc. This is what the myPosts variable is.
Using the react state hook, you can set that object to the current state of Posts, in your case this would be rooms.
When you add something to the database, Firestore will return a reference to the newly added item. You can then call get() to get the document back if you need it.
Try changing to (see comment before this)
const [data, setData] = useState({});

Why is React not treating my array properly?

When I render this React component:
import React, { useState, useEffect, useCallback } from "react";
// import RenderLineChart from "../recharts/rechart.component";
import axios from "axios";
import "./chart-item.styles.scss";
const ChartItem = ({ apiUrl }) => {
const [chartData, setChartData] = useState([]);
//function that pulls data from APIs
const loadChartData = useCallback(() => {
axios.get(apiUrl).then((response) => {
setChartData(response.data);
});
}, [apiUrl]);
//runs initial pull from API
useEffect(() => {
loadChartData();
}, [loadChartData]);
console.log("Raw Data: ");
console.log(chartData);
console.log("Array: ");
console.log(chartData.historical);
// console.log("Array Element: ");
// console.log(chartData.historical[0]);
return <div className="chart"></div>;
};
export default ChartItem;
The console output is such:
When I uncomment that third console.log statement and save the component file without refreshing localhost, the output is:
But when I actually refresh the localhost tab, the output is such:
As if it is no longer being treated as an array. Can anyone explain what's going on here?
You have 2 mistakes in the snippet you posted.
You're declaring chartData as an array, but then you're updating it with an object (inside the loadChartData function)
When page loads the console.log is executed before the loadChartData function has completed the request, so at the first render chartData = [] and chartData[0] = undefined, but if you try to get chartData.historical[0] it throws you an error because it is defined as an array.
So, how can you fix this? The answer is pretty straightforward, first of all you have to use a consistend data-type for your state, if it's an array when declared, then when you update it, pass an array, if not you'll always have this kind of problems. This should fit your needs. If you want to log chartData everytime it upates you just need to write a new useEffect with the console.log inside and chartData as "dependency"
const [chartData, setChartData] = useState({ historical: [] })
// declare you api call
const apiCall = useCallback(() => {
try {
const response = await axios.get(url);
setChartData(response.data)
} catch (e) {
console.error(e)
}
}, [setChartData])
useEffect(() => {
apiCall()
}, [apiCall])
useEffect(() => {
console.log("Raw Data: ");
console.log(chartData);
console.log("Array: ");
console.log(chartData.historical);
console.log("Array Element: ");
console.log(chartData.historical[0]);
}, [chartData])
// do you stuff with jsx
I suggest you to take a look at Hooks Documentation
Hope this helps ✌️

Store data from useQuery with useState

I'm using React hooks both to fetch GraphQL data with react-apollo and to store local state:
const [userData, setUserData] = useState({})
const { loading, error, data } = useQuery(USER_QUERY)
However, I'm wondering how to store data to userData. Is this how it's supposed to work:
useEffect(() => {
setUserData(data)
}, [Object.entries(data).length])
Looks like what you have probably works. There is also a onCompleted option available in the options parameter. it takes a callback of type:
(data: TData | {}) => void
so this is another way of doing it:
const { loading, error, data } = useQuery(USER_QUERY, {onCompleted: setUserData})
What are you trying to do with the returned data that you are unable to accomplish by simply using it as destructured from the query hook? In most use cases it can be used immediately, as it will update itself when refetched.
If it is necessary (and it could be), as the other answer says, the useEffect hook you posted should work, but I would replace the dependency with simply data, to prevent an edge case where the response has an equal length consisting of different data and does not update:
useEffect(() => {
setUserData(data)
}, [data])
I think something like this would work - you will need to create the initial state with useState, could be empty array and then onComplete in the useQuery would setTranscationsData... it is triggered every render when state or props change. Could of course add an inital state inside useState which insn't an empty array.
const [transactionsData, setTransactionsData] = React.useState([]);
const { error, data } = useQuery(GET_TRANSACTIONS, {
onCompleted: () => {
setTransactionsData(data.transactions);
},
});
another example
const [getLegalStatement] = useLazyQuery(GET_LEGAL_STATEMENT, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
setTempLegalStatement(data.getLegalStatement);
},
onError: () => {
setTempLegalStatement({
consentedLegalStatementHash: '',
consentedSuppliersHash: '',
statement: '',
suppliersModal: '',
});
setTimeout(() => {
setRefetchNeeded(true);
}, 10000);
},
});
Use onSuccess
const [userData, setUserData] = useState({})
const { data, isLoading, error } = useQuery('QueryKey', QueryFunction, { onSuccess: setUserData })
This onSuccess callback function will fire setUserData(data) for you automatically any time the query successfully fetches new data.
To elaborate above, you can't use onSuccess/onSettled because those will not rerun if the data is cached, so if you leave the component and come back before the query expires your data won't get set.

Categories

Resources