Some problems with react.js - javascript

I'm recently working on react.js, and now I have two problems:
The file structure is like
>public
>src
>components
>img
x.png
Item.js
App.js
index.js
Item.js:
import React from 'react';
class Item extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<li className="todo-app__item">
<div className="todo-app__checkbox">
<input type="checkbox" id={this.props.num}
checked={this.props.completed} onClick = {this.props.onClick}/>
<label htmlFor={this.props.num}></label>
</div>
<h1 className="todo-app__item-detail">{this.props.text}</h1>
<img src='./components/img/x.png' className="todo-app__item-x"/>
</li>
);
}
}
export default Item;
App.js
import './App.css';
import React from 'react';
import Item from './components/Item.js';
class Main extends React.Component{
constructor(props){
super(props);
this.state={tasks: []}
}
handleKeyDown = (e)=>{
if (e.key === 'Enter') {
this.setState (prevState => ({
tasks: [...prevState.tasks, {content: e.target.value, completed: false}]
}));
}
}
handleClick = (e) =>{
this.setState (prevState => {
let newTasks = prevState.tasks.slice();
newTasks[e].completed = !prevState.tasks[e].completed;
return{tasks: newTasks};
})
}
/*displayAll = () =>{
}
displayActive = () =>{
}
displayCompleted = () =>{
}
deleteCompleted = () =>{
}*/
render(){
return(
<section className="todo-app__main">
<input className="todo-app__input"
placeholder="What needs to be done?" onKeyDown={this.handleKeyDown} />
<ul className="todo-app__list" id="todo-list">
{this.state.tasks.map(item =>
<Item num={this.state.tasks.indexOf(item)} text={item.content} completed={item.completed}
onClick={() => this.handleClick(this.state.tasks.indexOf(item))}/>)}
</ul>
<footer className="todo-app__footer" id="todo-footer">
<div className="todo-app__total"> {this.state.tasks.filter(e=>e.completed===false).length} Left</div>
<ul className="todo-app__view-buttons">
<button>All</button>
<button>Active</button>
<button>Complete</button>
</ul>
<div className="todo-app__clean">
<button>Clear complete</button>
</div>
</footer>
</section>
);
}
}
My x.png is not showing properly. How to fix it?
The three buttons in the bottom, I hope that completed can do something like displaying the task that satisfies task[1]=true without deleting the item. How do I implement this?

You can modify the src attribute of img, the relative path is wrong now
<img src='./img/x.png' className="todo-app__item-x"/>
Use react state to render different buttons
{this.state.tasks[x].completed && <button>Complete</button>}
eg: when tasks x is completed, display button.

Related

React App class won't render when a child Component changes

