Cannot find the error in React application - javascript

I have been writing an application (e-Commerce, as a project, following a tutorial) with React. I am getting an error of 'TypeError: Cannot read property 'length' of undefined' when referring to a cart object. Here's some background context. I am generating the cart object using a useState hook near the top of my App.js component:
const [cart, setCart] = useState({});
A little further down in App.js a console.log statement executes without errors suggesting that the cart object exists:
console.log(cart);
However, when I try to pass the cart object to a Cart component in the render section of App.js the aforementioned error (e.g. 'TypeError: Cannot read property 'length' of undefined') is generated. Why is this happening and how do I fix it?
Here is the code of App.js
import React, { useState, useEffect } from 'react'
import { commerce } from './lib/commerce';
import { Products, Navbar, Cart } from './components';
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
}
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
}
const handleAddToCart = async (productID, quantity) => {
const item = await commerce.cart.add(productID, quantity);
setCart(item.cart);
}
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
console.log(cart);
return (
<div>
<Navbar totalItems={cart.total_items} />
{/* <Products products={products} onAddToCart={handleAddToCart} /> */}
<Cart cart={cart} />
</div>
)
}
export default App
And here is the code of the component (Cart) that I am passing the cart object into:
import React from 'react'
import { Container, Typography, Button, Grid } from "#material-ui/core";
import useStyles from './styles';
const Cart = ({ cart }) => {
const isEmpty = !cart.line_items.length;
const classes = useStyles();
const EmptyCart = () => {
<Typography variant="subtitle1">
You have no items your shopping cart..
</Typography>
}
const FilledCart = () => {
<>
<Grid container spacing={3}>
{
cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<div>{item.name}</div>
</Grid>
))
}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: { cart.subtotal.formatted_with_symbol }
</Typography>
<div>
<Button className={classes.emptyButton} size="large" variant="contained" type="button" color="secondary">Empty Cart</Button>
<Button className={classes.checkoutButton} size="large" variant="contained" type="button" color="primary">Checkout</Button>
</div>
</div>
</>
}
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">Your shopping cart</Typography>
{
isEmpty ? <EmptyCart /> : <FilledCart />
}
</Container>
)
}
export default Cart

