How to Fix the React JS useState Error When State is Passed as a Prop

When developing an ecommerce webshop with React JS and Commerce.js, I encountered a perplexing error related to useState. I initially thought the issue might have been something obscure, but after debugging and testing, I pinpointed the root cause and came up with a solution. If you’re facing a similar problem, here’s a detailed explanation of the issue and how I solved it.

Original Code

App.js

{ useState, useEffect } from 'react';
import Navbar from './Navbar';
import Cart from './Cart';
import { commerce } from './lib/commerce';

const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});

const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};

const fetchCart = async () => {
const cartData = await commerce.cart.retrieve();
setCart(cartData);
};

const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
};

useEffect(() => {
fetchProducts();
fetchCart();
}, []);

return (
<>
<Navbar totalItems={cart.total_items} />
<Cart cartItems={cart} />
</>
);
};

Error Explanation

Error:

Uncaught Error: Objects are not valid as a React child (found: object with keys {raw, formatted, formatted_with_symbol, formatted_with_code})

Root Cause:

This error occurs because you are trying to render an object in JSX. React cannot interpret an object directly as a string or number, which causes the error. The issue arises when trying to render properties of the cartItems object, such as cartItems.subtotal, which is itself an object with keys like raw, formatted, formatted_with_symbol, and formatted_with_code.

In the Cart.js file, you may have written this:

<Typography variant="h4">
Subtotal: {cartItems.subtotal}
</Typography>

But cartItems.subtotal is an object, and React doesn’t know how to display it as a string. If you attempt to render an object directly, it throws an error.

The cartItems.subtotal object might look something like this:

{
raw: 35.0,
formatted: "35.00",
formatted_with_symbol: "$35.00",
formatted_with_code: "USD 35.00"
}

React can’t display the entire object, but it can display the formatted_with_symbol string if you tell it to.

How to Fix It

Solution:

I noticed that I was already passing the correct property in this line:

cartItems.subtotal.formatted_with_symbol

However, I needed to make sure that cartItems.subtotal exists before trying to render the value. Here’s the improved code:

Fix Code for Cart.js:

const Cart = ({ cartItems }) => {
const classes = useStyles();

if (!cartItems.line_items) {
return <Typography variant="h4">Loading...</Typography>;
}

const EmptyCart = () => (
<Typography variant="subtitle1">
You have no items in your cart. Start adding some :)
</Typography>
);

const FilledCart = () => (
<>
<Grid container spacing={3}>
{cartItems.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<CartItem items={item} />
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: {cartItems?.subtotal?.formatted_with_symbol || '$0.00'}
</Typography>
<div>
<Button className={classes.emptyButton} size="large" variant="contained" color="secondary">
Empty Cart
</Button>
<Button className={classes.checkoutButton} size="large" variant="contained" color="primary">
Checkout
</Button>
</div>
</div>
</>
);

return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">
Your Shopping Cart
</Typography>
{!cartItems.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
);
};

Why This Works:

  • We are using optional chaining (?.) to safely access properties like cartItems.subtotal.formatted_with_symbol. This ensures that if cartItems.subtotal is null or undefined, React won’t throw an error.
  • I also used the fallback '$0.00' for the subtotal, so it doesn’t break the layout if the data isn’t available yet.

Practice Functionalities to Add

Now that we’ve fixed the error, here are some additional features you can practice implementing to enhance the functionality of your ecommerce site.

Empty Cart Function

Add a function to allow users to empty their cart:

handleEmptyCart = async () => {
const response = await commerce.cart.empty();
setCart(response.cart);
};

Add this to your button:

<Button onClick={handleEmptyCart}>Empty Cart</Button>

Remove Single Item

You might want to remove a single item from the cart. Here’s how you can do it:

handleRemoveFromCart = async (productId) => {
const response = await commerce.cart.remove(productId);
setCart(response.cart);
};

Pass handleRemoveFromCart to each CartItem and trigger it on delete.

Update Item Quantity

Allow users to change the quantity of an item in the cart:

handleUpdateCartQty = async (productId, quantity) => {
const response = await commerce.cart.update(productId, { quantity });
setCart(response.cart);
};

Conditional Rendering for Navbar

Ensure that your Navbar component handles undefined values properly:

<Navbar totalItems={cart?.total_items || 0} />

Better State Defaults

Instead of:

[cart, setCart] = useState([]);

Use:

[cart, setCart] = useState({ line_items: [], subtotal: {} });

This prevents errors when trying to access properties of cart before it has been initialized.

Whenever dealing with nested async data, always use optional chaining (cartItems?.subtotal?.formatted_with_symbol) to prevent errors when properties are still loading. This method ensures that your app won’t break if certain data is missing or not yet available.

Final Thought

When you’re working with asynchronous data and using React useState, you have to be extra cautious about passing complex objects as props. By using optional chaining and ensuring that your data is available before rendering, you can create a more stable and error-free React app. This small but effective pattern will improve your app’s robustness and ensure smoother user experiences.

Related blog posts