I just started to learn React. I'm trying to write a Todo list and so far it looks like:
However when I check the box of a Todo, the count of things left to do won't change even when the state of a list of Todos changes (the 'checked' property of a Todo that I just checked change to true)
My App.js:
import React, {Component} from 'react';
import TaskComponent from "./TaskComponent";
class App extends Component {
constructor(props) {
super(props)
this.state = {
taskList: [],
newTaskContent: ''
}
this.generateTask = this.generateTask.bind(this)
this.updateNewTaskContent = this.updateNewTaskContent.bind(this)
}
generateTask() {
if (this.state.newTaskContent) {
const joined = this.state.taskList.concat({
id: this.state.taskList.length + 1,
content: this.state.newTaskContent,
checked: false
})
this.setState({taskList: joined, newTaskContent: ''})
}
}
updateNewTaskContent({target: {value}}) {
this.setState({newTaskContent: value})
}
render() {
return (
<div>
<ul>{this.state.taskList.map(task => <TaskComponent key={task.id} task={task.content}
checked={task.checked}/>)}</ul>
<input type='text' placeholder='Type your new task'
onChange={this.updateNewTaskContent} value={this.state.newTaskContent}/>
<button name='generateTask' onClick={this.generateTask}>Generate task</button>
<div>There are {this.state.taskList.filter(task => !task.checked).length} things left to do!</div>
</div>
);
}
}
export default App;
My TaskComponent.js file:
import React, {Component} from 'react'
class TaskComponent extends Component {
constructor({task, checked}) {
super(undefined)
this.state = {
taskContent: task,
checkedState: checked
}
this.changeHandler = this.changeHandler.bind(this)
}
changeHandler({target: {checked}}) {
this.setState({checkedState: checked})
}
render() {
return (
<div>
<span>{this.state.taskContent}</span>
<input type="checkbox" checked={this.state.checkedState} onChange={this.changeHandler}/>
</div>
);
}
}
export default TaskComponent;
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
taskList: [],
newTaskContent: ''
}
this.generateTask = this.generateTask.bind(this)
this.updateNewTaskContent = this.updateNewTaskContent.bind(this)
}
generateTask() {
if (this.state.newTaskContent) {
const joined = this.state.taskList.concat({
id: this.state.taskList.length + 1,
content: this.state.newTaskContent,
checked: false
})
this.setState({taskList: joined, newTaskContent: ''})
}
}
updateNewTaskContent({target: {value}}) {
this.setState({newTaskContent: value})
}
render() {
return (
<div>
<ul>{this.state.taskList.map(task => <TaskComponent key={task.id} task={task.content}
checked={task.checked}/>)}</ul>
<input type='text' placeholder='Type your new task'
onChange={this.updateNewTaskContent} value={this.state.newTaskContent}/>
<button name='generateTask' onClick={this.generateTask}>Generate task</button>
<div>There are {this.state.taskList.filter(task => !task.checked).length} things left to do!</div>
</div>
);
}
}
class TaskComponent extends React.Component {
constructor({task, checked}) {
super(undefined)
this.state = {
taskContent: task,
checkedState: checked
}
this.changeHandler = this.changeHandler.bind(this)
}
changeHandler({target: {checked}}) {
this.setState({checkedState: checked})
}
render() {
return (
<div>
<span>{this.state.taskContent}</span>
<input type="checkbox" checked={this.state.checkedState} onChange={this.changeHandler}/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Inside the TaskComponent class I add the function for the event of changing the "checked" state of the check box but somehow the 'taskList' state in my App does not change at all when I try to console.log it. What is my problem here? Be gentle since I'm new to React and Javascript in general.
You are setting state in TaskComponent and expecting it to change the prop in App.js.
Instead of setting a TaskComponents's state when it is checked, I would recommend calling a function passed in as a prop when it is checked, which has its id and new value. Something along the lines of:
App.js:
// somewhere in your class:
handler(id, value) {
// set state to reflect changes
}
// in your render()
<ul>{this.state.taskList.map((task) => {
<TaskComponent onChange={this.handler} id={task.id} key={task.id} task={task.content} checked={task.checked} />})}</ul>
In TaskComponent.js:
changeHandler({target: {checked}}) {
this.props.onChange(this.props.id, checked);
}
I would also recommend making TaskComponent not have state at all, because it seems unnecessary to me.

why does one react project use .bind(this) method but another react project does not use it? [duplicate]

This question already has answers here:
Arrow vs classic method in ES6 class
(1 answer)
Why we don't need to bind the arrow function in React?
(2 answers)
Closed 2 years ago.
I did 2 tutorials on using REACT. "to-do-app" is a django/react project while "robofriends" is a purely react project. "to-do-app" uses .bind(this) for its functions while "robofriends" does not use this method. While i understand why the .bind method is needed, i do not understand why "robofriends" was able to work without binding its functions.
to-do app (apps.js)
import React from 'react';
import './App.css';
class App extends React.Component{
constructor(){
super();
this.state={
todoList:[],
activeItem:{
id:null,
title:"",
completed:false
},
editing:false,
}
this.fetchTasks=this.fetchTasks.bind(this)
this.handleChange=this.handleChange.bind(this)
this.handleSubmit=this.handleSubmit.bind(this)
}
componentWillMount(){
this.fetchTasks()
}
fetchTasks(){
console.log('fetching')
fetch('http://127.0.0.1:8000/api/task-list/')
.then(response=>response.json())
.then(data=>
this.setState({todoList:data})
)
}
handleChange(e){
const name=e.target.name;
const value=e.target.value;
console.log('Name', name)
console.log('Value', value)
this.setState({
activeItem:{
...this.state.activeItem,
title:value
}
})
}
handleSubmit(e){
e.preventDefault()
console.log('ITEM', this.state.activeItem)
var url='http://127.0.0.1:8000/api/task-create/'
fetch(url, {
method:'POST',
headers:{
'Content-type':'application/json',
},
body:JSON.stringify(this.state.activeItem)
}).then((response) =>{
this.fetchTasks()
this.setState({
activeItem:{
id:null,
title:"",
completed:false
}
})
}).catch(function(error){
console.log('ERROR', error)
})
}
render(){
const tasks=this.state.todoList
return(
<div className="container">
<div id="task-container">
<div id="form-wrapper">
<form onSubmit={this.handleSubmit} id="form">
<div className="flex-wrapper">
<div style={{flex:6}}>
<input onChange={this.handleChange} className="form-control" id="title" type="text" name="title" placeholder="Add task" />
</div>
<div style={{flex:1}}>
<input id="submit" className="btn btn-warning" type="submit" name="Add" />
</div>
</div>
</form>
</div>
<div id="list-wrapper">
{tasks.map((task, index)=>{
return(
<div key={index} className="task-wrapper flex-wrapper">
<div style={{flex:7}}>
<span>{task.title}</span>
</div>
<div style={{flex:1}}>
<button className="btn btn-sm btn-outline-info">Edit</button>
</div>
<div style={{flex:1}}>
<button className="btn btn-sm btn-outline-dark delete">-</button>
</div>
</div>
)
})}
</div>
</div>
</div>
)
}
}
export default App;
robofriends(apps.js)
import React, {Component} from 'react';
import CardList from '../components/Cardlist';
// import {robots} from './robots';
import SearchBox from '../components/SearchBox';
import './App.css';
import Scroll from '../components/Scroll';
import ErrorBoundary from '../components/ErrorBoundary';
class App extends Component {
constructor(){
super()
this.state={
robots:[],
searchfield:''
} //a state is what changes in an app
}
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(response=> response.json())
.then(users => this.setState({robots:users}))
}
// remember to use => for functions you create yourself.
onSearchChange=(event) => {
this.setState({searchfield:event.target.value})
}
render(){
const {robots, searchfield}=this.state;
const filteredRobots=robots.filter(robot=>{
return robot.name.toLowerCase().includes(searchfield.toLowerCase());
})
if (!robots.length){
return <h1>Loading</h1>
}else{
return(
<div className='tc'>
<h1 className='f1'>Robofriends</h1>
<SearchBox searchChange={this.onSearchChange}/>
<Scroll>
<ErrorBoundary>
<CardList robots={filteredRobots}/>
</ErrorBoundary>
</Scroll>
</div>
);
}
}
}
export default App;
robofriends 'searchbox.js'
import React from 'react';
const SearchBox=({searchChange})=>{
return (
<div className='pa2'>
<input
className='pa3 ba b--green bg-lightest-blue'
type='search'
placeholder='search robots'
onChange={searchChange}
/>
</div>
);
}
export default SearchBox;
fetchTasks(){
if you define a function like this, then you need to .bind(this)
as this will lose the context of this of class, to maintain it we need to use .bind(this)
But if you use below one then no need to bind, it maintains the context of this, so no need of using .bind(this)
Arrow functions do not bind their own this, instead, they inherit the
one from the parent scope, which is called "lexical scoping"
fetchTasks = () => {
For more detail : DO READ
Hope the below snippet will help you get understanding :
const { useState , useEffect } = React;
class App extends React.Component {
constructor() {
super();
this.state = {
name : "Vivek"
}
this.withBound = this.withBound.bind(this);
}
withoutBound() {
console.log(this.state.name)
}
withBound() {
console.log(this.state.name)
}
withFatArrow = () => {
console.log(this.state.name)
}
render() {
return (
<div>
Check the console on click:
<br/><br/>
<button onClick={this.withoutBound}>Without Bind</button>
<br/><br/>
<button onClick={this.withBound}>With Bind</button>
<br/><br/>
<button onClick={this.withFatArrow}>Fat Arrow</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>

Get prop value from div in React

A working example of my problem can be found at:
https://codepen.io/RyanCRickert/pen/vYYQeaW
I am prop drilling a function two levels and passing that function along with an index to a rendered component. When a name is submitted it renders a new component which shows the name and div which has an onClick (X). I am trying to receive the index of where the name is located in the array which it lives so that I may splice it out when the button is clicked.
If I enter the name "Bob" for example, then click the div with the listener I can console log the event.target. Using the above example I get "<div class='person-item__X' value='0'>X</div>" for event.target and undefined for event.target.value. The value is being assigned as <div onClick={props.removeName} class="person-item__X" value={props.value}>X</div>.
Am I just unable to grab the value of a div in such a manor? Or is there something that I am missing? Thank you
Change these to your code
const PersonListItem = props => (
<div class="person-item">
<div class="person-item__name">{props.name}</div>
<div onClick={() => props.removeName(props.value)} class="person-item__X" value={props.value}>X</div>
</div>
);
Inside PeopleList replace this line
<PersonListItem key={index} name={person} value={index} removeName={(id) => props.removeName(id)} />
Inside TeamGenerator replace this line
<PeopleList people={this.state.names} removeName={(id) => this.handleRemoveName(id)} />
now in handleRemoveName you will recieve a id of the item on which X was clicked
handleRemoveName = id => {
const currentArr = this.state.names;
console.log(id);
}
In your case, to grab the value inside this div, you should use ref API.
Your code should look like this:
TeamGenerator.js
import React, { Component } from "react";
import CustomModal from "./Modal";
import PeopleList from "./PeopleList";
import "./index.css";
export default class App extends Component {
constructor(props) {
super(props);
// Create a ref
this.divTextRef = React.createRef();
this.state = {
names: [],
selectedName: ""
};
}
handleCloseModal = () => {
this.setState({
selectedName: ""
});
};
handleChange = e => {
this.setState({ name: e.target.value });
};
handleRemoveName = index => {
// Get your name and index this way
console.log("Your text: ", this.divTextRef.current.innerHTML);
console.log("Your index: ", index);
};
handleSubmit = e => {
e.preventDefault();
const currentNames = this.state.names;
if (this.state.name)
currentNames.push(
this.state.name[0].toUpperCase() + this.state.name.slice(1)
);
this.setState({
name: "",
names: currentNames
});
};
render() {
return (
<div className="container">
<CustomModal
selectedName={this.state.selectedName}
closeModal={this.handleCloseModal}
/>
<form onSubmit={this.handleSubmit}>
<label>
Add name:
<input
type="text"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
<div className="people-list-container">
<PeopleList
people={this.state.names}
removeName={this.handleRemoveName}
upperRef={this.divTextRef} // Pass the ref down from your Component tree
/>
</div>
</div>
);
}
}
PeopleList.js
import React from "react";
import PersonListItem from "./PersonListItem";
export default class PeopleList extends React.Component {
render() {
return (
<div className="people-container">
<div className="people-title">List of people</div>
<div className="people-list">
{this.props.people.length === 0 ? (
<div className="people-item">
<span>No people added</span>
</div>
) : (
this.props.people.map((person, index) => (
<PersonListItem
key={index}
name={person}
value={index}
removeName={() => this.props.removeName(index)} // Passing index to the removeName function of Parent
upperRef={this.props.upperRef} // Continue passing it down to PersonListItem
/>
))
)}
</div>
</div>
);
}
}
PersonListItem.js
import React from "react";
const PersonListItem = props => (
<div className="person-item">
<div ref={props.upperRef} className="person-item__name"> // Use the passed ref
{props.name}
</div>
<div
onClick={props.removeName}
className="person-item__X"
value={props.value}
>
X
</div>
</div>
);
export default PersonListItem;
The div node does not have the value like input, so you can not grab it by your old way.

How to identify a single div element among set of elements in react?

I have a react app with a list of Div elements to create some Cards. Each card has 'read more' button to expand and collapse a paragraph and I toggle it for each mouse click. My problem is, for each click, it expand paragraphs in all cards instead only paragraph in the card I clicked. So I can't identify the clicked (this) card.
Component:
class BidCard extends Component {
constructor(props) {
super(props);
this.state = {
readMoreOpen: false,
}
}
readMore() {
this.setState({ readMoreOpen: !this.state.readMoreOpen })
}
render() {
const { articles } = this.props;
return (
articles.map(article => {
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={this.state.readMoreOpen ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={this.readMore.bind(this)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
export default BidCard;
How can I solve this?
You can save id of the expanded card to the state and the check it when rendering items:
class BidCard extends Component {
constructor(props) {
super(props);
this.state = {
readMoreOpen: [], // Use array here
}
}
// Add card id to the expanded array if not already there, otherwise remove it
readMore = (id) => {
this.setState(state => {
if (state.readMoreOpen.includes(id)) {
return {readMoreOpen: state.readMoreOpen.filter(readId => readId !== id)}
}
return {readMoreOpen: [...state.readMoreOpen, id]}
})
}
render() {
const { articles } = this.props;
return (
articles.map(article => {
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
{/*Check if the item is in expanded items array */}
<p className={this.state.readMoreOpen.includes(article.id) ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={() => this.readMore(article.id)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
You will need to keep expanded state per every card.
I would recommend to create component for card
articles.map(article => {
return (
<Article key={article.id} {...article} />
)
})
)
class Article extends Component {
state = {
readMoreOpen: false
}
readMore() {
this.setState(state => ({ readMoreOpen: !state.readMoreOpen }))
}
render () {
const {description} = this.props;
return (<div className="projectCardRoot" >
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={this.state.readMoreOpen ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{description}</p>
<div className="cardReadMore desktopDiv" onClick={this.readMore.bind(this)}>Read more</div>
</div>
</div>
</div>)
}
}
Other approach is to keep array of booleans with information of which article div should be currently expanded in this method you will need to update state with id of expanded article
readMore(id) {
this.setState({ articles: this.props.articles.map(article => article.id === id ? true : false) } )
}
and in render use boolean from state as information if it should be expanded
That's because all your cards currently share the same source of truth. You used a ternary operator to determine what class a Card would have depending on the state-value. Well, all Cards are using the same state-value to compare, so understandably, if one is affected, then all would be too.
There's more than one way to resolve this, but the most appropriate would probably be to create a separate Card Component. This makes it so each Card component has their own state to keep track of.
See working sandbox: https://codesandbox.io/s/quizzical-mahavira-wz8iu
Parent.js
import React from "react";
import ReactDOM from "react-dom";
import Card from "./Card";
import "./styles.css";
class BidCard extends React.Component {
render() {
const { articles } = this.props;
return articles.map(article => {
return <Card article={article} />;
});
}
}
BidCard.defaultProps = {
articles: [{ description: "woof" }, { description: "meow" }]
};
const rootElement = document.getElementById("root");
ReactDOM.render(<BidCard />, rootElement);
Card.js
import React, { useState } from "react";
const Card = ({ article }) => {
const [readOpen, setReadOpen] = useState(false);
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p
className={readOpen ? "openFullParagraph" : "closeFullParagraph"}
id="projectCardDesc"
>
{article.description}
</p>
<div
className="cardReadMore desktopDiv"
onClick={() => setReadOpen(!readOpen)}
>
Read more
</div>
</div>
</div>
</div>
);
};
export default Card;
I did a few modifications to your code. This way it should work.
I added comments that explain the the changes. The main idea is that you should not simply store the boolean readMoreOpen status (which in your code is treated as a kind of shared between all the cards) but specific card identity.
My changes works if there could be only one "expanded" card at any moment. If your design supposes that there could be a few "expanded" cards at the same time the solution would be more complex though not much.
class BidCard extends Component {
constructor(props) {
super(props);
// the way you've tried to keep status (open/closed) it wasn't tied to any speciifc card
// you should store this specific card instead
this.state = {
//readMoreOpen: false,
expandedCard: null,
}
this.readMore = this.readMore.bind(this);
}
readMore(article) {
//this.setState({ readMoreOpen: !this.state.readMoreOpen })
this.setState({expandedCard: article})
}
render() {
const { articles } = this.props;
const { expandedCard } = this.state;
return (
articles.map(article => {
// the look of each card depends on state.expandedCard only if article == expandedCard it's shown with 'openFullParagraph' class
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={article == expandedCard ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={() => this.readMore(article)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
export default BidCard;

Passing data to parent - recipebook React

I am working on the recipe book app and trying to implement the edit entry function.
How it works is to input recipe name (e.g Pasta), followed by ingredients (e.g egg, flour, salt). The ingredients have to be input with commas and will be shown as a list.
Pasta
-Egg
-Flour
i can see that it is somewhat working, because i can see the new entries in the input text (e.g initially was egg,flour,salt -> egg,flour,salt,water) when i tried to edit it again.
However, the extra ingredients (in the above example: water) is not showing up in the list. Do i have to figure a way to re-render the list?
updates:
I think i know where the error might be. There is some issue passing the data and setting the state.
<EditRecipe recipe={this.props.recipe} editRecipe={this.editRecipe.bind(this, this.props.recipe.id, recipe)}/>
App.js
import React, { Component } from 'react';
// import logo from './logo.svg';
import './App.css';
import uuid from 'uuid';
import Modal from 'react-modal';
import RecipeList from './components/RecipeList/RecipeList';
import AddRecipe from './components/AddRecipe/AddRecipe';
class App extends Component {
constructor(props){
super(props);
this.state = {
recipes:[]
};
}
getRecipes(){
this.setState({recipes:[
{
id: uuid.v4(),
food: "pumpkin pie",
ingredients: ["pumpkin puree", "sweetened condensed milk", "eggs", "pumpkin pie spice", "pie crust"]
},
{
id: uuid.v4(),
food: "spaghetti",
ingredients: ["noodles", "tomato sauce", "meatballs"]
},
{
id: uuid.v4(),
food: "onion pie",
ingredients: ["onion", "pie crust"]
},
]});
}
componentWillMount(){
this.getRecipes();
}
handleAddRecipe(recipe){
let recipes = this.state.recipes;
recipes.push(recipe);
this.setState({recipes: recipes});
}
handleDeleteRecipe(id){
let recipes = this.state.recipes;
let index = recipes.findIndex(x => x.id === id);
recipes.splice(index,1);
this.setState({recipes: recipes});
}
handleEditRecipe(id, recipe){
let recipes = this.state.recipes;
let index = recipes.findIndex(x => x.id === id);
recipes.splice(index,1,recipe);
this.setState({recipes: recipes});
}
render() {
return (
<div className="App">
<RecipeList recipes={this.state.recipes} onDelete={this.handleDeleteRecipe.bind(this)} onEdit={this.handleEditRecipe.bind(this)}/>
<AddRecipe addRecipe={this.handleAddRecipe.bind(this)}/>
</div>
);
}
}
export default App;
RecipeList.js
import React, { Component } from 'react';
import Collapsible from 'react-collapsible';
import RecipeItem from '../RecipeItem/RecipeItem'
import './RecipeList.css';
class RecipeList extends Component{
deleteRecipe(id){
this.props.onDelete(id);
}
editRecipe(id, recipe){
this.props.onEdit(id, recipe);
}
render(){
let recipeItem;
if(this.props.recipes){
recipeItem=this.props.recipes.map(recipe => {
return(
<RecipeItem onEdit={this.editRecipe.bind(this)} onDelete={this.deleteRecipe.bind(this)} key={recipe.id} recipe={recipe} />
)
});
}
return(
<div className="recipeList box">
{recipeItem}
</div>
)
}
}
export default RecipeList;
RecipeItem.js
import React, { Component } from 'react';
import Collapsible from 'react-collapsible';
import EditRecipe from '../EditRecipe/EditRecipe';
class RecipeItem extends Component{
deleteRecipe(id){
this.props.onDelete(id);
}
editRecipe(id, recipe){
this.props.onEdit(id, recipe);
}
render(){
let recipe=this.props.recipe
let foodName=recipe.food;
let ingredientItem;
if(recipe.ingredients){
ingredientItem=recipe.ingredients.map(ingredient=>{
return(
<a className="panel-block">
{ingredient}
</a>
)
})
}
return(
<ul>
<li className="Recipe">
<Collapsible trigger={foodName} transitionTime="200" easing="ease-in-out">
<nav className="panel">
<p className="panel-heading">
Ingredients
</p>
{ingredientItem}
<div className="panel-block">
<button className="button is-warning is-outlined" onClick={this.deleteRecipe.bind(this, this.props.recipe.id)}>
Delete
</button>
<EditRecipe recipe={this.props.recipe} editRecipe={this.editRecipe.bind(this, this.props.recipe.id, recipe)}/>
</div>
</nav>
</Collapsible>
</li>
</ul>
);
}
}
export default RecipeItem;
EditRecipe.js
import React, { Component } from 'react';
import RecipeForm from '../RecipeForm/RecipeForm';
// import './EditRecipe.css';
import Modal from 'react-modal';
import uuid from 'uuid';
// import Modal from 'boron/DropModal';
// import './RecipeList.css';
class RecipeEdit extends Component{
constructor(props){
super(props);
this.state = {
revisedRecipe:{
id: this.props.recipe.id,
food: this.props.recipe.food,
ingredients: this.props.recipe.ingredients
},
modalIsOpen: false,
speed: 100
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal(){
this.setState({modalIsOpen: true});
}
closeModal(){
this.setState({modalIsOpen: false});
}
handleSubmit(e){
const revised = this.state.revisedRecipe;
this.props.editRecipe(revised);
e.preventDefault();
}
handleNameChange(e){
this.setState({revisedRecipe:{
food: e.target.value
}
});
}
handleIndChange(e){
this.setState({revisedRecipe:{
ingredients: e.target.value
}
});
}
render(){
const speed = this.state.speed;
let recipe=this.props.recipe;
let foodName=this.state.revisedRecipe.food;
let ingredients=recipe.ingredients;
return(
<div>
<button className="button is-primary" onClick={this.openModal}>Edit Recipe</button>
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
closeTimeoutMS={speed}
contentLabel="Example Modal"
>
<div className="field">
<h2 className="title is-2">Edit Recipe</h2>
<form>
<label className="label">Recipe</label>
<div className="control">
<input className="input" type="text" placeholder="Recipe Name" ref="recipeName" value={this.state.revisedRecipe.food} onChange={this.handleNameChange.bind(this)}/>
</div>
<div className="field">
<label className="label">Ingredients</label>
<div className="control has-icons-left has-icons-right">
<input className="input" type="text" placeholder="Enter ingredients. (if more than 1 ingredient, separate them with commas)" ref="ingredients" value={this.state.revisedRecipe.ingredients} onChange={this.handleIndChange.bind(this)}/>
<span className="icon is-small is-left">
<i className="fa fa-flask"></i>
</span>
</div>
</div>
<div className="field is-grouped">
<div className="control">
<button className="button is-primary" onClick={this.closeModal}>Edit Recipe</button>
</div>
<div className="control">
<button className="button" onClick={this.closeModal}>Cancel</button>
</div>
</div>
</form>
</div>
</Modal>
</div>
);
}
}
export default RecipeEdit;
I believe you're actually getting an error when trying to re-render after updating a list. The ingredients property in the recipes are an array (as shown in getRecipes()) but you're setting the new state of ingredients (in EditRecipe) as a string: "egg,flour,salt,water" and then trying to render the ingredients as an array: ingredients.map().
When you render an input field with an array <input value={["egg", "flour"]} /> it does show the values separated by comma, but the event.target.value in onChange is actually a string.
In EditRecipe's, handleIndChange could be fixed with:
this.setState({revisedRecipe: {ingredients: e.target.value.split(',')}});
This does have another problem, though in that you are overriding the revisedRecipe completely. So all of the setState calls should be something like:
const recipe = this.state.revisedRecipe;
recipe.ingredients = e.target.value.split(',');
this.setState({revisedRecipe: recipe);

Categories

Resources