React state keep old state - javascript

I have some issue in React that seems to keep last or old state.
I have a parent component called MyLists.js that contain a loop function where I rendered child component called Item.js
{
this.state.listProducts.map(d =>
<Item data={d} />
)}
And in my Item.js component I set state in constructor :
this.state = { showFullDescription: false }
The variable "showFullDescription" allows me to see the entire description of a product. Now I have for example 2 products and all states "showFullDescription" are set to false so :
Product 1 => (showFullDescription = false)
Product 2 => (showFullDescription = false)
Next, I show full description for Product 2 by clicking a button and I set state to true so Product 2 => (showFullDescription = true)
The problem is when I add another product, let's call it "Product 3", the full description of "Product 3" is directly shown and for "Product 2" it is hidden. It seems that last state is reflected on "Product 3".
I am really sorry for my english, it's not my native language
Here is full source code :
MyLists.js
import React, { Component } from 'react';
import ProductService from '../../../../services/ProductService';
import Item from './Item';
class MyLists extends Component {
constructor(props) {
super(props);
this.state = {
products: []
}
this.productService = new ProductService();
this.productService.getAllProducts().then((res) => {
this.setState({
products: res
})
});
}
addProduct(data){
this.productService.addProduct(data).then((res) => {
var arr = this.state.products;
arr.push(res);
this.setState({
products: arr
})
})
}
render() {
return (
<div>
{
this.state.products.map(d =>
<Item data={d} />
)}
</div>
)
}
}
export default MyLists;
Item.js
import React, { Component } from 'react';
import Truncate from 'react-truncate';
class Item extends Component {
constructor(props) {
super(props);
this.state = {
showFullDescription: false
}
}
render() {
return (
<div>
<h2>{this.props.data.title}</h2>
{
!this.state.showFullDescription &&
<Truncate lines={10} ellipsis={<a className="btn btn-primary read-more-btn" onClick={() => this.setState({showFullDescription: true})}>Show More</a>}>
{this.props.data.description}
</Truncate>
)}
{
this.state.showFullDescription &&
<span>
{this.props.data.description}
</span>
}
</div>
)
}
}
export default Item;

You have some syntax problems and missing && for !this.state.showFullDescription.
I've slightly changed the component and use ternary operator to render conditionally. It is a little bit ugly right now, the logic can be written outside of the render. Also, I suggest you to use a linter if you are not using.
MyLists.js
class MyLists extends React.Component {
state = {
products: [
{ id: 1, description: "Foooooooooooooooooooooooooooooooooooooooooo", title: "first" },
{ id: 2, description: "Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrrrrrrrr", title: "second" },
{ id: 3, description: "Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz", title: "third" },
]
}
render() {
return (
<div>
{
this.state.products.map(d =>
<Item data={d} />
)}
</div>
)
}
}
Item.js
class Item extends React.Component {
state = {
showFullDescription: false,
}
render() {
return (
<div>
<h2>{this.props.data.title}</h2>
{ !this.state.showFullDescription
?
<Truncate lines={3} ellipsis={<span>... <button onClick={() => this.setState({showFullDescription: true})}>Read More</button></span>}>
{this.props.data.description}
</Truncate>
:
<span>
{this.props.data.description}
</span>
}
</div>
)
}
}
Here is working sandbox: https://codesandbox.io/s/x24r7k3r9p

You should try mapping in the second component as:
class Item extends React.Component {
state = {
showFullDescription: false,
}
render() {
return (
<div>
{this.props..data.map(data=>
<h2>{this.props.data.title}</h2>
{ !this.state.showFullDescription
?
<Truncate lines={3} ellipsis={<span>... <button onClick={() =>
this.setState({showFullDescription: true})}>Read More</button>
</span>}>
{this.props.data.description}
</Truncate>
:
<span>
{this.props.data.description}
</span>)}
}
</div>
)
}
}

You should have a 'key' property (with unique value) in 'Item' - No warnings about it in console?

Related

Re-Render From Child Component After State Change

