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.
Related
I am building a To-Do List web app with React as my first project.
I want to implement local storage which works fine only that,I am unable to handle check and uncheck of the checkbox prefectly.
Here is a link to the deployed website so you can understand the problem I am having.
https://rapture-todo.netlify.app/
When you add a todo, and mark it complete.
on reload, the checkbox of the todo is unchecked but the todo is marked complete.
Here is my source code[github link- https://github.com/coolpythoncodes/React-ToDo-List].
For App.js
import React, { Component } from 'react';
import Header from './component/Header';
import Info from './component/Info';
import AddToDo from './component/AddToDo';
import TodoListItem from './component/TodoListItem';
import './sass/main.scss';
class App extends Component{
constructor(props){
super(props);
this.state= {
value: '',
list: [],
show: true,
};
this.handleChange= this.handleChange.bind(this);
this.handleSubmit= this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.deleteTask = this.deleteTask.bind(this);
}
componentDidMount() {
const list = window.localStorage.getItem('userTodo') ? JSON.parse(localStorage.getItem('userTodo')) : [];
this.setState({ list })
}
handleChange(e) {
this.setState({value:e.target.value})
}
// Handle submission of user todo item
handleSubmit(e) {
e.preventDefault();
const newTask = {
id: Date.now(),
userTodo: this.state.value,
isCompleted: false,
checked: false,
}
// Validate form so user doesn't add an empty to do
if (this.state.value.length > 0) {
this.setState({
list: [newTask, ...this.state.list],
value: '', // Clear input field
show: true, // Success message
}, ()=>{
window.localStorage.setItem('userTodo', JSON.stringify(this.state.list));
})
}
}
// Handles checkbox
handleInputChange(id) {
this.setState({list: this.state.list.map(item => {
if (item.id === id) {
item.isCompleted = !item.isCompleted;
item.checked = !this.state.checked;
}return item
})}, ()=>{
window.localStorage.setItem('userTodo', JSON.stringify(this.state.list));
})
}
// Delete a task
deleteTask(id){
this.setState({list: this.state.list.filter(item => item.id !== id )},()=>{
window.localStorage.setItem('userTodo', JSON.stringify(this.state.list))
})
console.log(this.state.list)
}
render(){
return(
<div>
<Header />
<Info />
<AddToDo onChange={this.handleChange} value={this.state.value} onSubmit={this.handleSubmit} />
<TodoListItem deleteTask={this.deleteTask} onChange={this.handleInputChange} list={this.state.list} defaultChecked={this.state.checked} />
</div>
)
}
}
export default App;
For TodoListItem.js
import React, { Component } from 'react';
import ToDoItem from './ToDoItem';
import '../sass/main.scss';
class ToDoListItem extends Component{
render(){
const {list, onChange, deleteTask, defaultChecked} = this.props;
return(
<div>
{list.map((todo)=>{
return (
<ToDoItem
key={todo.id}
userTodo={todo.userTodo}
isCompleted={todo.isCompleted}
onChange={onChange}
id={todo.id}
deleteTask={deleteTask}
defaultChecked={defaultChecked}
/>
)
})}
</div>
)
}
}
export default ToDoListItem;
For TodoItem.js
import React, { Component } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrashAlt } from '#fortawesome/free-solid-svg-icons'
import '../sass/main.scss';
class ToDoItem extends Component{
render(){
const {userTodo, isCompleted, onChange, id, deleteTask, defaultChecked} = this.props;
const checkStyle = isCompleted ? 'completed-todo' : 'not-completed-todo';
return(
<div className={`container ${checkStyle}`}>
<input type="checkbox" onChange={onChange.bind(this, id)} defaultChecked={defaultChecked}/>
<div >
<p className='title'>{userTodo}</p>
</div>
{/* Delete button */}
<button onClick={deleteTask.bind(this, id)}><FontAwesomeIcon className='remove-icon' icon={faTrashAlt} /></button>
</div>
)
}
}
export default ToDoItem;
Please note: I have gone through other questions similar to the problem I am having but I could not solve this problem.
If I did not state the question well, please let me know.
In the below code in App.js,
<TodoListItem deleteTask={this.deleteTask} onChange={this.handleInputChange} list={this.state.list} defaultChecked={this.state.checked} />
You are setting, defaultChecked={this.state.checked} Why do you do that? There is nothing called checked in the state.
In fact, there is no need to pass the defaultValue.
Make the following changes,
In App.js, remove defaultValue prop for TodoListItem
<TodoListItem deleteTask={this.deleteTask} onChange={this.handleInputChange} list={this.state.list}/>
In TodoListItem.js, remove defaultChecked={defaultChecked}
<ToDoItem
key={todo.id}
userTodo={todo.userTodo}
isCompleted={todo.isCompleted}
onChange={onChange}
id={todo.id}
deleteTask={deleteTask}
defaultChecked={defaultChecked} // Remove this.
/>
In ToDoItem.js,
<input type="checkbox"onChange={onChange.bind(this, id)}
defaultChecked={isCompleted} // Replace defaultValue with isCompleted
/>
I have added an input box which would print items on submit. After that, I am trying to delete items from the list by adding a delete button. But, the button isn't working. I assume I have applied wrong logic in the onDelete function. I have used the filter function to print out elements which are true. I am not sure what mistake I have done over here.
App.js:
import React, { Component } from 'react'
import Result from './Result';
import Navbar from './Navbar';
import Jumbotron from 'react-bootstrap/Navbar';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
export default class App extends Component {
constructor(){
super();
this.state={
addedItems:[],
emailValue:''
}
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
this.onDelete=this.onDelete.bind(this);
}
handleChange(e){
this.setState({
emailValue:e.target.value
})
}
handleSubmit(e){
e.preventDefault();
let items_copy= this.state.addedItems;
items_copy.push(this.state.emailValue)
console.log("item copy"+items_copy)
this.setState({
addedItems:items_copy,
emailValue:''
})
console.log(this.state.addedItems)
}
onDelete(id){
console.log("deleted clicked")
let newArray=this.state.addedItems.filter(i=>i!==this.state.addedItems.id)
this.setState({
addedItems:newArray
})
}
render() {
console.log("")
return (
<div>
<Navbar/>
<Jumbotron>
<p>
Enter the things you would like to buy.
</p>
</Jumbotron>
<Form>
<Form.Group controlId="formBasicEmail">
<Form.Control value={this.state.emailValue} onChange={this.handleChange}type="email" placeholder="Enter email" />
</Form.Group>
<Button onClick={this.handleSubmit}variant="primary" type="submit">
Submit
</Button>
</Form>
{(this.state.addedItems.length>0)?
<Result item={this.state.addedItems} onDelete={this.onDelete} />:'empty' }
</div>
)
}
}
Result.js
import React from 'react'
import ListGroup from 'react-bootstrap/ListGroup'
import Button from 'react-bootstrap/Button'
export default function Result(props) {
const btnStyle={
marginTop:-7,
float:'right'
};
const {item,onDelete}=props;
//let show=item.map(item=>item);
console.log(item)
let itemShow = item.map((item, key,id) =>{
return (
<ListGroup.Item as="li" key={key}>{item}
<Button onClick={()=>onDelete(id)}style={btnStyle}variant="danger">Delete</Button></ListGroup.Item>
)})
return (
<div>
<ListGroup as="ul">
{itemShow}
</ListGroup>
</div>
)
}
let temp = this.state.addedItems;
temp.filter(i=>i.id!==id);
this.setState({
addedItems: temp
});
The first argument from the filter function is the current item, which I'm assuming you're storing objects so the above should work. If you want to filter by index the second argument to filter is the index,
.filter((item, index)=>{});
Just replace this line in your OnDelete method :
newArray=this.state.addedItems.filter(i=>i!==this.state.addedItems.id)
By :
const index = this.state.addedItems.map(function(e) { return e.id; }).indexOf(id);
if (index > -1) {
this.state.addedItems.splice(index, 1);
}
Should do the trick ?
According to the definition of splice function by Mozilla Developer.
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'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:
I need some help building a todo list with React. Im a beginner so there are a lot of aspects of react I don't understand. I've created a Todo list at the moment this lists an un ordered list with the input, edit and remove button a select menu and a further input field.
Firstly Im confused to where I update my state. the "App.js" is where my main state is stored and im trying to keep it that way. I need to know how to edit the input field (todo listitem) which stores the new value. Im then looking to create a "completed list" where i want to store the input field as well as the select option (which ever is clicked) Please could someone give me some guidelines for this. Thank you in advance
import React, { Component } from 'react';
import Form from './Components/Form'
import './App.css';
import List from './Components/List'
import Completed from './Components/Completed'
class App extends Component {
constructor(props){
super(props)
this.state={
isEditing:false,
text:"",
items:[],
completed:[
{
}
]
}
this.submit=this.submit.bind(this);
this.eventHandler=this.eventHandler.bind(this)
}
submit=(e)=>{
e.preventDefault();
this.setState({
items:[
{
name:this.state.text,
},
...this.state.items
],
text:""
})
}
remove=(index)=>{
this.setState({
items:this.state.items.filter((_,i) => i!==index)
})
}
onChange=(index)=>{
this.setState({
items:this.state.items.filter((_,i) => i!==index)
});
}
eventHandler=(e)=>{
this.setState ({
text:e.target.value
})
}
handleNameEdits=()=>{
this.setState({
isEditing:true
})
}
edit=()=>{
this.setState({
isEditing:!this.state.isEditing
})
}
myoptions=(e)=>{
this.setState({
completed:[
{
webmaster:e
},
...this.state.completed
]
})
}
render() {
return (
<div className="App">
<header className="App-header">
<Form submit={this.submit} myValue={this.state.text} eventHandler=
{this.eventHandler}/>
{this.state.items.map && this.state.items.map((item,index)=>(
<List key={index}
name={item.name}
edit={this.edit}
change={()=>this.onChange(index)}
remove={()=>this.remove(index) }
handleNameEdits={this.handleNameEdits}
myoptions={(e =>this.myoptions(e.target.value))}
/>
))}
</header>
<div>
completed
</div>
</div>
);
}
}
export default App;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Edit from './Edit';
class List extends Component {
constructor(props) {
super()
this.options = [
{name:'web1'},
{name:'web2'},
{name:'web3'},
{name:'web4'}
];
}
render() {
const {key} = this.props;
const x=this.options;
return (
<ul>
<li>{this.props.name}
<button onClick={this.props.edit}>Edit</button>
<button onClick={this.props.remove}>Remove</button>
<select onChange={this.props.myoptions}>
{this.options.map(options => <option>{options.name}</option> )}
</select>
<label> Completed
</label><input type="checkbox" onChange=
{this.props.change} checked={this.props.change} onClick=
{this.props.submit}/>
<label> Ticket Number </label><input type='text'/>
</li>
</ul>
)
}
}
export default List;
import React from 'react'
import PropTypes from 'prop-types';
const Form= props=> {
return (
<form onSubmit={props.submit}>
<input type='text' value={props.myValue} onChange=
{props.eventHandler}/>
<button> click</button>
</form>
)
}
Form.PropTypes={
onSubmit:PropTypes.func.isRequired,
evenHandler:PropTypes.func.isRequired,
myValue:PropTypes.string.isRequired
}
export default Form