export default class Home extends Component {
state = {
items : [ { text: 'Buy grocery', done: true},
{ text: 'Play guitar', done: false},
{ text: 'Romantic dinner', done: false}
]
}
onItemClick = () =>{
this.setState(
prevState => ({
items: prevState.items.map(
el => el.key === key? { ...el, done: true }: el
)
})
)
}
render() {
return (
<Fragment>
<h1>
Home Page
</h1>
<TodoList items={this.state.items} clickAction={this.onItemClick} />
</Fragment>
)
}
export default class TodoList extends Component {
render() {
const itemlist = this.props.items.map((item, index) =>{
return <div key={index}>
<h4 onClick={this.props.clickAction}> {item.text + " " + item.done}</h4> </div>
})
return (
<Fragment>
<h5>
TodoList Page
</h5>
<section>
{itemlist}
</section>
</Fragment>
)
}
}
I want to update a single object property inside the array. onItemClick function, which should be called when user clicks an item in the list, if the item is marked "false" as done. Otherwise the onItemClick should not be called and the click event itself should not be propagated further.
getting error
'key' is not defined no-undef
When you are doing this:
el => el.key === key? { ...el, done: true }: el
you comapre el.key to key but the variable key is never defined
Related
I set button onClick as a parameter in the child component and drag and use onClick in the parent component.
The child component Room .
type Props = {
items?: [];
onClick?: any;
}
const Room = ({ onClick, items: [] }: Props) => {
return (
<div>
{items.length ? (
<>
{items.map((item: any, index: number) => {
return (
<>
<button key={index} onClick={() => { console.log('hi'); onClick }}>{item.name}</button>
</>
)
}
</>
)
</div>
)
}
This is the parent component.
const LoadingRoom = () => {
const handleWaitingRoomMove = (e: any) => {
e.preventDefault();
console.log('Hello Move')
}
return (
<>
<Room
items={[
{
name: "Waiting Room",
onClick: {handleWaitingRoomMove}
},
{
name: "Host Room",
},
]}
>
</Room>
</>
)
}
I want to call parent component's onClick handleWaitingRoomMove but it's not getting called.
However, console.log('hi') on the button is called normally whenever the button is clicked. Only button is not called. Do you know why?
onlick is a attribute to the child element. so move it outside of the items array
<Room
onClick={handleWaitingRoomMove}
items={[
{
name: "Waiting Room",
},
{
name: "Host Room",
},
]}
>
In the child, missing () for onClick
onClick={(ev) => { console.log('hi'); onClick(ev) }}
Demo
You are passing onClick in items, not direct props
<button key={index} onClick={item.onClick}>{item.name}</button>
so your component will be
type Props = {
items?: [];
}
const Room = ({ items: [] }: Props) => {
return (
<div>
{items.length ? (
<>
{items.map((item: any, index: number) => {
return (
<>
<button key={index} onClick={item.onClick}>{item.name}</button>
</>
)
}
</>
)
</div>
)
}
It would probably be more advantageous to have one handler that does the work, and use that to identify each room by type (using a data attribute to identify the rooms). That way you keep your data and your component logic separate from each other. If you need to add in other functions at a later stage you can.
const { useState } = React;
function Example({ data }) {
// Handle the room type by checking the value
// of the `type` attribute
function handleClick(e) {
const { type } = e.target.dataset;
switch (type) {
case 'waiting': console.log('Waiting room'); break;
case 'host': console.log('Host Room'); break;
case 'guest': console.log('Guest Room'); break;
default: console.log('Unknown room'); break;
}
}
// Keep the room data and the handler separate
return (
<div>
{data.map(obj => {
return (
<Room
key={obj.id}
data={obj}
handleClick={handleClick}
/>
);
})}
</div>
);
}
// Apply a data attribute to the element
// you're clicking on, and just call the handler
// `onClick`
function Room({ data, handleClick }) {
const { type, name } = data;
return (
<button
data-type={type}
onClick={handleClick}
>{name}
</button>
);
}
const data = [
{ id: 1, type: 'waiting', name: 'Waiting Room' },
{ id: 2, type: 'host', name: 'Host Room' },
{ id: 3, type: 'guest', name: 'Guest Room' }
];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
button:not(:last-child) { margin-right: 0.25em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
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}
/>
So I am a beginner in react. I was messing around with a to-do list and encountered a state related problem.
//main todolist
import React from 'react'
import './todolist.css'
import Itemtodo from './Itemtodo'
class Todolist extends React.Component{
constructor(){
super()
this.state = {
todos: [
{
id:1,
text: 'html',
completed: true
},
{
id:2,
text: 'css',
completed: true
},
{
id:3,
text: 'js',
completed: false
},
{
id:4,
text: 'react',
completed: false
},
{
id:5,
text: 'review',
completed: false
}
]
}
}
// i think it came from this method
ev = (id) =>{
this.setState((prev) =>{
const newarr = prev.todos.map((data) =>{
if(data.id === id){
data.completed = !data.completed
}
return data
})
return {
todos:newarr
}
})
}
render(){
const array = this.state.todos.map(data => <Itemtodo key={data.id} todo={data.text} ic={data.completed} ev={this.ev} id={data.id}/>)
return (
<div className="todolist">
{array}
</div>
)
}
}
export default Todolist
// item todo
import React, { Component } from 'react'
import './itemtodo.css'
export class Itemtodo extends Component {
render() {
return (
<div className ='itemtodo'>
<input type='checkbox' checked={this.props.ic} onChange={() => this.props.ev(this.props.id)}/>
<p>{this.props.todo}</p>
</div>
)
}
}
export default Itemtodo
I REALLY think that the problem was from "main todolist" because if I changed the "ev" method to do "checked every checkbox with a click" like this, it worked
// i think it came from this method
ev = (id) =>{
this.setState((prev) =>{
const newarr = prev.todos.map((data) =>{
data.completed = true
return data
})
return {
todos:newarr
}
}) //set state ends
}
I did some experiment by console loging the "newar" and it did not change. So I think it's because of the
data.completed = !data.completed
did not work please help me! Thank you
Hi Please check this example. It is working fine.
Todolist Component
import React, {Component} from 'react'
class Todolist extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{id: 1, text: 'html', completed: true},
{id: 2, text: 'css', completed: true},
{id: 3, text: 'js', completed: false},
{id: 4, text: 'react', completed: false},
{id: 5, text: 'review', completed: false}
]
}
}
// i think it came from this method
ev = (id, changedValue) => {
this.setState((prev) => {
let item = prev.todos.filter((data) => data.id === id)[0];
item.completed = changedValue;
return{
todos: prev.todos
};
})
};
render() {
const array = this.state.todos.map(data => <Itemtodo key={data.id} todo={data.text} ic={data.completed}
ev={this.ev} id={data.id}/>);
return (
<div className="todolist">
{array}
</div>
)
}
}
export default Todolist
Itemtodo Component
class Itemtodo extends Component {
render() {
return (
<div className='itemtodo'>
<input type='checkbox' checked={this.props.ic} onChange={() => this.props.ev(this.props.id, !this.props.ic)}/>
{this.props.todo}
</div>
)
}
}
Here is a working example of a todo list that has TodoItem as a pure component, because toggleCompleted will not mutate this will work (you have to click on the todo to toggle completed).
//using React.memo will make TodoItem a pure
// component and won't re render if props didn't change
const TodoItem = React.memo(function TodoItem({
todo: { id, name, completed },
toggleCompleted,
}) {
const r = React.useRef(0);
r.current++;
return (
<li
onClick={toggleCompleted(id)}
style={{ cursor: 'pointer' }}
>
rendered:{r.current}, {name}, completed:
{completed.toString()}
</li>
);
});
function TodoList({ todos, toggleCompleted }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
/>
))}
</ul>
);
}
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, name: '1', completed: false },
{ id: 2, name: '2', completed: false },
]);
//use callback so the handler never changes
const toggleCompleted = React.useCallback(
(id) => () =>
setTodos((todos) =>
//map todos into a new array of todos
todos.map(
(todo) =>
todo.id === id
? //shallow copy todo and toggle completed
{ ...todo, completed: !todo.completed }
: todo //just return the todo
)
),
[]
);
return (
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
/>
);
}
ReactDOM.render(<App />, document.getElementById('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="root"></div>
And here is a broken example where I will mutate the todo, even though you changed the data you did not change the reference of the todo item so React won't re render pure TodoItem component.
//using React.memo will make TodoItem a pure
// component and won't re render if props didn't change
const TodoItem = React.memo(function TodoItem({
todo: { id, name, completed },
toggleCompleted,
}) {
const r = React.useRef(0);
r.current++;
return (
<li
onClick={toggleCompleted(id)}
style={{ cursor: 'pointer' }}
>
rendered:{r.current}, {name}, completed:
{completed.toString()}
</li>
);
});
function TodoList({ todos, toggleCompleted }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
/>
))}
</ul>
);
}
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, name: '1', completed: false },
{ id: 2, name: '2', completed: false },
]);
//use callback so the handler never changes
const toggleCompleted = React.useCallback(
(id) => () =>
setTodos((todos) =>
//map todos into a new array of todos
todos.map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
console.log('changed todo:', todo);
}
//it is always returning the same todo that it
// got passed into, only mutates the one that
// needs to be toggled but that todo is still the
// same
return todo;
})
),
[]
);
return (
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
/>
);
}
ReactDOM.render(<App />, document.getElementById('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="root"></div>
I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!
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
});
}