Issue
The issue with your code is that the initial state doesn't match what you access on the initial render.
In App component the cart state is just an empty object.
const [cart, setCart] = useState({});
cart is passed as a prop to Cart component and the code assumes cart.line_items is defined in order to access a length property or map function. cart.line_items is OFC undefined so attempting to access the length property (or map) throws the TypeError: Cannot read property 'XYZ' of undefined
const isEmpty = !cart.line_items.length;
and
cart.line_items.map(.....
but when I console.log it out in App.js, it actually does print out
the necessary information.
The console.log(cart); is in the function body of the component so it's incorrectly logging the cart state as an unintentional side-effect, it should be logged from a useEffect hook so you see the value per render cycle. The other issue here is that you aren't accessing any nested properties so this will never throw an error. I'd be willing to bet that with this code you have at least 1 or 2 logs entires that are just empty objects ({}) and then you see some logs with populated nested properties.
Example possible log output:
{}
{}
{ line_items: [.....], subtotal: ..... }
{ line_items: [.....], subtotal: ..... }
Solutions
Regarding the state logging, you should use an useEffect hook with a dependency on the state value you are logging. This will log the cart state on the initial render and later only when the cart state value is updated.
useEffect(() => {
console.log(cart);
}, [cart]);
For the error, you've several options to help guard against errors when accessing your cart state.
Provide valid initial state that matches what is accessed during the render cycle, add line_items: [] to the initial cart state such that cart.line_items will now exist and have a length property.
const [cart, setCart] = useState({ line_items: [] });
Use a guard clause or Optional chaining on the passed cart prop.
const isEmpty = !(cart.line_items && cart.line_items.length);
or
const isEmpty = !cart.line_items?.length);
and
cart.line_items && cart.line_items.map(.....
or
cart.line_items?.map(.....
May as well guard the subtotal sub-state as well in the case that cart.subtotal isn't defined.
<Typography variant="h4">
Subtotal: { cart.subtotal?.formatted_with_symbol }
</Typography>

There is another way to approach the error (it solved my problem).
We only need to add this line:
if (!cart.line_items) return "Loading...";
and then remove the variable from the top and add it inside of the if statement:
{!cart.line_items.length ? <EmptyCart /> : <FilledCart />}
as sometimes we don't need to create a variable if the content is meaningful enough on its own.
The code:
import React, { useEffect } from "react";
import { Container, Typography, Button, Grid } from "#material-ui/core";
import useStyles from "./styles";
//
//
const Cart = ({ cart }) => {
// const isEmpty = !(cart.line_items && cart.line_items.length);
const classes = useStyles();
//
//So if the cart is EMPTY show the following:
const EmptyCart = () => {
<Typography variant="subtitle1">
You have no items in your shopping cart, start adding some!
</Typography>;
};
//
//
//So if the cart is FILLED show the following:
const FilledCart = () => (
<>
<Grid container spacing={3}>
{cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<div>{item.name}</div>
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: {cart.subtotal?.formatted_with_symbol}
</Typography>
<div>
<Button
className={classes.emptyButton}
size="large"
type="button"
variant="contained"
color="secondary"
>
Empty cart
</Button>
<Button
className={classes.checkoutButton}
size="large"
type="button"
variant="contained"
color="primary"
>
Checkout
</Button>
</div>
</div>
</>
);
//
if (!cart.line_items) return "Loading";
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">
Your shopping Cart
</Typography>
{!cart.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
);
};
export default Cart;

Related

react.js and commerce.js problem, cart value totalItems isnt working as it should

Im having problem displaying the total amount of items in the cart. The cart itself loads without a problem and items are also added without a problem. But when I try to get the total amount of items in the cart totalItems = {cart.total_items} and pass it on to the navbar.jsx file to be displayed, I get many errors. The app.js file is as follows:
import React, { useState, useEffect } from 'react';
import { commerce } from './lib/commerce';
import { Products, Navbar } from './components';
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
}
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
console.log(cart);
return (
<div>
<Navbar totalItems={cart.total_items} />
<Products products={products} onAddToCart={handleAddToCart} />
</div>
)
};
export default App;
The main errors I get are: "Uncaught Error: Objects are not valid as a React child (found: object with keys {totalItems}). If you meant to render a collection of children, use an array instead." and "The above error occurred in the component:". Im very new to react.js so any help would be appreciated.
Navbar.jsx code is as follows:
import React from 'react';
import { AppBar, Toolbar, IconButton, Badge, MenuItem, Menu, Typography } from '#material-ui/core';
import { ShoppingCart } from '#material-ui/icons';
import logo from '../../assets/mainlogo.png';
import useStyles from './styles';
const navbar = ({ totalItems }) => {
const classes = useStyles;
return (
<>
<AppBar position="fixed" className={classes.appBar} color="inherit">
<Toolbar>
<Typography variant="h6" className={classes.title} color="inherit">
<img src={logo} alt="Commerce.js" height="25px" className={classes.image} />
Project
</Typography>
<div className={classes.grow} />
<div className={classes.button}>
<IconButton aria-label="Show cart items" color="inherit">
<Badge overlap="rectangular" badgeContent={totalItems} color="secondary">
<ShoppingCart/>
</Badge>
</IconButton>
</div>
</Toolbar>
</AppBar>
</>
)
}
export default navbar
i believe we are working on the same project from youtube ,i also encountered this problem.
u should try adding ? after the item then adding .cart
setMyCart(item?.cart)
or you can just ignore the .cart it will work just fine.
every-time you face this undefined error you should work around it with ? or ternary such as this example:
if (!myCart.line_items) return 'Loading...' return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3" gutterBottom>
Your Shopping Cart
</Typography>
{!myCart.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container> ) }

How to pass the search query to table filter between different JS files?

