I'm trying to open a modal dialog from a set of cards that has been looping from the data the component receives. I can't figure it out how to make the modal get the appropriate data from the clicked card. In my code below, I tried to put the modal outside the loop, but then I can't figure it out how to pass the id of the clicked card to a new function which would control the modal
Here is the Component that manages the cards loop and contains the modal
import React, {Component} from 'react';
import {Nav, NavItem, NavLink, Card, CardImg, CardText,
CardBody, CardTitle, CardSubtitle, FormGroup, Input, Col, Button, Modal, ModalHeader, ModalBody} from 'reactstrap';
import classnames from 'classnames';
class ProductCard extends Component {
constructor(props){
super(props);
this.state={
productList: this.props.products,
isModalOpen: false
}
this.toggleModal = this.toggleModal.bind(this)
}
toggleModal() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
}
render(){
return(
this.state.productList.map(prod => (
<div key={prod.prod_id} className="col-12 col-md-3 mb-4 rowCard" onClick={this.toggleModal}>
<Card>
<CardImg top width="100%" src={prod.prod_image} alt={prod.prod_name_eng}/>
<CardBody>
<CardTitle>{prod.prod_name_eng}</CardTitle>
<CardSubtitle>{prod.prod_cost_total}</CardSubtitle>
<CardText>{prod.prod_description}</CardText>
</CardBody>
</Card>
<Modal isOpen={this.state.isModalOpen} toggle={this.toggleModal}>
<ModalHeader toggle={this.toggleModal}>{prod.prod_name_eng}</ModalHeader>
<ModalBody>{prod.prod_description}</ModalBody>
</Modal>
</div>
))
);
}
}
Any help is welcome! thanks
I would suggest moving the Modal outside of your map, since that makes things more complicated than they need to be. If you do this, then you toggleModal method is then responsible for accepting an index (supplied by the map function) and then you would just need to retrieve the correct text for the modal elements.
toggleModal(index) {
this.setState({
cardIndex: index,
isModalOpen: !this.state.isModalOpen
});
}
Then you're modal just needs to reference the productList in state, access the index and get the title and description:
class ProductCard extends Component {
constructor(props) {
super(props);
this.state = {
productList: this.props.products,
cardIndex: null,
isModalOpen: false
};
this.toggleModal = this.toggleModal.bind(this);
}
toggleModal(id) {
console.log(id);
this.setState({
cardIndex: id,
isModalOpen: !this.state.isModalOpen
});
}
render() {
const { productList, cardIndex } = this.state;
console.log("CardIndex: ", cardIndex);
console.log("Product: ", productList[cardIndex]);
return (
<Fragment>
{productList.map((prod, index) => {
return (
<div
key={prod.prod_id}
className="col-12 col-md-3 mb-4 rowCard"
onClick={e => this.toggleModal(index)}
>
<Card>
<CardImg top src={prod.prod_image} alt={prod.prod_name_eng} />
<CardBody>
<CardTitle>{prod.prod_name_eng}</CardTitle>
<CardSubtitle>{prod.prod_cost_total}</CardSubtitle>
<CardText>{prod.prod_description}</CardText>
</CardBody>
</Card>
</div>
);
})}
<Modal
isOpen={this.state.isModalOpen}
toggle={e => this.toggleModal(cardIndex)}
>
<ModalHeader toggle={e => this.toggleModal(cardIndex)}>
{cardIndex !== null && productList[cardIndex].prod_name_eng}
</ModalHeader>
<ModalBody>
{cardIndex !== null && productList[cardIndex].prod_description}
</ModalBody>
</Modal>
</Fragment>
);
}
}
Here is a codesandbox link to a working version:
Related
I'm trying to get my React CardImg to render with no luck. I'm importing the photos array, passing it in state, and trying to render it on line's 40 CardImg component. I've tried changing the file paths with no luck. Any help would be appreciated. Images folder is inside the public folder.
import React, { Component } from 'react';
import { CardTitle, Jumbotron, Card, CardBody, CardHeader, CardImg, Button, Modal, ModalHeader, ModalBody, Form, FormGroup, Input, Label, Col, Row, InputGroup, CardSubtitle, CardImgOverlay } from 'reactstrap';
import DatePicker from "react-datepicker";
import { PHOTOS } from '../shared/photos';
import "react-datepicker/dist/react-datepicker.css";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
photos: PHOTOS,
};
this.toggleModal = this.toggleModal.bind(this)
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(values) {
this.props.postQuery(values);
};
toggleModal() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
}
render() {
return (
<React.Fragment>
<Jumbotron id="homePage"></Jumbotron>
<div className="container">
<div className="row">
<div className="col-sm-6">
<h1>Maximum Experience</h1>
<h2>Minimum Weight</h2>
</div>
<div className="col-sm-6">
<Card>
<CardImg src={this.state.photos.image} width='100%' alt="Hiking" />
</Card>
</div>
</div>
photos.js
export const PHOTOS = [
{
id: 0,
name: 'Card Home Image',
image: 'images/homeCard.jpg',
}
]
Your this.state.photos is an array. So you need {this.state.photos[0].image}.
When you have more than one photo objects in the array you need to iterate over it to render all photos, something like
{
this.state.photos.map(photo => (
<Card>
<CardImg src={photo.image} width='100%' alt="Hiking" />
</Card>
)
}
I had already found a solution by luck so I don't quite understand how it worked even after trying to read stuff online.
I am simply trying to get the array of comments inside the selectedDish prop. The selectedDish's state is populated inside the Main Component's function, and I am trying to access selectedDish's array of comments inside the DishDetail component.
I was able to get the comments by:
{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}
But I am unable to when I just do the following and receive this error "Cannot read property 'comments' of undefined":
{ this.renderComments(this.props.selectedDish.comments)}
Why does this work? Shouldn't this.props.selectedDish.comments be enough as I am able to successfully render the "renderDish(dish)" function?
Main Component
import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import Menu from './MenuComponent';
import Dishdetail from './DishdetailComponent';
import { DISHES } from '../shared/dishes';
class Main extends React.Component {
constructor() {
super();
this.state = {
dishes: DISHES,
selectedDish: null
};
// this.onDishSelect = this.onDishSelect.bind(this);
}
onDishSelect(dishId) {
this.setState({
selectedDish: dishId
});
}
render() {
return (
<div>
<Navbar dark color="primary">
<div className="container">
<NavbarBrand href="/">Ristorante Con Fusion</NavbarBrand>
</div>
</Navbar>
<Menu dishes={this.state.dishes} onClick={(dishId) => this.onDishSelect(dishId)} />
<Dishdetail selectedDish={this.state.dishes.filter((dish) => dish.id === this.state.selectedDish)[0]} />
</div>
);
}
}
export default Main;
DishDetail Component
import React, { Component } from 'react';
import { Card, CardImg, CardText, CardBody, CardTitle } from 'reactstrap';
export default class Dishdetail extends React.Component {
constructor(props) {
super(props);
}
renderDish(dish) {
if (dish != null)
return (
<Card >
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
);
else
return (
<div></div>
);
}
renderComments(comments) {
let list = (<div></div>);
if (comments != null) {
list = (
<ul className="list-unstyled">
{comments.map(c => {
return (
<li key={c.id}>
<p>{c.comment}</p>
<p>-- {c.author}, {new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: '2-digit' }).format(new Date(Date.parse(c.date)))}</p>
</li>
);
})}
</ul>
);
}
return (
<div>
<h4>Comments</h4>
{list}
</div>
);
}
render() {
return (
<div className="row">
<div className="col-12 col-md-5 m-1">
{this.renderDish(this.props.selectedDish)}
</div>
<div className="col-12 col-md-5 m-1">
{/* This works: */}
{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}
{/* This doesn't: */}
{/* { this.renderComments(this.props.selectedDish.comments)} */}
</div>
</div>
);
}
}
When your main component renders for the first time the props are still undefined as DISHES haven't been loaded, reasons for that might be the DISHES are initiated with some external resource.
So when this.props.selectedDish is undefined you can't access it's key because they don't exist. So react throws this error.
{ this.props.selectedDish && this.renderComments(this.props.selectedDish.comments)}
So this ensures this.props.selectedDish exists and then you are accessing it's comments key.
When your component is loaded the first time the selectedDish state value is null. It only gets set later, hence the error.
Changing the below code
constructor() {
super();
this.state = {
dishes: DISHES,
selectedDish: null
};
// this.onDishSelect = this.onDishSelect.bind(this);
}
To
constructor() {
super();
this.state = {
dishes: DISHES,
selectedDish: {}
};
// this.onDishSelect = this.onDishSelect.bind(this);
}
will make it work. However I feel that what you are doing now i.e. null check and then render is a more robust way of doing it.
Just a few pointers , you don't need to have a class based component since you aren't using lifecycle methods etc.
you can use destructuring to reduce typing
let {selectedDish:{comments}} = this.props .
You could solve your problem with a default value for comments ,
`const Dishdetail = ({selectedDishes:{comments}}=[]}) => {}
and conditional rendering in render
<div>
{ comments.length > 0 &&
//map you comments here if you have some
}
</div>`
I have 2 class components in 2 different js files. The "dish" variable is defined in one and I'm trying to use it in the second file. I'm still new to this, so I'm really not sure if I'm structuring this correctly. Could some one help?
First class component
import React, { Component } from "react";
import {
Card,
CardImg,
CardImgOverlay,
CardText,
CardBody,
CardTitle
} from "reactstrap";
import Dishdetail from "./DishdetailComponent";
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
selectedDish: null,
details: Dishdetail
};
}
onDishSelect(dish) {
this.setState({
selectedDish: dish
});
}
renderDish(dish) {
if (dish != null) {
return <Dishdetail details={this.state.details} />;
} else {
return <div />;
}
}
render() {
const menu = this.props.dishes.map(dish => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id} onClick={() => this.onDishSelect(dish)}>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle> {dish.name} </CardTitle>
</CardImgOverlay>
</Card>
</div>
);
});
return (
<div className="container">
<div className="row"> {menu}</div>
<div className="row">
<div className="col-12 col-md-5 m-1">
{this.renderDish(this.state.selectedDish)}{" "}
</div>{" "}
</div>{" "}
</div>
);
}
}
export default Menu;
=======================
Second code
import React, { Component } from "react";
import {
Card,
CardImg,
CardImgOverlay,
CardText,
CardBody,
CardTitle
} from "reactstrap";
import Menu from "./MenuComponent";
class Dishdetail extends Component {
constructor(props) {
super(props);
this.state = {
dish: Menu
};
}
render() {
return (
<div className="container">
<div className="row">
<dish />
<Card>
<CardImg top src={dish.image} alt={dish.name} />
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
</div>
</div>
);
}
}
export default Dishdetail;
The error message I'm getting is that "dish" variable is not defined
You cannot set props to state in child like this,
this.state = {
dish: Menu
};
As you are passing details as props from parent component you should do this,
constructor(props) {
super(props)
this.state = {
dish: props.details,
}
}
And you should use this state in child component like.
{this.state.dish}
Demo - How to pass data from parent and usage in child component.
Update
Your Code - I have corrected your code. Just make sure you pass correct details to your Menu component.
Note: This code gives you error because I don't know what this.props.dishes is. Ignore the error and concentrate on code only.
I am building a product grid order tool for an e-commerce website. It allows the merchandiser to change the order in which the products display.
This is achieved through drag-and-drop superpowers of Packery by David Desandro https://packery.metafizzy.co/
Seems there are two ways to do this. Either run his code (with jQuery) in a componentDidMount() {}; or find a React version of Packery, like https://www.npmjs.com/package/react-packery-component . There are a number of these but all present a similar problem. Their examples call the object differently. (Mine has curly braces). And I am getting a frightening TypeError!
TypeError: Cannot read property 'bool' of undefined
import React, { Component } from 'react'
import {
Card,
CardImg,
CardBody,
CardTitle,
Input,
InputGroup,
Container,
Row,
// Col,
Jumbotron
} from 'reactstrap';
import Papa from 'papaparse'
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css'
import Packery from 'react-packery-component'
class App extends Component {
constructor(props) {
super(props);
this.state = {data: [] }; // State holds gridorder / neworder
this.handleChange = this.handleChange.bind(this);
this.updateData = this.updateData.bind(this)
}
handleChange(event) {
event.preventDefault()
const inventory = event.target.files[0]
Papa.parse(inventory, {
header: true,
complete: this.updateData
})
} // END
updateData(results) {
const data = results.data
console.log(data)
this.setState({data}) // {data:data}
}
renderData() {
return this.state.data.length > 1
? this.state.data.map((item) => ( // Object in return
<Card className="grid-item" key={item.sku} >
<CardImg src={item.image} />
<CardTitle> {item.sku} </CardTitle>
<CardBody> {item.name} </CardBody>
</Card>
))
: null
}
render() {
return (
<div>
<Jumbotron>
<form >
<InputGroup>
Name:
<Input type="file" onChange={this.handleChange} />
</InputGroup>
</form>
</Jumbotron>
<div className="album">
<Container>
{/* This throws a TypeError. NOTE: I am calling renderData() */}
<Packery className="grid" > {this.renderData()} </Packery>
</Container>
</div>
</div>
);
}
} // END
export default App
The reason I am keeping the object in state is because, that is the thing that will change. gridorder in, neworder out. Thank you in advance, for I could sure use the help.
I need to render a modal/lightbox component dynamic into a list array component, but it only renders the last modal content.
How can I turn this modal component dynamic to call it from the main component and populate it with correct data from an object array?
My List component is:
import React, { Component } from 'react';
import LightBox from './LightBox';
class ListPrice extends Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
}
toggleModal = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return (
<div>
{this.props.products.map(product => {
return(
<div>
<a key={product.id} onClick={this.toggleModal}>
<h3>{product.title}</h3>
<p>{product.description}</p>
</a>
<LightBox key={product.id} show={this.state.isOpen}
onClose={this.toggleModal}>
{product.modalContent}
</LightBox>
</div>
);
})}
</div>
);
}
}
export default ListPrice;
And my LightBox component is (I removed styles to display short code here):
import React from 'react';
import PropTypes from 'prop-types';
class LightBox extends React.Component {
render() {
if(!this.props.show) {
return null;
}
return (
<div>
<div>
{this.props.children}
<div>
<button onClick={this.props.onClose}>
Close
</button>
</div>
</div>
</div>
);
}
}
LightBox.propTypes = {
onClose: PropTypes.func.isRequired,
show: PropTypes.bool,
children: PropTypes.node
};
export default LightBox;
Thank you for any advice :)
With show={this.state.isOpen} you always display all the modals - only the last one is visible as other modals are displayed behind it.
In order to fix that you must show only the selected dialog. You can store opened dialog in state with construct like this.setState({ openedDialog: product.id }).
Then you can query if the dialog is open by using this.state.openedDialog === product.id. That should do the job.
openModal = (id) = () => {
this.setState({
openedDialog: id
});
}
closeModal = () => {
this.setState({
openedDialog: null
});
}
show={this.state.openedDialog === product.id}
onClick={this.openModal(product.id)}
onClose={this.closeModal}