Home > Back-end >  ReactJS Object mapping, Undefined property, even though it's there
ReactJS Object mapping, Undefined property, even though it's there

Time:10-10

I am trying to create a Stripe implementation plan with Cloud Firestore from Firebase and ReactJs and I am facing an issue that I can`t figure out. I have this object that I want to access its priceId

{
    "tax_code": null,
    "images": [],
    "name": "Premium Plan",
    "description": "4k   HDR   5 Family Members",
    "active": true,
    "role": null,
    "metadata": {},
    "prices": {
        "priceId": "price_1LpD2rHO1lI6EMwl58lLNxRo",
        "product": "prod_MYJgCtqv3JyCPn",
        "metadata": {},
        "unit_amount": 15000,
        "transform_quantity": null,
        "interval_count": 1,
        "tiers_mode": null,
        "description": "150",
        "trial_period_days": null,
        "type": "recurring",
        "active": true,
        "tiers": null,
        "interval": "month",
        "tax_behavior": "unspecified",
        "currency": "dkk",
        "recurring": {
            "trial_period_days": null,
            "usage_type": "licensed",
            "interval_count": 1,
            "interval": "month",
            "aggregate_usage": null
        },
        "billing_scheme": "per_unit"
    }
}

That is how my code looks. First I am fetching the data from CloudFirestore and I create an object that has the price id within each product. Than in the return I am trying to map this object, and to get the priceId. For some reason that does not work. If I console.log(productData?.prices?.priceId) it shows me the priceId, but then it says undefined like you see in the picture below: undefined property.

I need the priceId so I make the loadCheckout work. Thank you for your help.


function PlanScreen() {
  const [products, setProducts] = useState([]);
  const user = useSelector(selectUser);

  //   console.log(products)
  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const q = query(collection(db, "products"), where("active", "==", true));
    const querySnapshot = await getDocs(q);
    const products = {};
    const data = querySnapshot.forEach(async (productDoc) => {
      products[productDoc.id] = productDoc.data();
      const subQuery = query(
        collection(db, `products/${productDoc.id}/prices`)
      );
      const subQDetails = await getDocs(subQuery);
      subQDetails.docs.map((price) => {
        products[productDoc.id].prices = {
          priceId: price.id,
          ...price.data(),
        };
      });
    });
    setProducts(products);
  };

  const loadCheckout = async (priceId) => {
    const firstQ = collection(db, `customers/${user.uid}/checkout_sessions`);
    const secondQ = query(
      addDoc(firstQ, {
        price: priceId,
        success_url: window.location.origin,
        cancel_url: window.location.origin,
      })
    );

    onSnapshot(secondQ, async (snap) => {
      const { error, sessionId } = snap.data();

      if (error) {
        alert(`An error occured: ${error.message}`);
        console.log(error.message);
      }
      if (sessionId) {
        const stripe = await loadStripe(
          "pk_test_51LpCKmHO1lI6EMwlLvPQQSBZ2A6JTa6PmqXwLcYstnaf04qaG7CQYGW3DYWmrIL4QjzXfKQnDQaIeWSjrHyLx0fY000otcrJ7t"
        );
        stripe.redirectToCheckout({ sessionId });
      }
    });
  };

  return (
    <div className="planScreen">
      {Object.entries(products).map(([productId, productData]) => {
        console.log(productData);
        return (
          <div className="planScreen_plan" key={productId}>
            <div className="planScreen_info">
              <h5>{productData.name}</h5>
              <h6>{productData.description}</h6>
              <h6>{productData?.prices?.priceId}</h6>
            </div>

            <button onClick={() => loadCheckout(productData.prices.priceId)}>
              Subscribe
            </button>
          </div>
        );
      })}
    </div>
  );
}

CodePudding user response:

Your call to setProducts(products) runs before any of the await getDocs(subQuery) has completed, because forEach is not an asynchronous operator.

You can either move the call to setProducts(products) into the callback (simplest), or use a for of loop (which does allow asynchronous callbacks in its body) to loop over the products.

Move the call to setProducts into the callback

const fetchData = async () => {
  const q = query(collection(db, "products"), where("active", "==", true));
  const querySnapshot = await getDocs(q);
  const products = {};
  const data = querySnapshot.forEach(async (productDoc) => {
    products[productDoc.id] = productDoc.data();
    const subQuery = query(
      collection(db, `products/${productDoc.id}/prices`)
    );
    const subQDetails = await getDocs(subQuery);
    subQDetails.docs.map((price) => {
      products[productDoc.id].prices = {
        priceId: price.id,
        ...price.data(),
      };
    });
    setProducts(products); //            
  • Related