0

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 - checks if voucher is in ClaimedVoucher table - returns true/false

export default (voucher, user) => {
  const [result, setResult] = useState(false);

  async function isClaimed() {
    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("This is result: ", result);
    return result;
  }

  useEffect(() => {
    isClaimed().then((result) => setResult(result));
  }, []);

  return result;
};

The query works fine and I can get the values I want from it.

In my main function, I first get the list of vouchers that I want to check that they're claimed and store them in a list. Then I use a map function to return the ones that haven't been claimed. This is where my question arises, how do I return the jsx for the vouchers that haven't been claimed based off the result of the firebase query?

var user = Firebase.auth().currentUser;
    var uid = user.uid;
    const [getVouchers, voucherList, errorMessage] = getVouchersAPI(ID); //List of vouchers to be checked
  
    return(
<ScrollView>
    {voucherList.map((item, index) => {
              var voucher = item;

              var isVoucherClaimed = isClaimedAPI(voucher.voucherID, uid);
              if (
                isVoucherClaimed === false
              ) {
                  return <Text>{item.name}<Text>
                }
    })}
  </ScrollView>
 );

Currently I am getting the error message "rendered more hooks than the previous rerender" and to be honest I am completely stuck with this. I have tried many different avenues to try and solve this and I can't seem to get it working - I know to use async / await / useEffect but still can't seem to get it working. If anyone has an answer it would be greatly appreciated.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
kiermcguirk
  • 3
  • 1
  • 3
  • What does `isClaimedAPI` do? Can is that another async operation? If so, the same logic of [my answer](https://stackoverflow.com/a/63206006/209103) to your previous question applies: there should be no asynchronous calls inside the render method. – Frank van Puffelen Aug 02 '20 at 14:05
  • Hi thanks for your response. I believe isClaimed is the async function that returns a promise, I am then using the .then() on the use effect to store the result and then return it. I am using functional components not class/render(). But if it still applies that I cannot use the async method in the map function then how can I achieve my goal? Many thanks – kiermcguirk Aug 02 '20 at 14:16
  • It is always the same: 1) start the asynchronous operation in `componentDidMount`/`useEffect`, 2) set the data to the state when it's available, 3) render the data from the state. – Frank van Puffelen Aug 02 '20 at 15:12
  • I still don't understand what this line does though: `var isVoucherClaimed = isClaimedAPI(voucher.voucherID, uid);`. Instead of describing it in words, can you show the code for `isClaimedAPI`? – Frank van Puffelen Aug 02 '20 at 15:12
  • I probably should have laid it out more clearly in the initial code snippet, but the first snippet is isClaimedAPI.js and in isClaimedAPI.js there is the async function isClaimed. `var isVoucherClaimed = isClaimedAPI(voucher.voucherID, uid);` is me calling the code snippet at the top. I am confused because I've tried implementing using the three steps you've provided: the async function is in the useEffect, I set the state of "result" using the useState hook and then I return it to the main function. I'm definitely misunderstanding something somewhere. – kiermcguirk Aug 02 '20 at 16:44
  • The way I was trying to construct it, was that `var isVoucherClaimed = isClaimedAPI(voucher.voucherID, uid);` calls **isClaimedAPI**, which then calls the async function **isClaimed**, which returns either true/false, and then the `isClaimed().then((result) => setResult(result));` sets the state of result which is then returned by **isClaimedAPI* back to the main function. So var isVoucherClaimed is either true/false for whether or not the voucher was claimed or not. – kiermcguirk Aug 02 '20 at 16:51
  • You can't call asynchronous functions from the render method. As long as you keep trying that, you will run into the same problems as before - and this question is the same as your previous one. You'll have to do as I said before: 1) start the asynchronous operation in componentDidMount/useEffect, 2) set the data to the state when it's available, 3) render the data from the state. – Frank van Puffelen Aug 02 '20 at 16:55
  • I added some more example to my answer on your previous question, which all boil down to the same 3 steps I've given there and above. https://stackoverflow.com/questions/63205729/how-to-use-useeffect-inside-map-function/63206006#63206006 – Frank van Puffelen Aug 02 '20 at 17:01
  • I see what you mean. I see now that I cannot use the isClaimedAPI function in the return statement of my main function. However, now I am completely uncertain at how I am supposed to implement this functionality - I need to call the function for each voucher in the list to check each one individually. How else would I be able to conditionally display them if I can't use it in the `voucherList.map(` ? – kiermcguirk Aug 02 '20 at 17:45
  • Using `voucherList.map` in the rendering is not a problem, but you can't make any async calls in there. So you'll need to load all `ClaimedVoucher` documents in a `componentDidMount`/`useEffect`, store them in the state, and *then* you can use `voucherList.map` in the render method to look up the document *synchronously* in the state. – Frank van Puffelen Aug 02 '20 at 19:42

0 Answers0