Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
i am beginner for react and i have 3 questions
1)How can i prevent creating blank tasks(see attached image)
2)how to add strikethrough for checked tasks for UI?
3)why i'm getting this error
Warning: Each child in a list should have a unique "key" prop.
my react code :
import React from "react";
import "./styles.css";
let id = 0
const Todo = props => (
<li>
<input type="checkbox" checked={props.todo.checked} onChange={props.onToggle} />
<button onClick={props.onDelete}>delete</button>
<span>{props.todo.text}</span>
</li>
)
class App extends React.Component {
constructor() {
super()
this.state = {
todos: [],
}
}
addTodo() {
const text = prompt("TODO text please!")
this.setState({
todos: [
...this.state.todos,
{id: id++, text: text, checked: false},
],
})
}
removeTodo(id) {
this.setState({
todos: this.state.todos.filter(todo => todo.id !== id)
})
}
toggleTodo(id) {
this.setState({
todos: this.state.todos.map(todo => {
if (todo.id !== id) return todo
return {
id: todo.id,
text: todo.text,
checked: !todo.checked,
}
})
})
}
render() {
return (
<div>
<div>Todo count: {this.state.todos.length}</div>
<div>Unchecked todo count: {this.state.todos.filter(todo => !todo.checked).length}</div>
<button onClick={() => this.addTodo()}>Add TODO</button>
<ul>
{this.state.todos.map(todo => (
<Todo
onToggle={() => this.toggleTodo(todo.id)}
onDelete={() => this.removeTodo(todo.id)}
todo={todo}
/>
))}
</ul>
</div>
)
}
}
export default App;
result:
todo-list result image
See corrections with the comments:
import React from 'react';
let id = 0;
const Todo = (props) => (
// style based on props
<li style={{ textDecoration: props.todo.checked ? 'line-through' : ''}}>
<input
type='checkbox'
checked={props.todo.checked}
onChange={props.onToggle}
/>
<button onClick={props.onDelete}>delete</button>
<span>{props.todo.text}</span>
</li>
);
class App extends React.Component {
constructor() {
super();
this.state = {
todos: [],
};
}
addTodo() {
const text = prompt('TODO text please!');
// only do this if text has value
text && this.setState({
todos: [...this.state.todos, { id: id++, text: text, checked: false }],
});
}
removeTodo(id) {
this.setState({
todos: this.state.todos.filter((todo) => todo.id !== id),
});
}
toggleTodo(id) {
this.setState({
todos: this.state.todos.map((todo) => {
if (todo.id !== id) return todo;
return {
id: todo.id,
text: todo.text,
checked: !todo.checked,
};
}),
});
}
render() {
return (
<div>
<div>Todo count: {this.state.todos.length}</div>
<div>
Unchecked todo count:{' '}
{this.state.todos.filter((todo) => !todo.checked).length}
</div>
<button onClick={() => this.addTodo()}>Add TODO</button>
<ul>
{this.state.todos.map((todo) => (
<Todo
key={todo.id} // need a unique key using id
onToggle={() => this.toggleTodo(todo.id)}
onDelete={() => this.removeTodo(todo.id)}
todo={todo}
/>
))}
</ul>
</div>
);
}
}
export default App;
How can I prevent creating blank tasks(see attached image)
You will have to check that text is not an empty string before creating the task
addTodo() {
const text = prompt("TODO text please!")
if(text !=== ""){ // <- check here
this.setState({
todos: [
...this.state.todos,
{id: id++, text: text, checked: false},
],
})
}
}
how to add strikethrough for checked tasks for UI?
this can be done easily whit css using text-decoration: line-through;, so, create a class with that property (or you can do it inline also) and if the state of the task is done, add that class to the component
.text-strike {
text-decoration: line-through;
}
<Todo className={checked ? 'text-strike' : null}}
why i'm getting this error
Every time you loop through an array to create views, React needs something to tell all the components from each other apart so when the times comes to update one of them in the UI (re-render) it knows exactly which one it should update thus increasing performance
{this.state.todos.map(todo => (
<Todo
onToggle={() => this.toggleTodo(todo.id)}
onDelete={() => this.removeTodo(todo.id)}
todo={todo}
key={somethingUnique} // <- you need to add this key
/>
))}
in the addTodo function add a validation for empty todo like below
addTodo() {
const text = prompt("TODO text please!")
if(text === undefined || text === "" || text?.trim() === ""){
alert("You are lazy!!! enter proper value.");
}else{
this.setState({
todos: [
...this.state.todos,
{id: id++, text: text, checked: false},
],
})
}
}
for striking wrap text with <s> tag
your reacts rendering mechanism needs to differentiate elements and it uses key as a differentiator to easily update any changes made to that element in your for loop, as dom doesn't speak for loop it's react that does for you and that's sort of interface for react to understand that it has to maintain it.
Hence add a key to your todo list as below
<Todo
onToggle={() => this.toggleTodo(todo.id)}
onDelete={() => this.removeTodo(todo.id)}
todo={todo}
key = {todo.id}
/>
Related
I have defined a state variable called fullText.
fullText default value is false.
When Full Text is clicked, I want to reverse my state value and enable the text to be closed and opened. But when it is clicked, the texts in all rows in the table are opened, how can I make it row specific?
{
!this.state.fullText ?
<div onClick={() => this.setState({ fullText: !this.state.fullText })}
className="text-primary cursor-pointer font-size-14 mt-2 px-2"
key={props.data?.id}>
Full Text
</div>
:
<>
<div onClick={() => this.setState({ fullText: !this.state.fullText
})}className="text-primary cursor-pointer font-size-14 mt-2 px-2"
key={props.data?.id}>
Short Text
</div>
<span>{ props.data?.caption}</span>
</>
}
It appears that the code sample in the question is repeated for each row, but there is only 1 state fullText (showMore in the CodeSandbox) that is common for all these rows. Hence they all open or close together.
If you want individual open/close feature for each row, then you need 1 such state per row. An easy solution could be to embed this state with the data of each row:
export default class App extends Component {
state = {
data: [
{
id: 1,
description: "aaaaa",
showMore: false // Initially closed
},
// etc.
]
};
render() {
return (
<>
{this.state.data.map((e) => (
<>
{ /* Read the showMore state of the row, instead of a shared state */
e.showMore === false ? (
<div onClick={() => this.setState({ data: changeShow(this.state.data, e.id, true) })}>
Show description{" "}
</div>
) : (
<>
<div onClick={() => this.setState({ data: changeShow(this.state.data, e.id, false) })}>
Show less{" "}
</div>
<span key={e.id}>{e.description} </span>
</>
)}
</>
))}
</>
);
}
}
/**
* Update the showMore property of the
* row with the given id.
*/
function changeShow(data, id, show) {
for (const row of data) {
if (row.id === id) { // Find the matching row id
row.showMore = show; // Update the property
}
}
return data;
}
there are two specific desired outputs you might want. you can either make it open only one row at a time. you can also make them individual row specific to open or close independently.
For making specific row behave on its own you can store a list of ids in state for the expanded view and show the relevant component or behaviour.
class RowSpecific extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIds: [],
data: [
{ id: "1", otherInfo: {} },
{ id: "2", otherInfo: {} },
{ id: "3", otherInfo: {} },
],
};
this.handleExpandHide = this.handleExpandHide.bind(this);
}
handleExpandHide(expand, id) {
if (!id) return;
let sIds = [...this.state.selectedIds];
if (expand) sIds.push(id);
else sIds.remove((rId) => rId === id);
this.setState({
selectedIds: sIds,
});
}
render() {
let list = this.state.data.map((data) => {
let show = this.state.selectedIds.find((id) => data.id === id);
return show ? (
<ExpandedView
data={data}
onExpandHide={(e) => {
this.handleExpandHide(true, data.id);
}}
/>
) : (
<CollapseView
data={data}
onExpandHide={(e) => {
this.handleExpandHide(true, data.id);
}}
/>
);
});
return list;
}
}
For making it open only one at a time you can save the clicked id in state and then when showing it only the matched id in state should be shown as open . rest are closed.
class SingleRow extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedId: "2",
data: [
{ id: "1", otherInfo: {} },
{ id: "2", otherInfo: {} },
{ id: "3", otherInfo: {} },
],
};
}
render() {
let list = this.state.data.map((data) => {
let show = data.id === this.state.selectedId;
return show ? <ExpandedView data={data} /> : <CollapseView data={data} />;
});
return list;
}
}
I have created a Todo List app successfully to some extent.
My constructor function of TodoList is as follows:
class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = { value: '',
todoList: [{ id: 1, content: "Call Client" },
{ id: 2, content: "Write Log" }] }
this.onChangeValue = this.onChangeValue.bind(this);
this.onAddItem = this.onAddItem.bind(this);
}
The remaining body of TodoList has Add Item functionality, by using two methods onChangeValue and onAddItem
onChangeValue = event => {
this.setState({ value: event.target.value });
};
onAddItem = () => {
if (this.state.value !== '') {
this.setState(state => {
const todoList = state.todoList.concat({ id: state.todoList.length + 1, content: this.state.value});
return {
todoList,
value: '',
};
});
}
};
render() {
const listItems = this.state.todoList.map((todo) =>
<ListItem key={todo.id} id={todo.id} content={todo.content}/>
)
return <>
<ul className="todo-list">
{listItems}
</ul>
{/* <AddItem/> */}
<div className="add-item">
<input type="text" onChange={this.onChangeValue}/>
<button type="submit" onClick={this.onAddItem}>Add Item</button>
</div>
</>
}
}
Delete functionality and Mark as read functionality are created in the ListItem component using methods handleChange and handleDeleteClick.
class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = { done : false, editing : '', deleted : false }
this.handleChange = this.handleChange.bind(this);
this.handleDeleteClick = this.handleDeleteClick.bind(this);
}
handleChange(event) {
this.setState({done: event.target.checked})
}
handleDeleteClick() {
this.setState({ deleted : true })
}
render() {
if (this.state.deleted === false) {
return <li className="list-item">
{/* special class is added to the paragraph to strike the text when marked as done */}
<p className={this.state.done ? 'done' : ''}>{this.props.content}</p>
<ul className="actions">
<li>
<label htmlFor={'item_' + this.props.id}>Mark as done</label>
<input name={'item_' + this.props.id} id={'item_' + this.props.id} type="checkbox" onChange={this.handleChange}/>
</li>
<li>
{/* Edit button is disabled once the task is marked as done */}
{ this.state.done ? <button type="button" disabled>Edit</button> : <button type="button">Edit</button> }
</li>
<li><button type="button" onClick={this.handleDeleteClick}>Delete</button></li>
</ul>
</li>
}
else {
return null;
}
}
}
Now the only thing remaining is the edit functionality which I cannot figure out if it's possible or not.
The source code of my application can be found in this codepen:
https://codepen.io/blenderous/pen/rNeywyZ
We could create an onEditItem method inside the TodoList item and pass this method to each ListItem. This method would receive an id and a newContent values to process the updates.
// TodoList component
...
onEditItem = (id, newContent) => {
const newTodoList = this.state.todoList.map((todo) => {
// return todo.id !== id ? todo : { ...todo, content: newContent }
// not same id? leave as is
if (todo.id !== id) {
return todo;
}
// update content with the newContent value
return { ...todo, content: newContent };
});
this.setState({ todoList: newTodoList });
};
Then on our ListItem, we'll create an handleEditClick method that will handle the click event for our edit button.
// ListItem component
...
handleEditClick() {
const { id, content } = this.props;
// prompt to edit the current content
const newContent = prompt("Edit:", content);
// call the TodoList editTodo passing the id and the new content
// of the current todo
this.props.editTodo(id, newContent);
}
Now we'll use this method on our edit button like so
...
<button
type="button"
disabled={this.state.done} // disabled once the task is marked as done
onClick={this.handleEditClick}
>
Edit
</button>
I'm working on my First project with React, I have an App and a ToDo. I am defining a deleteToDo method and I want the method to call this.setState() and pass it a new array that doesn't have the to-do item being deleted with the use of the .filter() array method. I don't want to alter the code to much or introduce more complexity. In essence I would like to keep it as straight forward as possible. I am still a beginner with React so this has been a big learning process. I feel that I am close.
This is the main app
import React, { Component } from 'react';
import './App.css';
import ToDo from './components/ToDo.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ description: 'Walk the cat', isCompleted: true },
{ description: 'Throw the dishes away', isCompleted: false },
{ description: 'Buy new dishes', isCompleted: false }
],
newTodoDescription: ''
};
}
deleteToDo(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.deleteToDo = this.state.filter(index);
this.setState({ todos: todos });
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { description: this.state.newTodoDescription, isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo], newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
render() {
return (
<div className="App">
<ul>
{ this.state.todos.map( (todo, index) =>
<ToDo key={ index } description={ todo.description } isCompleted={ todo.isCompleted } toggleComplete={ this.toggleComplete } deleteToDo={ this.deleteToDo } />
)}
</ul>
<form onSubmit={ (e) => this.handleSubmit(e) }>
<input type="text" value={ this.state.newTodoDescription } onChange={ (e) => this.handleChange(e) } />
<input type="submit" />
</form>
</div>
);
}
}
export default App;
And this the ToDo aspect
import React, { Component } from 'react';
class ToDo extends Component {
render() {
return (
<li>
<button type="button" onClick={ this.props.deleteTodo} > delete </button>
<input type="checkbox" checked={ this.props.isCompleted } onChange={ this.props.toggleComplete } />
<span>{ this.props.description }</span>
</li>
);
}
}
export default ToDo;
You slice and array without the index, that's may be why your delete not work
deleteToDo(index) {
const todos = this.state.todos.slice(index, 1);
this.setState({ todos: todos });
}
1) You need to bind your deleteToDo method in the constructor
this.deleteToDo = this.deleteToDo.bind(this);
2) You need to set a new property on the component that is the same as its index.
<ToDo
key={index}
id={index}
description={ todo.description }
// ...
/>
3) Then you can pass that index as the argument to deleteToDo (making sure you spell the method name correctly).
<button
type="button"
onClick={() => this.props.deleteToDo(this.props.index)}
>Delete
</button>
4) Finally, you can strip down your deleteToDo method to the following:
deleteToDo(index) {
// Return a new array that doesn't
// have a row with a matching index
const todos = this.state.todos.filter((el, i) => i !== index);
this.setState({ todos });
}
Here's a working version.
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
})
}
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
});
}