I'm receiving a cannot read props of undefined. I'm trying to destructure props but I need the hook calls. Is there a way for me to destructure props in a function or another way to resolve this issue?
ProductBrowse.jsx is formatting the products:
const ProductBrowse = () => {
const { id, name, img, store, price, desc, inCart } = this.props.product;
const [open, setOpen] = React.useState(false);
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card>
<CardActionArea>
<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={name}
productDesc={desc}
/>
<CardHeader
title={name}
subheader={formatCurrency(price)}
/>
<CardMedia
image={img}
alt={desc}
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='p'>
{desc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size='small' /*To Checkout*/>BUY NOW</Button>
<Button
size='small'
onClick={() => {
console.log('Added to Cart');
}}
>
ADD TO CART
</Button>
<Button size='small'>REVIEW</Button>
</CardActions>
</Card>
</Box>
);
}
You can convert your class based component to a functional component like this:
const ProductBrowse = ({ product }) => {
const { id, name, img, store, price, desc, inCart } = product;
...
}
export default ProductBrowse;
As you can see, the product props are being destructured. The entire props object is available if you were to provide more props and want to use them as well.
i.e.
const ProductBrowse = (props) => {
const { id, name, img, store, price, desc, inCart } = props.product;
...
}
export default ProductBrowse;
You are trying to use hooks in class based components. Please refer converted functional component
const ProductBrowse = props => {
const { id, name, img, store, price, desc, inCart } = props.product;
const [open, setOpen] = useState(false);
const classes = useStyles();
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card>
<CardActionArea>
{<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={name}
productDesc={desc}
/> }
<CardHeader title={name} subheader={formatCurrency(price)} />
<CardMedia image={img} alt={desc} />
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
{desc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" /*To Checkout*/>BUY NOW</Button>
<Button
size="small"
onClick={() => {
console.log("Added to Cart");
}}
>
ADD TO CART
</Button>
<Button size="small">REVIEW</Button>
</CardActions>
</Card>
</Box>
);
};
Also while using this components pass product as it's props as you are destructuring in ProductBrowse component. It should be like this:
<ProductBrowse products={this.products} />
Related
Im having troubles to expand and contract a Collapse Component from MaterialUI since Im mapping and array and iterating the same component, when i press the collapse button, all components expands/contracts at the same time ( I suppose that Im not providing an identifier to point where the collapse function should be used),Im currently Using an State to control the collapse action:
const [expanded, setExpanded] = useState(false);
This is the return where I iterate the component using map on RecetasAll object,
return (
<React.Fragment key={RecetasAll.id}>
<Card className="searchItem" sx={{ maxWidth: 345 }}>
<CardHeader
action={<IconButton aria-label="settings"></IconButton>}
title={RecetasAll.titulo}
/>
<h4
className="Dieta"
style={{
backgroundColor: color(RecetasAll.Tiporeceta.tipoReceta),
}}
>
{RecetasAll.Tiporeceta.tipoReceta}
</h4>
<span className="Calorias">{RecetasAll.informacionNutricional}</span>
<CardMedia
component="img"
height="194"
image={RecetasAll.imagen}
alt="Paella dish"
/>
<CardContent>
{RecetasAll.Productos.map((Productos) => {
return (
<React.Fragment key={Productos.id}>
<Typography variant="body2" color="text.secondary">
{Productos.producto}
</Typography>
</React.Fragment>
);
})}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<ExpandMore
expand={expanded}
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent id={RecetasAll.id}>
<Typography paragraph>Preparacion:</Typography>
<Typography paragraph>{RecetasAll.pasos}</Typography>
<Button
href="#contained-buttons"
variant="contained"
onClick={handleSearch}
>
Ver mas
</Button>
</CardContent>
</Collapse>
</Card>
</React.Fragment>
);
});
return <>{itemRecetas}</>;
}
Im triying to set an id property to the CardContent since its the child of the Collapse component
id={RecetasAll.id}
this is the function Im using to expand or collapse but I dont know how to get the id properly to compare its value with expanded state:
const handleExpandClick = (e) => {
let clickedItemId = e.currentTarget.id;
if (expanded === clickedItemId) {
setExpanded(!expanded);
} else {
setExpanded(clickedItemId);
}
};
You could refactor every card into a new component and that way you can have a state to open/close the individual card. When iterating you can pass in the RecetasAll.
const MyCard = ({ RecetasAll }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpanded = () => {
setIsExpanded(prevIsExpanded => !prevIsExpanded);
};
return (
...
<ExpandMore
expand={isExpanded}
onClick={toggleExpanded}
aria-expanded={isExpanded}
>
...
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
...
);
};
If you don't want to use a new component you could store all the ids of the expanded cards in a state. Based on if the id is in the array the card will be expanded or collapsed.
const [expandedIds, setExpandedIds] = useState([]);
const toggleExpanded = (id) => {
setExpandedIds((prevExpandedIds) => {
// if id is already in array remove
if (prevExpandedIds.includes(id))
return prevExpandedIds.filter((i) => i !== id);
// else add to array
return [...prevExpandedIds, id];
});
};
return (
...
<ExpandMore
expand={expandedIds.includes(RecetasAll.id)}
onClick={() => toggleExpanded(RecetasAll.id)}
aria-expanded={expandedIds.includes(RecetasAll.id)}
>
...
<Collapse in={expandedIds.includes(RecetasAll.id)} timeout="auto" unmountOnExit>
...
)
You have to Imagine I have a node inside this node are comments(messages) and now I want the user who made the comment to be able to edit or even delete his own comment directly, but only the user who created it.
I honestly don't know if it works that way, but I hope someone can help me with this.
Edit: I add a deleteHandler.
function Messages({ forum }) {
console.log(forum);
const dispatch = useDispatch();
const classes = useStyles();
const [message, setMessage] = useState("");
const messageList = useSelector((state) => state.messageList);
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
const { messages } = messageList;
const handleClick = async () => {
const finalMessage = `${userInfo.userName}: ${message}`;
await dispatch(createMessageAction(forum._id, finalMessage));
dispatch(listMessage());
setMessage("");
};
const deleteHandler = (id) => {
if (window.confirm("Are you sure? you want to delete")) {
dispatch(deleteMessageAction(id));
}
};
useEffect(() => {
dispatch(listMessage());
}, []);
console.log(messages);
return (
<div>
<div className={classes.messagesOuterContainer}>
<div className={classes.messagesInnerContainer}>
<Typography gutterBottom variant="h6">
Comments
</Typography>
{messages
?.filter((message) => message.forumID === forum._id)
?.map((c) => (
<Typography key={c._id} gutterBottom variant="subtitle1">
<strong>{c.messageText} </strong>
<EditIcon />
<IconButton aria-label="delete">
<DeleteIcon onClick={() => deleteHandler(message._id)}/>
</IconButton>
</Typography>
))}
</div>
{userInfo?.userName && (
<div style={{ width: "70%" }}>
<Typography gutterBottom variant="h6">
Comments
</Typography>
<TextField
fullwidth="false"
rows={4}
variant="outlined"
label="add a Comment"
multiline
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<Button
style={{ marginTop: "10px" }}
fullwidth="false"
disabled={!message}
variant="contained"
color="primary"
onClick={handleClick}
>
Send
</Button>
</div>
)}
</div>
</div>
);
My MessageList state:
>messageList
>messages
>0: {_id:"....", forumID:"..", messageText:"...", user:".."
>1: .....
You must have information about the author of the comment. Assuming each entry in messages has that information, create a conditional that checks to see if the current user's id is the same as the message's author id.
Something along the lines of this.
{messages
?.filter((message) => message.forumID === forum._id)
?.map((c) => (
<Typography key={c._id} gutterBottom variant="subtitle1">
<strong>{c.messageText} </strong>
{message.userId === userLogin.id && ( // modify keys names accordingly
<>
<EditIcon />
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
</>
)}
</Typography>
))}
I'm receiving the error 'expected an assignment of function call and instead saw an expression.
*The above error is resolved, it's now giving me an Unhandled Rejection (TypeError): render is not a function. It functions properly until I go to the products page and then gives me the error.
I console logged it and it was pulling the information but then it breaks when I import the ProductBrowse component to display the information.
ProductPage:
class ProductPage extends React.Component {
state = {
shoppingCartOpen: false,
};
drawerToggleClickHandler = () => {
this.setState((prevState) => {
return { shoppingCartOpen: !prevState.shoppingCartOpen };
});
};
render() {
let shoppingCartDrawer;
if (this.state.shoppingCartOpen) {
shoppingCartDrawer = <ShoppingCartDrawer />;
}
return (
<ProductStyling>
<ButtonAppBar drawerClickHandler={this.drawerToggleClickHandler} />
<H1>Products</H1>
{shoppingCartDrawer}
<ProductConsumer>
<Grid container spacing={3}>
{(value) => {
return value.products.map((prod, idx) => {
return (
<Grid item xs={3} key={`${prod.name}-${prod.store}-${idx}`}>
<ProductBrowse
productName={prod.name}
productDesc={prod.desc}
productImg={prod.img}
productPrice={prod.price}
/>
</Grid>
);
});
}}
</Grid>
</ProductConsumer>
;
</ProductStyling>
);
}
}
Structure of value:
const ProductContext = React.createContext();
class ProductProvider extends React.Component {
state = {
products: productData,
};
addToCart = () => {
console.log('Hello from add to cart'); };
render() {
return (
<ProductContext.Provider
value={{ ...this.state, addToCart: this.addToCart }}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
ProductBrowse:
const ProductBrowse = ({
productName,
productDesc,
productImg,
productPrice,
}) => {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card className={classes.root}>
<CardActionArea onClick={() => openModal()}>
<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={productName}
productDesc={productDesc}
/>
<CardHeader
title={productName}
subheader={formatCurrency(productPrice)}
/>
<CardMedia
className={classes.media}
image={productImg}
alt={productDesc}
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='p'>
{productDesc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size='small' /*To Checkout*/>BUY NOW</Button>
<Button size='small'>ADD TO CART</Button>
<Button size='small'>REVIEW</Button>
</CardActions>
</Card>
</Box>
);
};
ProductData:
import desk from '../../../assets/img/desk.webp';
export const productData = [
{
img: desk,
name: 'Desk',
store: 'Local Furniture Shop 1',
price: 9.99,
desc: "This sturdy desk is built to outlast years of coffee and hard work. You get a generous work surface and a clever solution to keep cords in place underneath."
},
Index.js:
ReactDOM.render(
<React.StrictMode>
<Auth0Provider
domain={auth0Domain}
client_id={auth0ClientID}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
audience={auth0Audience}
scope={"read:current_user"}
>
<ProductProvider>
<Provider store={store}>
<App />
</Provider>
</ProductProvider>
</Auth0Provider>
</React.StrictMode>,
document.getElementById('root')
);
You are not returning anything from ProductConsumer You need to do like this:
<ProductConsumer>
<Grid container spacing={3}>
{(value) => {
return value.products.map((prod, idx) => {
return (
<Grid item xs={3} key={`${prod.name}-${prod.store}-${idx}`}>
<ProductBrowse
productName={prod.name}
productDesc={prod.desc}
productImg={prod.img}
productPrice={prod.price}
/>
</Grid>
);
});
}}
</Grid>
</ProductConsumer>;
I am trying to write a function that will save a specific object in an array that the user will click I am unsure of how to grab the data I need from the object
this is the function at the moment:
saveArticle = event => {
const userId = localStorage.getItem("userId")
event.preventDefault();
const article = {
title: this.title,
summary: this.summary,
link: this.link,
image: this.image,
userId: userId
}
console.log(article)
// API.saveArticle()
}
and this is the component where I map through the array
const articleCard = props => {
const { classes } = props
return (
<div>
{props.articles.map((article, i) => {
console.log(article);
return (
<div key={i}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.media}
image={article.image}
title={article.title}
href={article.link}
/>
<CardContent>
<Typography gutterBottom variant="headline">
{article.title}
</Typography>
<Typography component="p">
{article.summary}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button href={article.link} size="small" color="primary">
Read Article
</Button>
<Button onClick={props.saveArticle} size="small" color="primary">
Favorite
</Button>
</CardActions>
</Card>
</div>
)
})}
</div>
)
}
I cant seem to grab the objects properties that I'd like to get and I am pretty lost as too how!
any help would be much appreciated thanks guys!
Try this
<Button onClick={(e) => props.saveArticle(article, e)} size="small" color="primary">
Favorite
</Button>
and
saveArticle = (article, event) => {
const userId = localStorage.getItem('userId');
event.preventDefault();
const article = {
title: article.title,
summary: article.summary,
link: article.link,
image: article.image,
userId: userId
};
console.log(article);
// API.saveArticle()
};
Reference: Passing Arguments to Event Handlers
So I've looked everywhere for a solution, with no luck.
I am trying to pass a parent React component's this.state.nextID to the child component as a property. However, when I try to access that property in the child, it is null. I am using Material-UI for react and I think the problem is with the withStyles function, because when I inspect the source of the page, I see the key property on the withStyles(ServerBlock) node. But then there is a child of that node that is ServerBlock, with no key property. What am I doing wrong?
ConfigBlock.js
class ConfigBlock extends Component {
constructor () {
super()
this.state = {
children: [],
nextID: 0
}
this.handleChildUnmount = this.handleChildUnmount.bind(this);
}
handleChildUnmount = (key) => {
console.log(key)
this.state.children.splice(key, 1);
this.setState({children: this.state.children});
}
addServerBlock() {
this.state.children.push({"id": this.state.nextID, "obj": <ServerBlock unmountMe={this.handleChildUnmount} key={this.state.nextID} />})
this.setState({children: this.state.children})
this.state.nextID += 1
}
addUpstreamBlock() {
this.state.children.push({"id": this.state.nextID, "obj": <UpstreamBlock unmountMe={this.handleChildUnmount} key={this.state.nextID} />})
this.setState({children: this.state.children})
this.state.nextID += 1
}
render () {
const {classes} = this.props;
return (
<div className={classes.container}>
<Card className={classes.card}>
<CardContent>
<Typography className={classes.title} color="primary">
Config
</Typography>
<div>
{this.state.children.map((child, index) => {
return (child.obj);
})}
</div>
</CardContent>
<CardActions>
<Button variant="contained" color="primary" className={classes.button} onClick={ this.addServerBlock.bind(this) }>
Server
<AddIcon />
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={ this.addUpstreamBlock.bind(this) }>
Upstream
<AddIcon />
</Button>
</CardActions>
</Card>
</div>
);
}
}
ConfigBlock.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(ConfigBlock);
ServerBlock.js
class ServerBlock extends Component {
constructor (props) {
super(props)
this.state = {
children: []
}
}
addServerBlock() {
this.state.children.push(<NginxEntry/>)
this.setState({children: this.state.children})
}
deleteMe = () => {
this.props.unmountMe(this.props.key);
}
render () {
const {classes} = this.props;
return (
<div className={classes.container}>
<Card className={classes.card}>
<CardContent>
<Typography className={classes.title} color="primary">
Server
</Typography>
</CardContent>
<CardActions>
<Button variant="contained" color="primary" className={classes.button} onClick={() => { console.log('onClick'); }}>
Key/Value
<AddIcon />
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={() => { console.log('onClick'); }}>
Location
<AddIcon />
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={() => { console.log('onClick'); }}>
Comment
<AddIcon />
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={ this.deleteMe }>
<DeleteIcon />
</Button>
</CardActions>
</Card>
</div>
);
}
}
ServerBlock.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(ServerBlock);
key is a special React attribute, it is not a prop, i.e. the child will never have access to it's value. If the child needs to use that value, provide it via another prop (as well as via the key), e.g.
<ServerBlock
unmountMe={this.handleChildUnmount}
key={this.state.nextID}
id={this.state.nextID}
/>
...Aside from that, your code is quite unusual. You should not be directly mutating state (you should always use setState), and you wouldn't normally store whole components in your state. Just as food for thought here's an alternative (untested) implementation of your ConfigBlock component that uses setState and shifts around some of the logic a bit:
class ConfigBlock extends Component {
constructor () {
super()
this.state = {
nextID = 0,
children: [],
}
this.handleChildUnmount = this.handleChildUnmount.bind(this);
// bind this function once here rather than creating new bound functions every render
this.addBlock = this.addBlock.bind(this)
}
handleChildUnmount = (key) => {
console.log(key)
this.setState(state => {
return {
// `state.children.splice(key, 1)`, aside from mutating the state,
// will not work as expected after the first unmount as the ids and
// array positions won't stay aligned
children: state.children.slice().filter(child => child.id !== key)
}
})
}
// Consolidate the two addBlock functions, given we're determining the type
// of component to render in the render function.
addBlock(blockType) {
this.setState(state => {
return {
children: [...state.children, { id: state.nextID, type: blockType }]
nextID: state.nextID + 1
}
})
}
render () {
const {classes} = this.props;
return (
<div className={classes.container}>
<Card className={classes.card}>
<CardContent>
<Typography className={classes.title} color="primary">
Config
</Typography>
<div>
{this.state.children.map(child => {
// determine the component to render here rather than in the handlers
if (child.type === 'server') {
return <ServerBlock key={child.id} id={child.id} unmountMe={this.handleChildUnmount(child.id)} />
} else if (child.type === 'upstream') {
return <UpstreamBlock key={child.id} id={child.id} unmountMe={this.handleChildUnmount(child.id)} />
}
})}
</div>
</CardContent>
<CardActions>
<Button variant="contained" color="primary" className={classes.button} onClick={this.addBlock('server')}>
Server
<AddIcon />
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={this.addBlock('upstream')}>
Upstream
<AddIcon />
</Button>
</CardActions>
</Card>
</div>
);
}
}