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
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 initializingproducts
as an empty object and then looping through thequerySnapshot
to populate it asynchronously usingforEach
andgetDocs
.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 accessproductData.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:By using
Promise.all
withmap
to create an array of promises (pricePromises
), we can ensure that all the asynchronous operations for fetching prices complete before setting the state ofproducts
. Then, within theproductsData
array, eachproductData
object contains the correspondingprices
property.With this updated code,
productData.prices
should no longer be undefined when accessed in the rendering part of your component.Based on the code you provided ππ»ββοΈ I think, the way the
products
object show up In the console it like π:π¨π»βπ« Here the key is presented as an actual string,π€Έπ»ββοΈ in another way the
products
obj is formatted in the console like aJSON
data, so when you want to access it likeproducts.property
it will not work unless you parsed from{"key":"value"}
to{key:"value"}
using:it will convert it to an actual javascript object with accessible properties β¨ππ».
or you can use ππ»ββοΈ:
it should work, hopefully, π