Firebase promise - unable to set state, state array is empty - reactjs

I have a promise to get a firebase collection and then return respective data (all according firebase documentation).
The data is returning fine, the only problem is that it seems that I am unable to set the collection data to a state array. When console.log(chats)this returns an empty array.
The data is returning fine as adding a console.log(doc.data()) in the first then logs the data ok but then it is empty.
What am I doing wrong in the promise?
const HomeScreen: React.FC<Props> = ({ navigation}) => {
const [chats, setChats] = useState<{[key: string]: any}>([]);
useEffect(() => {
const chatsArray = [{}];
db.collection("chats")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
chatsArray.push({
id: doc.id,
data:doc.data(),
})
});
})
.then(() => {
setChats(chatsArray)
console.log(chats);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}, []);
return (
<SafeAreaView>
<ScrollView style={styles.container}>
{chats go here}
</ScrollView>
</SafeAreaView>
)
}
export default HomeScreen;

The console.log(chats); doesn't work, since updating the state is a asynchronous operation that won't have completed by the time you log the value. Try console.log(chatsArray); to see the actual value.
In addition, consider passing the array as the result along the promise chain instead of using a variable in the context:
db.collection("chats")
.get()
.then((querySnapshot) => {
return querySnapshot.docs.map((doc) => {
return {
id: doc.id,
data:doc.data(),
}
});
})
.then((chatsArray) => {
setChats(chatsArray)
console.log(chatsArray);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
Or simpler and with the same result:
db.collection("chats")
.get()
.then((querySnapshot) => {
const chatsArray = querySnapshot.docs.map((doc) => {
chatsArray.push({
id: doc.id,
data:doc.data(),
})
});
setChats(chatsArray)
console.log(chatsArray);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});

Related

TypeError: Cannot read properties of undefined (reading 'setRestaurants')

I'm working on a project where I am trying to fetch a list of restaurants from a database and display them on the screen.
When I run the code below I get this error:
TypeError: Cannot read properties of undefined (reading
'setRestaurants')
at CustomerDashPage.js:39
at async fetchList (CustomerDashPage.js:39)
at async getList (CustomerDashPage.js:32)
I know the fetch from the database works as I can console.log restaurants after I get them and all the tags from the database are the same as what is initially in the useState.
const [restaurants, setRestaurants] = useState([
{
Restaurant_id: "R763567026",
Restaurant_menuID: 0,
Restaurant_name: "Boston Pizza",
Restaurant_address: "271 Blackmarsh Rd",
Restaurant_postal: "P1Z 7A5",
Restaurant_username: "firstrest",
Restaurant_orders: ["O415052628", "O321764897", "O252073901", "O724516036"],
Restaurant_menuID: "M859068353",
Restaurant_category: "Japanese",
Restaurant_availability: true,
Restaurant_openHour: "11h00",
Restaurant_closeHour: "22h00",
},
]);
useEffect(() => {
const getList = async () => {
const fetchRest = await fetchList('R763567026');
}
getList();
}, [])
const fetchList = async (id) => {
try {
const resp = await fetch("/restaurant/id/" + id)
.then((resp) => resp.json())
.then(data => this.setRestaurants(data)).then(console.log(restaurants))
.catch(err => console.log(err));
}
catch (err) {
throw err;
console.log(err);
}
return true;
}
//Controls what happens when a restaurant is selected.
const selectRestaurant = async (id) => {
console.log(id);
};
return (
<div>
<Header />
<RestaurantList
itemList={restaurants}
component={RestaurantCard}
onSelect={selectRestaurant}
>
{" "}
</RestaurantList>
</div>
);
};
export default CustomerDash;
Any help would be much appreciated
As Abu mentioned in his answer, you need to call setRestaurants, not this.setRestaurants. Also, since you are using async/await syntax, you don't need all of those .then() calls.
const fetchList = async (id) => {
const response = await fetch(`/restaurant/id/${id}`).catch((err) => throw err);
const json = await response.json();
setRestaurants(json);
console.log(restaurants);
return true;
};
It's functional component so use setRestaurants instead of this.setRestaurants
const fetchList = async (id) => {
try {
const resp = await fetch("/restaurant/id/" + id)
.then((resp) => resp.json())
.then(data =>
setRestaurants(data))
.catch(err => console.log(err));
}
catch (err) {
throw err;
console.log(err);
}
}
After updating state, you won't get state value instantly. so your console.log(restaurants) won't work.

Read Data from Firebase and save it into an Array - React

i just can't figure out why i can't save my loaded data into an array.
i`m trying to push the data to the array once the data is fully loaded (Within then())
Any idea why it's not working?
Many thanks :)
useEffect(() => {
fetchData = async () => {
let tempArray = [];
await firebase.firestore().collection('users').get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
firebase.firestore().collection('users').doc(doc.id).collection('posts').get().then((snapShot) => {
snapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
})
})
console.log(tempArray) // Output: Array []
}
fetchData();
}, [])
.forEach IS NOT ASYNCHRONOUS - it WILL NOT wait for your inner-loop .get()s. You need to do something like:
await firebase.firestore().collection('users').get().then((querySnapshot) => {
Promise.all(querySnapshot.map((doc) => {
firebase.firestore().collection('users').doc(doc.id).collection('posts').get().then((snapShot) => {
snapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
})
)
})
In addition - this seems pretty dang inefficient - since you're fetching ALL your users, and ALL their posts, you could just use a collectionGroup is a SINGLE round-trip, then sort by .parent if you need sorting (you don't show any such need in the example presented)
await firebase.firestore()..collectionGroup('posts').get().then((querySnapShot) => {
querySnapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
Finally, you're mixing async/await with .then() syntax, which is generally not recommended:
// .get() is asynchronous
const querySnapShot = await firebase.firestore()..collectionGroup('posts').get();
// push() is synchronous, so need for await
querySnapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})

Render array's nested object values react

Here is how my savedLinksData array prints in my console:
Here is my code that attempts to get the linkType value:
{savedLinksData.map((saved) => {
return <h1>{saved.linkType}</h1>;
})}
What I'm doing wrong?
I think there may be something wrong with the way I stored the values in the array. It doesn't look like the value are inside of the array.
Picture of console.log("----->", saved)
const [savedLinksData, setSavedLinksData] = useState([]);
// query for saved links data
useEffect(() => {
if (user) {
async function fetchData() {
const request = await db
.collection("users")
.doc(user)
.collection("saved")
.onSnapshot((snapshot) =>
setSavedLinks(
snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
)
);
}
fetchData();
} else {
setSavedLinks([]);
}
}, [user]);
useEffect(() => {
if (savedLinks.length > 0) {
let newArray = [];
savedLinks.map((saved) => {
db.collection("users")
.doc(saved.savedUser)
.collection("links")
.doc(saved.savedLinkId)
.get()
.then(function (doc) {
if (doc.exists) {
// console.log("Document data:", doc.data());
newArray.push(doc.data());
// setSavedLinksData([...savedLinksData, doc.data()]);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch(function (error) {
console.log("Error getting document:", error);
});
});
setSavedLinksData(newArray);
}
}, [savedLinks]);

React updating data of an array but changes not appears

I have a array of groups which is brought from API, and is displayed on a table..
return(
<Table
data={props.data}
columns={columns}
noHeader={true}
/>
)
const mapStateToProps = state => ({
data: state.Table.arrayGroups,
})
In this table I have a column that has a button that enable and disable a group, that calls this action:
export const handleStatus = (data, status, id, endPoint, ActionType) => {
let index = data.findIndex( array => array.id === id);
console.log(index)
if (status) {
console.log('entrei disable')
data[index].enable = false
console.log(data)
return dispatch => {
httpPut(`${endPoint}/${id}/disable`)
.then(resp => {
return [
dispatch({
type: ActionType,
payload: data
})
]
})
.catch(error => {
console.log(error)
})
}
}
else {
console.log('entrei enable')
data[index].enable = true
return dispatch => {
httpPut(`${endPoint}/${id}/enable`)
.then(resp => {
return [
dispatch({
type: ActionType,
payload: data
})
]
})
.catch(error => {
console.log(error)
})
}
}
}
On this action I'm basically getting data array and changing a value inside of it.. and update the arrayGroups.. like this :
case 'MODIFIED_GROUPS':
return { ...state, arrayGroups: action.payload }
the issue is : after doing it table continuous displaying the previous array Data
Connect by react-redux is a PureComponent . What it means is that unless the props change it doesn't trigger a re-render. And it performs a shallow equality and reference check.
Since in your case you are mutating the data, the update isn't triggereing.
Clone the data obejct and update it
export const handleStatus = (data, status, id, endPoint, ActionType) => {
let index = data.findIndex( array => array.id === id);
console.log(index)
const newData = [...data]; // shallow cloning data
if (status) {
console.log('entrei disable')
newData[index].enable = false
console.log(newData);
return dispatch => {
httpPut(`${endPoint}/${id}/disable`)
.then(resp => {
return [
dispatch({
type: ActionType,
payload: newData
})
]
})
.catch(error => {
console.log(error)
})
}
}
else {
console.log('entrei enable')
newData[index].enable = true
return dispatch => {
httpPut(`${endPoint}/${id}/enable`)
.then(resp => {
return [
dispatch({
type: ActionType,
payload: newData
})
]
})
.catch(error => {
console.log(error)
})
}
}
}

Updating Data in Firebase using React

I am updating an object in firebase using React js.
I'm using this boilerplate as reference.
updateBookList: (id, data) => {
return firebaseDb.ref('NewBooks').child(id).update(data).then(() => {
return {};
}).catch(error => {
return {
errorCode: error.code,
errorMessage: error.message
}
});
},
The following updates the Books fine.
What I want to do is return the result instead of returning a blank {}. How can I return the result of what I updated?
This is how I fetch books:
fetchBooks: () => {
return new Promise((resolve, reject) => {
const bookSub = firebaseDb.ref('NewBooks').on("value", books => {
resolve(books.val());
}, error => {
reject(error);
})
})
},
If you want to return the value, you need to retrieve it. You can do that using once and the value event, which returns a promise that resolves to a Firebase snapshot:
updateBookList: (id, data) => {
let ref = firebaseDb.ref('NewBooks');
return ref
.child(id)
.update(data)
.then(() => ref.once('value'))
.then(snapshot => snapshot.val())
.catch(error => ({
errorCode: error.code,
errorMessage: error.message
}));
}
Also, you could simplify your fetchBooks by using once there, too:
fetchBooks: () => {
return firebaseDb.ref('NewBooks')
.once("value")
.then(snapshot => snapshot.val());
}
once returns a promise, so you don't have to create your own and you won't have a dangling event listener. The call to on in your implementation of fetchBooks will see a listener added with each call and multiple calls to resolve will be attempted if the database changes.

Resources