I am working on the backend of my React Native project with Firebase. I want to retrieve records from the database and render a flat ist from an array. I tried to make an array, but outside the function, it is empty again...
Here is my code:
var brand_list = [] // the list
//retrieve record from firebase database
firebase.firestore().collection('Brand').get().then((querySnapshot) => {
querySnapshot.forEach(snapshot => {
let data = snapshot.data();
brand_list.push(data);
})
console.log(brand_list) // contains the records
})
console.log(brand_list) // empty again
What is a possible fix for this, so I can nicely render my flatlist?
In react-native you need to use the state to first initialize the variable and then use setState() to update the variable, setState() functions tell the react-native to update the UI. if the array is empty outside the function that means you need to use async-await here, to make sure you await for a function to be finished first.
I think you try this:
constructor() {
super();
this.state = { dataFetched: [] };
}
async fetchList(){
await firebase.firestore().collection('Brand').get().then((querySnapshot) => {
querySnapshot.forEach(snapshot => {
let data = snapshot.data();
this.setState((prevState)=>{
dataFetched:[...prevState.dataFetched, data]
});
})
console.log(this.state.dataFetched);
// now you can use this.state.dataFetched to update the flatList
}
Related
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
I'm making a react app that works with a API that provides data to my App. In my data base I have data about pins on a map. I want to show the info of those pins on my react app, I want them to render. I get that information with axios and this url: http://warm-hamlet-63390.herokuapp.com/pin/list
I want to retrieve the info from that url, with axios.get(url), stringify the JSON data and then parse it to an array of pins.
The Problem:
My page will be rendered before I get the data back from the server, because axios is async, so I will not be able to show anything. UseEffect and useState won't work because I need something in the first place (I think).
What i've tried:
I tried to use useEffect and useState, but as I said, I think I need something in the first place to change it after. I also tried to use await, but await won't stop the whole React App until it has a response, although it would be nice if there is something that stops the app and waits until I have the array with the info so I can show it on the App then. I tried everything with async. I'm fairly new to React so there might be something basic i'm mssing (?). I've been on this for days, I can't get this to work by any means.. Any help, youtube videos, documentation, examples, is help. Anything. How the hell do I render something that needs to wait for the server respond?
My code:
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
async function pin(){
const result = []
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
const res = await axios.get(url)
console.log(res.data.data);
if(res.data){
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
console.log(result);
}
return result;
}
class App extends React.Component{
render(){
return(
<div>
<Pin/>
<Mapa/>
</div>
)
}
}
export default App
I don't fully understand what you are trying to output but how you would usually handle this is with both the useState hook and the useEffect hook see example below.
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
const pin = () => {
const [result, setResults] = useState([]);
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
useEffect(() => {
//Attempt to retreive data
try {
const res = transformData();
if (res) {
// Add any data transformation
setResults(transformData(res))
}
else {
throw (error)
}
}
catch (error) {
//Handle error
}
}, [])
// Handle data transformation
const transformData = async () => {
const res = await axios.get(url)
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
return result
}
if (!result) {
// Return something until the data is loaded (usually a loader)
return null
}
// Return whatever you would like to return after response succeeded
return <></>;
}
This is all assuming that Pin is a component like you have shown in your code, alternatively, the call can be moved up to the parent component and you can add an inline check like below to render the pin and pass some data to it.
{result && <Pin property={someData} />}
Just a bit of background the useEffect hook has an empty dependency array shown at the end "[]" this means it will only run once, then once the data has updated the state this will cause a rerender and the change should be visible in your component
Rest assured, useEffect() will work. You need to use a condition to conditionally render the content when it comes back from the server.
In the example below if results has a length < 1 the message Loading ... will be rendered in the containing <div>, once you're results are received the state will be updated (triggering a re-render) and the condition in the template will be evaluated again. This time though results will have a length > 1 so results will be rendered instead of Loading ...
I’m operating under the assumption that you’re function pin() is returning the results array.
const app = (props) => {
const [results, setResult] = useState([]);
React.useEffect(() => {
const getPin = async () => {
if (!results) {
const results = await pin();
setResult([…results])
}
}
getPin();
},[results]);
return (
<div>
{result.length ? result : 'Loading ... '}
</div>
)
}
Started playing around with createAsyncThunk for learning purpose, decided to implement a shopping cart with firebase firestore but I ran into problems when trying to implement pagination in my react app.
How should I return the last visible state into my redux state during the initial load and subsequent load (infinite loading)
I am basing on code from redux tutorial sandbox :https://codesandbox.io/s/github/reduxjs/redux-essentials-example-app/tree/checkpoint-3-postRequests/?from-embed, but instead of connecting to a fake api, I am using firebase firestore.
Code to fetch product from firestore : ProductSlice.js
const InitialState = {
products : [],
status: 'idle',
error: null,
last: null, //to hold lastVisible when fetching data from firestore
}
export const fetchProducts = createAsyncThunk(types.RECEIVE_PRODUCTS, async (limit) => {
const resp = await fire_base_product.firestore()
.collection(collection_name).orderBy('id').limit(limit)
let result = resp.get().then((querySnapshot) => {
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1] //how set this to redux state
const products = querySnapshot.docs.map((doc)=> {
return { ...doc.data()}
})
return {
products: products,
lastVisible: lastVisible
};
})
return result;
}
I am not quite sure on how to set this lastVisible data back into redux state, is it possible to do that with reference?
#Edit:
Tried to return both product list and last visible as an array and assign lastVisible in createSlice as stated below:
const productSlice = createSlice({
name:'products',
initialState:
reducers: {},
extraReducers:{
[fetchProducts.fulfilled]: (state, action) => {
state.products = state.products.concat(action.payload.products)
state.last = action.payload.lastVisible // this causes call stack error
}
}
});
With the above coding, two error will be reported if I run react app,
Trying to assign non serialize value into redux state
Maximum call stack size exceeded in firestore
I then tried to add middleware serializableCheck during create configuration as below:
export default configureStore({
middlleware: getDefaultMiddlleWare({
serializableCheck: {
//ignore action type
ignoredActions : ['RECEIVE_PRODUCTS/fulfilled']
// ignore path
ignoredPath: ['products.last']
}
}),
... // reducer codes
})
Even though now I have dismissed the first error, call stack exceeded still exists. Does anyone knows why this is happening ? Feel free to discuss if there is any workaround on this. Thanks.
Edit 2
Similar approach works when using context but does not work when using redux. Do I need to wrap return in promise as suggested in Firebase Unhandled error RangeError: Maximum call stack size exceeded ?
Did not managed to find a way to save lastVisible, but I found a workaround by just keeping track of the last retrieve id of my firestore data by saving it into redux state.
export const fetchProducts = createAsyncThunk(types.RECEIVE_PRODUCTS, async (limit) => {
const resp = await fire_base_product.firestore()
.collection(collection_name).orderBy('id').limit(limit)
let result = resp.get().then((querySnapshot) => {
var lastVisible = limit - 1; //only keep track of ID so we can avoid saving un-serialize coded
const products = querySnapshot.docs.map((doc)=> {
return { ...doc.data()}
})
return {
products: products,
lastVisible: lastVisible
};
})
return result;
}
And when during fetch of additional data we can then access the state by using getState() as below:
export const fetchMoreProducts = createAsyncThunk(types.LOAD_MORE_PRODUCTS, async (limit, {getState}) => {
const last = getState().products.last
var newProducts = await firebase_product.firestore()
.collection('store_products').orderBy('id')
.startAfter(last).limit(limit)
const result = newProducts.get().then((querySnapshot) => {
var lastVisible = last + limit;
const products = querySnapshot.docs.map((doc) => {
return { ...doc.data() }
})
return {
products : products,
lastVisible: lastVisible
}
})
// return retrieved data from firebase
return result
})
But doing this, I could skip the serialization check config all together as well. Not sure if this is the correct way, but this is how I got pagination working. Feel free to let me know if there is other way to approach this.
This error will come if you store non-serializable object in redux store!
if you are getting some non-serializable data from firebase, serialize it before storing it in redux store!
const nonSerializable = firestore().collection().doc(uid).get().data();
// if using this nonSerializable object in reducer, serialized it using JSON.parse(JSON.stringify())
const serializable = JSON.parse(JSON.stringify(nonSerializable))
you should save last visible path not the whole data like this
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path
I am building a react native app where I need to place all the data of users in a firebase firestore database into a flatlist. I am unsure of how to do this with all updated react native code, as all other stack overflow questions do not answer this question and I can not find any help elsewhere.
My code for fetching the react native data is:
state = {
data: []
};
unsubscribe = null;
componentDidMount() {
const user = this.props.uid || Fire.shared.uid;
const list = [];
this.unsubscribe = Fire.shared.firestore
.collection("users")
.doc(data)
.onSnapshot(doc => {
list.push(data);
});
this.setState({
data: list
)};
}
componentWillUnmount() {
this.unsubscribe();
}
The database structure is that the user has a name and an email. I try to call this by typing: "{this.state.name.email}", which does not work in the Flatlist. I already initialized my app in another file, where Fire.shared = new Fire(); is exported. If any help would be given on how to export such data into the state properly, I would be largely grateful. Thank you in advance!
The call to setState needs to be in the callback, so:
componentDidMount() {
const user = this.props.uid || Fire.shared.uid;
const list = [];
this.unsubscribe = Fire.shared.firestore
.collection("users")
.doc(data)
.onSnapshot(doc => {
list.push(data);
this.setState({
data: list
)};
});
}
I am developing a React app that takes data from a database on Google's Firebase.
The database is organized this way:
users {
user 1 {
name
surname
...
}
user 2 {
name
surname
...
}
}
How I retrieve data in React:
I take location and instrument from the url to filter the results of the search (using just location for now, but would like to use both), and I successfully manage to get the object printed in the console.
componentWillMount(){
const searchResults = FBAppDB.ref('users');
let location = this.props.location;
let instrument = this.props.instrument;
let profiles = [];
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
let users = snap.val();
Object.keys(users).map((key) => {
let user = users[key];
console.log(user);
profiles.push(user);
});
});
// this.setState({users: profiles[0]});
}
console output of the object:
Object {name: "Whatever", surname: "Surrname", ... }
What I seem to fail doing is to save these objects (could be more than one) in the state, so I can update the view when the user changes other filters on the page.
I tried to setState the actual user but the state results empty, any idea?
In addition to writing, what would be the best way to get the data back from the state? How do I loop through objects in the state?
Thanks
EDIT
This is how my render function looks like:
render(){
return(
<Row>
{
Object.keys(this.state.users).map(function (key) {
var user = this.state.users[key];
return <SearchResult user={user} />
})
}
</Row>
);
}
I tried to print this.state.users directly, but it doesn't work. Therefore I tried to come up with a (non-working) solution. I'm sorry for any noob mistake here.
I tried to setState the actual user but the state results empty, any
idea?
It is because your DB operation searchResult is asynchronous and JavaScript does not let the synchronous codes wait until the async code completes.
In your code, if you enable the commented setState invocation, that code will get executed while the async code is waiting for response.
You need to setState inside:
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
let users = snap.val();
const profiles = Object.keys(users).map((key) => users[key]);
this.setState({ users: profiles[0] });
});
Whenever you want something to be executed after an async code, put that inside a callback (to the async fn) or use a Promise.
What would be the best way to get the data back from the state? How do I loop through objects in the state?
setState can be asynchronous too. But you will get the new updated state on next render. So inside render() you can access users in the state as this.state.users and perform your operations using it.
UPDATE:
componentWillMount(){
const searchResults = FBAppDB.ref('users');
const location = this.props.location;
const instrument = this.props.instrument;
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
const users = snap.val();
const profiles = Object.keys(users).map((key) => users[key]);
// previously we were setting just profiles[0]; but I think you need
// the entire list of users as an array to render as search results
this.setState({ users: profiles }); // set the whole data to state
});
}
render(){
const users = this.state.users || []; // note that this.state.users is an array
return(
<Row>
{
users.length > 0
? users.map((user) => <SearchResult user={user} />)
: null
}
</Row>
);
}
Try putting setState inside the .on(() => ...) just after Object.keys(...). It's an asynchronous call to the server, right? I suppose that setState is called right after the call to the server is made, but before you retrieve any data.