I have a datagrid table in which I'm getting my database data from an API call and I have written the table code in one file. I also have a search functionality where you can search for a particular record inside the table, but this search code is in another file. I am having difficulty of passing my state variable containing the search parameter from my search file to the table file. I have separated all my components in different pages since it'd be easier to structure them using a grid in my App.js. How do I get my search query to my table file next?
My search code:
export default function SearchInput() {
const [searchTerm, setSearchTerm] = React.useState('');
return (
<Grid item xs={3}>
<Box mt={1.6}
component="form"
sx={{
'& > :not(style)': { m: 1, width: '20ch', backgroundColor: "white", borderRadius: 1},
}}
noValidate
autoComplete="off"
>
<TextField
placeholder="Search Customer ID"
variant="outlined"
size="small"
sx={{input: {textAlign: "left"}}}
onChange={(event) => {
setSearchTerm(event.target.value);
console.log(searchTerm);
}}
/>
</Box>
</Grid>
);
}
My table code:
export default function DataTable() {
const [pageSize, setPageSize] = React.useState(10);
const [data, setData] = React.useState([]);
useEffect(async () => {
setData(await getData());
}, [])
return (
<div style={{ width: '100%' }}>
<DataGrid
rows={data}
columns={columns}
checkboxSelection={true}
autoHeight={true}
density='compact'
rowHeight='40'
headerHeight={80}
disableColumnMenu={true}
disableSelectionOnClick={true}
sx={datagridSx}
pageSize={pageSize}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
rowsPerPageOptions={[5, 10, 15]}
pagination
/>
</div>
);
}
App.js
function App() {
return (
<div className="App">
<Container maxWidth="false" disableGutters="true">
<Grid container spacing={0}>
<ABClogo />
<HHHlogo />
</Grid>
<Grid container spacing={0}>
<LeftButtonGroup />
<SearchInput />
<RightButtonGroup />
</Grid>
<Grid container spacing={0}>
<DataTable />
<TableFooter />
</Grid>
</Container>
</div>
);
}
Here is a minimal example using createContext(), and useReducer() to lift up state and share it between components, similar to what you are after, but as jsNoob says, there are multiple options. This is one I'm comfortable with.
The concept is explained here: https://reactjs.org/docs/context.html
Essentially you can create 'global' state at any point in your component tree and using Provider / Consumer components, share that state and functionality with child components.
//Main.js
import React, { createContext, useContext, useReducer } from 'react';
const MainContext = createContext();
export const useMainContext => {
return useContext(MainContext);
}
const mainReducer = (state, action) => {
switch(action.type){
case: 'SOMETHING':{
return({...state, something: action.data});
}
default:
return state;
}
}
export const Main = () => {
const [mainState, mainDispatch] = useReducer(mainReducer, {something: false});
const stateOfMain = { mainState, mainDispatch };
return(
<MainContext.Provider value={stateOfMain}>
<MainContext.Consumer>
{() => (
<div>
<Nothing />
<Whatever />
</div>
)}
</MainContext.Consumer>
</MainContext.Provider>
)
}
Then you can have the other components use either or both of the main state and dispatch.
//Nothing.js
import {mainContext} from './Main.js'
const Nothing = () => {
const { mainState, mainDispatch } = useMainContext();
return(
<button onClick={() => {mainDispatch({type: 'SOMETHING', data: !mainState.something})}}></button>
)
}
Clicking the button in the above file, should change the display of the below file
//Whatever.js
import {mainContext} from './Main.js'
const Whatever = () => {
const { mainState } = useMainContext();
return(
<div>{mainState.something}</div>
);
}

Passing data through props and then setting to state becomes undefined?

On page load I'm getting the weather data for an api and then displaying it on sidebar then when you click on a city it shows the the city's weather in more detail. So basically I've been passing the data around from parent to child with props. I need to fill the detailed component with some initial data so I'm trying to send the first object in the data array to the child component through props and set it to state but when I try to render it is undefined and I'm not sure why.
It actually seems to be coming back undefined a couple times before setting it but when I try to render it on the page {weather.data.temp} I get 'Cannot read property 'temp' of undefined'.
Parent:
const fetchCity = async (city) => {
const res = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`);
return {
description: res.data.weather[0].description,
icon: res.data.weather[0].icon,
temp: res.data.main.temp,
city: res.data.name,
country: res.data.sys.country,
id: res.data.id,
};
};
function App() {
const [data, setData] = useState([]);
const [activeWeather, setActiveWeather] = useState([]);
useEffect(() => {
const fetchCities = async () => {
const citiesData = await Promise.all(["Ottawa", "Toronto", "Vancouver", "California", "London"].map(fetchCity)).catch((err) => {
console.log(err);
});
setData((prevState) => prevState.concat(citiesData));
};
fetchCities();
}, []);
const handleClick = (event) => {
const weather = JSON.parse(event.target.dataset.value);
setActiveWeather(weather);
};
return (
<div className="App">
<Header />
<Container>
<Row>
<Col>
<WeatherPanel data={data} handleClick={handleClick} />
</Col>
<Col>
<ActiveWeather activeWeather={activeWeather} data={data[0]} />
</Col>
</Row>
</Container>
</div>
);
}
export default App;
Child
import React, { useEffect, useState } from "react";
import { Container, Card } from "react-bootstrap";
const ActiveWeather = (props) => {
const [weather, setWeather] = useState();
useEffect(() => {
setWeather(props.data);
}, [props]);
console.log(weather);
return (
<Container>
<Card>
<Card.Header> </Card.Header>
{weather.temp}
</Card>
</Container>
);
};
export default ActiveWeather;
Other child
const WeatherPanel = (props) => {
return (
<div>
<Container fluid>
<Card style={{ boxShadow: "0 0 10px 2px lightgrey" }}>
<Card.Header> Favorite Location</Card.Header>
<ListGroup variant="flush">
<ListGroup.Item>
{props.data.map((item) => (
<ListGroup.Item key={item.id} data-value={JSON.stringify(item)} onClick={props.handleClick}>
<img src={`http://openweathermap.org/img/wn/${item.icon}#2x.png`} alt="Weather Icon" />
{item.city + ", " + item.country}
</ListGroup.Item>
))}
</ListGroup.Item>
</ListGroup>
</Card>
</Container>
</div>
);
};
export default WeatherPanel;
Issue
Calling setState does not update the state variable immediately. Why?
Solution
Check if weather is defined before accessing it.
Option 1: Use weather?.temp
Option 2: Use an if statement: if (!weather)
I think the issue is with your useEffect in the ActiveWeather component.
Because you are passing just props as the dependancy of your useEffect, any prop change will trigger it. So in your example activeWeather is probably triggering it, and you are then setting the state of weather to undefined because at mount stage, there isn't any data to pass in.
If you limit the dependence to only props.data. Then that useEffect will only run if something gets passed though the data prop. You can even use an if statement in there to double check that there is actually some data.
useEffect(() => {
if(props.data){
setWeather(props.data);
}
}, [props.data]);
In your parent component you are also passing in data[0] as the data prop. On mount, data is an empty array so if you say data[0] it will be undefined. Maybe wrap the rendering of your ActiveWeather component in an if to check if there is any data
<Col>
{ data.length > 0 && <ActiveWeather activeWeather={activeWeather} data={data[0]} /> }
</Col>

