I have two tables in Firebase: Vouchers & ClaimedVouchers. I am trying to display the vouchers that do not appear in the ClaimedVouchers table. So, I have a query that gets all of the vouchers, then another that checks if they're claimed and if it's claimed or not the function should return either true or false:
This is isClaimedAPI.js
export default () => {
var result;
async function isClaimed(voucher, user) {
var db = Firebase.firestore();
console.log("voucher");
await db
.collection("ClaimedVoucher")
.where("voucherID", "==", voucher)
.where("userID", "==", user)
.get()
.then(function (querySnapshot) {
if (querySnapshot.empty === false) {
result = true;
} else {
result = false;
}
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
console.log(result, "this is result");
return result;
//Call when component is rendered
useEffect(() => {
isClaimed().then((result) => setResult(result));
}, []);
return [isClaimed];
And then in my main function:
var user = Firebase.auth().currentUser;
var uid = user.uid;
const [getVouchers, voucherList, errorMessage] = getVouchersAPI(ID); //List of vouchers to be checked
const [isClaimed] = isClaimedAPI();
return(
<ScrollView>
{voucherList.map((item, index) => {
var voucher = item;
var isVoucherClaimed = isClaimed(voucher.voucherID, uid);
console.log("this is result, ", isVoucherClaimed);
if (
isVoucherClaimed === false
) {
return <Text>{item.name}<Text>
}
})}
</ScrollView>
);
Now nothing happens and I receive the following warning: [Unhandled promise rejection: FirebaseError: Function Query.where() requires a valid third argument, but it was undefined.] but I think this is unrelated to the issue.
Your isClaimed is an async function, meaning that it returns a promise - or a delayed result. If you want to wait for the result when calling isClaimed, you'll need to use await:
await isClaimed(voucher.voucherID, uid);
console.log(result);
This most likely isn't possible in a render method though, which is why (as Asutosh commented) you'll have to store the result in the state, and then use the state in your render method.
So the setup you need is:
Start the loading of all your data in componentDidMount or with useEffect.
When the data is loaded, put it in the state with setState or a state hook.
Use the data in your render method.
For a few examples of this, see:
Firebase switch header option with onAuthStateChanged
React + Firestore : Return a variable from a query
How to render async data in react + firestore?
Just to highlight how you can use isClaimed as hoook and set state calling a async function inside it. Later use the above hook in a react component. Please follow below sanbox.
https://codesandbox.io/s/confident-cray-4g2md?file=/src/App.js
Related
I have following hook which handle all query:
const useUsers = (userId: number) => {
const { data: user } = useQuery([QUERY.USERS, userId], () => getUser(userId))
return {
user
}
}
export const getUser = async (userId) => {
try {
const { data } = await fetch(`path/path/${userId}`)
return data;
} catch (e) {
throw new Error('Something went wrong');
}
}
Now in my main component I invoke this hook:
const [userId, setUserId] = useState(null)
const { user } = useUsers(userId);
Also I have a list of user. When I click on user I am setting userId by (setUserId). OnClick Function looks like this:
const handleUserDetails = (userId) => {
setUserId(userId);
console.log(user) // undefined
}
My problem is when I click I got undefined.
Only the second click returns the correct data
The user object is undefined because when you change the userId state, by calling setUserId(userId), a new fetch request is being triggered. Immediately, without waiting, you are calling console.log(user). And because the fetch request hasn't completed yet, the user object is still undefined.
When you click the button again, if the userId is the same as it was on the first click, the user object won't be (probably) undefined because the fetch request has completed and that's the cached value of the user object being displayed.
In order to be able to access the user object immediately after the request completes, you can use the useEffect hook and put user in its dependency list:
useEffect(() => {
// console.log only if user is defined
if (user) {
console.log("User won't be undefined here ", user);
}
}, [user]);
Here's a Codesandbox sample of what I exactly mean
I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>
I want to insert asynchronous data from Firestore into React elements. I have a couple of functions that handle the flow.
getEvents() is an asynchronous function that returns an array of objects that is my data from Firestore.
const getEvents = async() => {
try {
const data = query(collection(db, id));
const events = [];
const snapshot = await getDocs(data);
snapshot.forEach((doc) => events.push(doc.data()));
return events;
} catch(error) {
console.error(error);
}
}
This function is referenced in receiveEvents() where I take the returned data and put it into a global array in order to use it in the DOM.
let userEvents = [];
const receiveEvents = () => {
getEvents()
.then(result => userEvents = result)
.catch(error => console.error(error));
This function is used in displayEvents() to paste the returned data into the desired element. The function is called upon a button click.
const displayEvents = () => {
try {
const btnContainer = document.querySelector(".btn-container");
ReactDOM.render(<AppNavigation />, btnContainer);
receiveEvents().then(() => { return userEvents });
} catch(error) {
console.error(error);
}
}
I get an error index.js:1 TypeError: Cannot read properties of undefined (reading 'then') # displayEvents.
These functions use the logged user ID to access the right directory in the database. I retrieve the ID in a given function declared at the top of the file.
let id = null;
const getUserId = () => {
try {
console.log("getUserId()");
return auth.currentUser.uid;
} catch(error) {
console.error(error);
}
}
The <AppNavigation/> component returns a div, namely:
<div className="arrow-navigation-container">
<button className="arrow-btn"><span className="icon arrow">arrow_back</span></button>
<button className="arrow-btn"><span className="icon arrow">arrow_forward</span></button>
</div>
What can I do to get the asynchronous data into the userEvents array, so I can show it to the user upon request?
I would use hooks for this personally:
useState for storing the data
useEffect for triggering the fetching of the data
useContext for providing the data for whatever component that needs the data.
Examples how to use those hooks: https://reactjs.org/docs/hooks-reference.html#usestate
The useState would go into the same component as the useContext Provider, usually in example apps it's in component. The component which needs the data needs to be a child of the Context.Provider, but it doesn't need to be a direct child.
useEffect goes also to the same component, and with the dependencies you can decide if you want to fetch the data only once or for example when that ID changes.
I'm developing the front-end for my spring boot application. I set up an initial call wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
The data returned isn't comprehensive, and needs further call to retrieve other piece of information, for example this initial call return an employee id, but if I want to retrieve his name and display it I need a sub-sequential call, and here I'm experiencing tons of issues:
const getEmployeeName = async id => {
try {
const name = await fetchContext.authAxios.get(
'/employeeName/' + id
);
console.log((name["data"])); // <= Correctly display the name
return name["data"]; // return an [Object promise],
} catch (err) {
console.log(err);
}
};
I tried to wrap the return call inside a Promise.resolve() function, but didn't solve the problem. Upon reading to similar questions here on stackoverflow, most of the answers suggested to create a callback function or use the await keyword (as I've done), but unfortunately didn't solve the issue. I admit that this may not be the most elegant way to do it, as I'm still learning JS/React I'm open to suggestions on how to improve the api calls.
var output = Object.values(data).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Async functions always return promises. Any code that needs to interact with the value needs to either call .then on the promise, or be in an async function and await the promise.
In your case, you should just need to move your code into the existing useEffect, and setState when you're done. I'm assuming that the employeeID is part of the data returned by the first fetch:
const [name, setName] = useState('');
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
"/myapi/" + auth.authState.id
);
setData(data);
const name = await fetchContext.authAxios.get(
'/employeeName/' + data.employeeID
);
setName(name.data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
// ...
var output = Object.values(appointmentsData).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Note that the above code will do a rerender once it has the data (but no name), and another later when you have the name. If you want to wait until both fetches are complete, simply move the setData(data) down next to the setName
I have two tables in Firebase: Vouchers & ClaimedVouchers. I am trying to display the vouchers that do not appear in the ClaimedVouchers table. So, I have a query that gets all of the vouchers, then another that checks if they're claimed and if it's claimed or not the function should return either true or false:
This is isClaimedAPI.js
export default () => {
var result;
async function isClaimed(voucher, user) {
var db = Firebase.firestore();
console.log("voucher");
await db
.collection("ClaimedVoucher")
.where("voucherID", "==", voucher)
.where("userID", "==", user)
.get()
.then(function (querySnapshot) {
if (querySnapshot.empty === false) {
result = true;
} else {
result = false;
}
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
console.log(result, "this is result");
return result;
//Call when component is rendered
useEffect(() => {
isClaimed().then((result) => setResult(result));
}, []);
return [isClaimed];
And then in my main function:
var user = Firebase.auth().currentUser;
var uid = user.uid;
const [getVouchers, voucherList, errorMessage] = getVouchersAPI(ID); //List of vouchers to be checked
const [isClaimed] = isClaimedAPI();
return(
<ScrollView>
{voucherList.map((item, index) => {
var voucher = item;
var isVoucherClaimed = isClaimed(voucher.voucherID, uid);
console.log("this is result, ", isVoucherClaimed);
if (
isVoucherClaimed === false
) {
return <Text>{item.name}<Text>
}
})}
</ScrollView>
);
Now nothing happens and I receive the following warning: [Unhandled promise rejection: FirebaseError: Function Query.where() requires a valid third argument, but it was undefined.] but I think this is unrelated to the issue.
Your isClaimed is an async function, meaning that it returns a promise - or a delayed result. If you want to wait for the result when calling isClaimed, you'll need to use await:
await isClaimed(voucher.voucherID, uid);
console.log(result);
This most likely isn't possible in a render method though, which is why (as Asutosh commented) you'll have to store the result in the state, and then use the state in your render method.
So the setup you need is:
Start the loading of all your data in componentDidMount or with useEffect.
When the data is loaded, put it in the state with setState or a state hook.
Use the data in your render method.
For a few examples of this, see:
Firebase switch header option with onAuthStateChanged
React + Firestore : Return a variable from a query
How to render async data in react + firestore?
Just to highlight how you can use isClaimed as hoook and set state calling a async function inside it. Later use the above hook in a react component. Please follow below sanbox.
https://codesandbox.io/s/confident-cray-4g2md?file=/src/App.js