So what I want is, whenever I click on any list item the item should get struck. I've changed the state but I'm not sure about how to re-render this with new state. Can anyone please let me know how to do this?
Please don't mind my mistakes, I'm a newbie to this and also suggest me how to do this in better way if needed.
Child Components
class TodoList extends Component {
render() {
return (
<ul>
{this.props.items.map(thing => (
<List
key={thing.id}
item={thing}
items={this.props.items}
listid={thing.id}
/>
))}
</ul>
);
}
}
class List extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
render() {
return <li onClick={this.handleClick}>{this.props.item.item}</li>;
}
handleClick(event) {
const items = this.props.items;
items[this.props.listid] = {
item: "<strike>" + event.target.value + "</strike>",
id: this.props.listid
};
console.log(items);
this.setState({
items
});
}
}
You need to keep todos state in a parent component, and allow to state change from the inner components.
For example:
App.js (here we keep todos in state, and pass toggle function, and todo items as props to child components:
import React, { Component } from "react";
import TodoList from "./TodoList";
class App extends Component {
state = {
todos: [
{
id: 1,
text: "todo 1",
completed: false
},
{
id: 2,
text: "todo 2",
completed: true
},
{
id: 3,
text: "todo 3",
completed: true
}
]
};
toggle = id => {
const updatedTodos = this.state.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
};
}
return todo;
});
this.setState({
...this.state,
todos: updatedTodos
});
};
render() {
return <TodoList items={this.state.todos} toggle={this.toggle} />;
}
}
export default App;
TodoList.js
import React, { Component } from "react";
import Todo from "./Todo";
class TodoList extends Component {
render() {
return (
<ul>
{this.props.items.map(todo => (
<Todo key={todo.id} item={todo} toggle={this.props.toggle} />
))}
</ul>
);
}
}
export default TodoList;
Todo.js (here we call toggle function when handleClick, and pass the id of the todo)
import React, { Component } from "react";
class Todo extends Component {
handleClick = id => {
this.props.toggle(id);
};
render() {
const { id, text, completed } = this.props.item;
return (
<li onClick={() => this.handleClick(id)}>
{completed ? <strike>{text}</strike> : text}
</li>
);
}
}
export default Todo;
here is codesandbox:
https://codesandbox.io/s/polished-cloud-hmdw0

"Cannot read property 'map' of undefined" within React, what's wrong here?

Trying to get my head around props so forgive me if its a silly mistake. I am trying to pass all of my data into one variable and pass that out into props (using {item.text} and {item.key}), however, my ".map" isn't picking up anything and there's a bunch of errors, what's wrong with my code?
The problem lays specifically here in this block of code
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
Here is the code in full
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList(input) {
let listArray = this.state.list;
listArray.push(input);
var newItem = {
text: listArray,
key: Date.now()
};
this.setState(prevState => {
return {
list: prevState.list.concat(newItem)
};
});
this.setState({
list: listArray
})
}
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList(this.state.userInput)}>Press me</button>
<ul>
{this.testingSetup()}
</ul>
</div>
);
}
}
export default TodoListt;
You can use the spread operator to add to an existing array. Simply add a new object to the array in the state, and then clear the user input, ready for another item. Based on your code, here's a simple example of adding to a state list (haven't run myself, so just check for syntax errors and such):
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoList extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
const { list, userInput } = this.state;
// Add item to state list using spread operator and clear input
this.setState({
list: [...list, {text:userInput, key: Date.now()}],
userInput: ""
});
}
render() {
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList()}>Press me</button>
<hr/>
{/* For each item in the list, render the contents */}
{this.state.list.map(item => (
<div key={item.key}>
<h3>{item.text}</h3>
<p>Time: {item.key}</p>
</div>
))}
</div>
);
}
}
export default TodoList;

Don't know how to handle input in my project