cannot read property 'length' of undefined for shopping cart component

I'm building a shopping cart component and trying to format the cart layout. I just want to see the layout of the cart so I commented out the products component which should get me the cart layout alone, so I can see what it looks like and what's going on, however I keep getting the following error: "TypeError: Cannot read property 'length' of undefined" when I try to compile it in react.
My shopping cart component code:
import React from "react";
import { Container, Typography, Button, Grid } from "#material-ui/core";
import useStyles from "./styles";
const Cart = ({ cart }) => {
const isEmpty = !cart.line_items.length;
const classes = useStyles();
const EmptyCart = () => (
<Typography variant="subtitle1">No items selected</Typography>
);
const FilledCart = () => (
<>
<Grid container spacing={3}>
{cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<div>{item.name}</div>
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: {cart.subtotal.formatted_with_symbol}
</Typography>
<div>
<Button
className={classes.emptyButton}
size="large"
type="button"
variant="contained"
color="secondary"
>
Empty Cart
</Button>
<Button
className={classes.checkoutButton}
size="large"
type="button"
variant="contained"
color="primary"
>
Checkout
</Button>
</div>
</div>
</>
);
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">
Your Shopping Cart
</Typography>
{isEmpty ? <EmptyCart /> : <FilledCart />}
</Container>
);
};
export default Cart;
The error is specifically on the line:
const isEmpty = !cart.line_items.length;
Also my App JS code:
mport React, { useState, useEffect } from "react";
import { Products, Navbar, Cart } from "./components";
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 () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
console.log(cart);
return (
<div>
<Navbar totalItems={cart.total_items} />
{/*<Products products={products} onAddToCart={handleAddToCart} /> */}
<Cart cart={cart} />
</div>
);
};
export default App;
Any help is much appreciated.
Issue
There is no cart.line_items on the initial render since initial state is an empty object ({}).
const [cart, setCart] = useState({});
Solution
Provide valid initial state for the initial render so there's a truthy, defined cart.line_items object from which to have a length property, i.e. so !cart.line_items.length; can resolve to a value and not throw an error.
const [cart, setCart] = useState({ line_items: [] });

Conditional rendering shows both components

I have two components that I'm rendering based on the condition of a state, but I'm running into a problem where the wrong component is displayed a split second before the right component is displayed.
Fetching data async:
const [test, setTest] = useState();
const [loading, setLoading] = useState();
const [error, setError] = useState();
const fetchData = async () => {
console.log("running");
setLoading(true);
setError(false);
try {
const result = await axios(
"https://jsonplaceholder.typicode.com/posts/props.selectedId" // Is dynamic and changes on user click
);
setTest(result.data);
} catch (error) {
if (error.response.status == 404) {
setError(error);
setTest(null);
}
}
setLoading(false);
};
Rendering:
return (
<div className={classes.root}>
{!loading && !error && test? (
<div>
<Card className={classes.card}>
<CardContent>
<Title>Adattributes</Title>
<Typography variant="h6" component="h1">
name
</Typography>
<Grid container spacing={3}>
<Grid item xs={3}>
<Typography variant="subtitle2" component="h1">
address
</Typography>
{test.title}
</Grid>
</Grid>
</CardContent>
<CardActions>
<Component1
value={test}
setTest={setTest}
/>
</CardActions>
</Card>
</div>
) : (
<Component2 setTest={setTest} />
)}
</div>
);
});
Am I doing something wrong with the conditional rendering? Or do it have something to do with fetching async?
The initial state of your test state is falsy. This, plus operator precedence can lead to wrong errors. See http://www-lia.deis.unibo.it/materiale/JS/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence.html.
Probably what you want to so is:
(!loading && !error && test) ? ... : ...
In the first line you use useState hook without initial state, so your state become undefined (remember that undefined is falsy value).
const [test, setTest] = useState();
Either you should set initial state for test, or change your condition for rendering component.

Categories