skip to Main Content

I’m new to javascript and I’m working on a project where I need to access the prices property within a JavaScript object. This object is the product object from the stripe integration. When I log productData.prices, it shows undefined even though the productData object has the prices property. I’m not sure why this is happening. Can someone help me understand what might be causing this issue?

Here is the javascript code

export default function Subscription() {
  const [loading, setLoading] = useState(false);
  const [products, setProducts] = useState([]);
  const { currentUser } = useAuth();
  const [stripe, setStripe] = useState(null);

  useEffect(() => {
    const initializeStripe = async () => {
      const stripe = await loadStripe(
        process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY
      );
      setStripe(stripe);
    };

    initializeStripe();

    const q = query(collection(db, "products"), where("active", "==", true));
    getDocs(q).then((querySnapshot) => {
      const products = {};
      querySnapshot.forEach(async (doc) => {
        products[doc.id] = doc.data();
        const priceSnapshot = await getDocs(
          collection(db, "products", doc.id, "prices")
        );
        priceSnapshot.forEach((price) => {
          products[doc.id].prices = {
            priceId: price.id,
            priceData: price.data(),
          };
        });
      });
      setProducts(products);
    });
  }, []);

  async function loadCheckOut(priceId) {
    setLoading(true);
    const usersRef = doc(collection(db, "users"), currentUser.uid);
    const checkoutSessionRef = collection(usersRef, "checkout_sessions");

    const docRef = await addDoc(checkoutSessionRef, {
      price: priceId,
      trial_from_plan: false,
      success_url: window.location.origin,
      cancel_url: window.location.origin,
    });

    onSnapshot(docRef, (snap) => {
      const { error, sessionId } = snap.data();
      if (error) {
        alert(`An error occurred: ${error.message}`);
      }

      if (sessionId && stripe) {
        stripe.redirectToCheckout({ sessionId });
      }
    });
  }

  return (
    <>
      <Container className="mt-4 mb-4">
        <h1 className="text-center mt-4">Choose Your Plan</h1>
        <Row className="justify-content-center mt-4">
          {Object.entries(products).map(([productId, productData]) => {
            console.log(productData);
            console.log(productData.prices);

            return (
              <Col md={4} key={productId}>
                <Card>
                  <Card.Header className="text-center">
                    <h5>{productData.name}</h5>
                    <h5>$20.00 / month</h5>
                  </Card.Header>
                  <Card.Body>
                    <h6>{productData.description}</h6>
                    <Button
                      onClick={() => loadCheckOut(productData?.prices?.priceId)}
                      variant="primary"
                      block
                      disabled={loading}
                    >
                      {loading ? (
                        <>
                          <Spinner
                            animation="border"
                            size="sm"
                            className="mr-2"
                          />
                          Loading...
                        </>
                      ) : (
                        "Subscribe"
                      )}
                    </Button>
                  </Card.Body>
                </Card>
              </Col>
            );
          })}
        </Row>
      </Container>
    </>
  );
}

The console.log(productData); logs the following object

{
    "images": [],
    "description": "Access to dashboard",
    "tax_code": null,
    "active": true,
    "role": "premium",
    "name": "Premium",
    "metadata": {
        "firebaseRole": "premium"
    },
    "prices": {
        "priceId": "price_1N7SdbJHqW6OBlJ5itJgsGON",
        "priceData": {
            "billing_scheme": "per_unit",
            "interval": "month",
            "unit_amount": 2000,
            "currency": "usd",
            "product": "prod_NtF7pDBsqj5psh",
            "transform_quantity": null,
            "type": "recurring",
            "active": true,
            "metadata": {},
            "tax_behavior": "unspecified",
            "tiers": null,
            "tiers_mode": null,
            "interval_count": 1,
            "recurring": {
                "interval": "month",
                "aggregate_usage": null,
                "usage_type": "licensed",
                "interval_count": 1,
                "trial_period_days": null
            },
            "description": null,
            "trial_period_days": null
        }
    }
}

The console.log(productData.prices); logs undefined. Can someone help me understand what’s happening here and how i can resolve it? Thank you.

2

Answers


  1. Based on the code you provided, it seems that the issue lies in the way you’re setting the products state. In your code, you’re initializing products as an empty object and then looping through the querySnapshot to populate it asynchronously using forEach and getDocs.

    Since the population of products is done asynchronously, there’s a possibility that the state is being set before the asynchronous operations complete. As a result, when you try to access productData.prices in the rendering part of your code, it may still be undefined.

    To fix this issue, you can modify your code to use Promise.all to wait for all the asynchronous operations to complete before setting the state. Here’s an updated version of your code that implements this approach:

    useEffect(() => {
      const initializeStripe = async () => {
        const stripe = await loadStripe(
          process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY
        );
        setStripe(stripe);
      };
    
      initializeStripe();
    
      const fetchData = async () => {
        const q = query(collection(db, "products"), where("active", "==", true));
        const querySnapshot = await getDocs(q);
    
        const products = {};
    
        const pricePromises = querySnapshot.docs.map(async (doc) => {
          const priceSnapshot = await getDocs(
            collection(db, "products", doc.id, "prices")
          );
          const prices = {};
          priceSnapshot.forEach((price) => {
            prices[price.id] = price.data();
          });
          return { id: doc.id, data: doc.data(), prices };
        });
    
        const productsData = await Promise.all(pricePromises);
    
        productsData.forEach((productData) => {
          products[productData.id] = { ...productData.data, prices: productData.prices };
        });
    
        setProducts(products);
      };
    
      fetchData();
    }, []);
    

    By using Promise.all with map to create an array of promises (pricePromises), we can ensure that all the asynchronous operations for fetching prices complete before setting the state of products. Then, within the productsData array, each productData object contains the corresponding prices property.

    With this updated code, productData.prices should no longer be undefined when accessed in the rendering part of your component.

    Login or Signup to reply.
  2. Based on the code you provided πŸ’πŸ»β€β™‚οΈ I think, the way the products object show up In the console it like πŸ‘€:

    {"key":"value"}
    

    πŸ‘¨πŸ»β€πŸ« Here the key is presented as an actual string,πŸ€ΈπŸ»β€β™‚οΈ in another way the products obj is formatted in the console like a JSON data, so when you want to access it like products.property it will not work unless you parsed from {"key":"value"} to {key:"value"} using:

    JSON.parse(products);
    

    it will convert it to an actual javascript object with accessible properties βœ¨πŸ‘ŒπŸ».
    or you can use πŸ’πŸ»β€β™‚οΈ:

    products["property"]
    

    it should work, hopefully, πŸ‘€

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search