I'm working under the to-do list project and have got a problem with adding a comment to my list item. This is what I have right now:
App flow
After adding a list item you should be able to click in this item and a new window with comments will appear. In the comments section, comments should be added with Ctrl+Enter combination.
I've got a problem with adding comments to the list item (they should be added to the "comments" array of a particular list item).
Could you please explain what I'm doing wrong and why my comments aren't adding.
UPDATE: I've updated my code but the following mistake appears when I press Ctr+Enter to add a comment: [Error] (http://joxi.ru/Q2KR1G3U4lZJYm)
I've tried to bind the addItem method but no result. What's wrong with the addItem method?
Here is my main component:
App.js
import React, { Component } from 'react';
import './App.css';
import ListInput from './components/listInput'
import ListItem from './components/listItem'
import SideBar from './components/sideBar'
import CommentsSection from './components/commentsSection'
class App extends Component {
constructor(props){
super(props);
this.state = {
items: [
{
id: 0,
text: 'First item',
commentsCount: 0,
comments: [],
displayComment: false
},
{
id: 1,
text: 'Second item',
commentsCount: 0,
comments: [],
displayComment: false
},
{
id: 2,
text: 'Third item',
commentsCount: 0,
comments: [
'Very first comment',
'Second comment',
],
displayComment: false
},
],
nextId: 3,
activeComment: [],
}
}
// Add new item to the list
addItem = inputText => {
let itemsCopy = this.state.items.slice();
itemsCopy.push({id: this.state.nextId, text: inputText});
this.setState({
items: itemsCopy,
nextId: this.state.nextId + 1
})
}
// Remove the item from the list: check if the clicked button id is match
removeItem = id =>
this.setState({
items: this.state.items.filter((item, index) => item.id !== id)
})
setActiveComment = (id) => this.setState({ activeComment: this.state.items[id] });
addComment = (inputComment, activeCommentId ) => {
// find item with id passed and select its comments array
let commentCopy = this.state.items.find(item => item.id === activeCommentId)['comments']
commentCopy.push({comments: inputComment})
this.setState({
comments: commentCopy
})
}
render() {
return (
<div className='App'>
<SideBar />
<div className='flex-container'>
<div className='list-wrapper'>
<h1>Items</h1>
<ListInput inputText='' addItem={this.addItem}/>
<ul>
{
this.state.items.map((item) => {
return <ListItem item={item} key={item.id} id={item.id} removeItem={this.removeItem} setActiveComment={() => this.setActiveComment(item.id)}/>
})
}
</ul>
</div>
<CommentsSection inputComment='' items={this.state.activeComment}/>
</div>
</div>
);
}
}
export default App;
and my Comments Section component:
commentsSection.js
import React from 'react';
import './commentsSection.css';
import CommentInput from './commentInput'
import CommentsItem from './commentsItem'
export default class CommentsSection extends React.Component {
constructor(props){
super(props);
this.state = {value: this.props.inputComment};
this.handleChange = this.handleChange.bind(this);
this.handleEnter = this.handleEnter.bind(this);
this.addComment = this.addComment.bind(this)
}
handleChange = event => this.setState({value: event.target.value})
handleEnter(event) {
if (event.charCode === 13 && event.ctrlKey) {
this.addComment(this.state.value)
}
}
addComment(comment) {
// Ensure the todo text isn't empty
if (comment.length > 0) {
this.props.addComment(comment, this.props.activeComment);
this.setState({value: ''});
}
}
render() {
return (
<div className='component-section'>
<h1>{this.props.items.text}</h1>
<ul>
{ this.props.items.comments &&
this.props.items.comments.map((comment, index) => <p key={index}>{comment}</p>)
}
</ul>
<CommentsItem />
{/*<CommentInput />*/}
<div className='comment-input'>
<input type='text' value={this.state.value} onChange={this.handleChange} onKeyPress={this.handleEnter}/>
</div>
</div>
)
}
}
Change your CommentSection component addComment method and handleEnter method
addComment(comment) {
// Ensure the todo text isn't empty
if (comment.length > 0) {
// pass another argument to this.props.addComment
// looking at your code pass "this.props.items"
this.props.addComment(comment, this.props.items.id);
this.setState({value: ''});
}
}
handleEnter(event) {
if (event.charCode === 13 && event.ctrlKey) {
// passing component state value property as new comment
this.addComment(this.state.value)
}
}
Change your App Component addComment method
addComment = (inputComment, activeCommentId )=> {
// find item with id passed and select its comments array
let commentCopy = this.state.items.find(item => item.id === activeCommentId)['comments']
// if you want to push comments as object
// but looking at your code this is not you want
// commentCopy.push({comments: inputComment})
// if you want to push comments as string
commentCopy.push( inputComment)
this.setState({
comments: commentCopy
})
}

Show and Hide specific component in React from a loop

I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}

calling grandparent method from grandchild functional component in react

I'm trying to call a simple method from the grandparent component in my child component but from some reason I can't , I tried every possible way but I think I'm missing something
here's the full code :
import React, { Component } from 'react';
import './App.css';
var todos = [
{
title: "Example2",
completed: true
}
]
const TodoItem = (props) => {
return (
<li
className={props.completed ? "completed" : "uncompleted"}
key={props.index} onClick={props.handleChangeStatus}
>
{props.title}
</li>
);
}
class TodoList extends Component {
constructor(props) {
super(props);
}
render () {
return (
<ul>
{this.props.todosItems.map((item , index) => (
<TodoItem key={index} {...item} {...this.props} handleChangeStatus={this.props.handleChangeStatus} />
))}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos ,
text :""
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeStatus = this.handleChangeStatus(this);
}
handleTextChange(e) {
this.setState({
text: e.target.value
});
}
handleChangeStatus(){
console.log("hello");
}
handleSubmit(e) {
e.preventDefault();
const newItem = {
title : this.state.text ,
completed : false
}
this.setState((prevState) => ({
todos : prevState.todos.concat(newItem),
text : ""
}))
}
render() {
return (
<div className="App">
<h1>Todos </h1>
<div>
<form onSubmit={this.handleSubmit}>
< input type="text" onChange={this.handleTextChange} value={this.state.text}/>
</form>
</div>
<div>
<TodoList handleChangeStatus={this.handleChangeStatus} todosItems={this.state.todos} />
</div>
<button type="button">asdsadas</button>
</div>
);
}
}
export default App;
The method im trying to use is handleChangeStatus() from the App component in the TodoItem component
Thank you all for your help
This line is wrong:
this.handleChangeStatus = this.handleChangeStatus(this);
//Change to this and it works
this.handleChangeStatus = this.handleChangeStatus.bind(this);

Categories

Resources