onClick detection for both an li and button nested within li - javascript

so i'm trying to implement the line-through feature while having a delete button. clicking on the li text crosses out the item, and clicking on the del button removes it.
functionally, it works. the issue is when I delete an itme, say "2", it will apply the line-through style to the list item below it. i'm guessing this is because "onClick" is detected twice - both inside the list item and the button (because the button is technically nested within the list item). the moment I press on the DEL button for 2, the onClick is detected for list item 3, applying the line-through style. what would be the best way to go about correcting this?
my code with an App component and ListItem component:
import React, { useState } from "react";
import ListItem from "./ListItem";
function App() {
const [inputText, setInputText] = useState("");
const [items, setItems] = useState([]);
function handleChange(event) {
const newValue = event.target.value;
setInputText(newValue);
}
function addItem() {
setItems((prevItems) => {
return [...prevItems, inputText];
});
setInputText("");
}
function deleteItem(id) {
setItems((prevItems) => {
return prevItems.filter((item, index) => {
return index !== id;
});
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input onChange={handleChange} type="text" value={inputText} />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{items.map((todoItem, index) => (
<ListItem
key={index}
id={index}
item={todoItem}
delete={deleteItem}
/>
))}
</ul>
</div>
</div>
);
}
export default App;
____________________________________________________________________________________________________
import React, { useState } from "react";
function ListItem(props) {
const [clickedOn, setClickedOn] = useState(false);
function handleClick() {
setClickedOn((prevValue) => {
return !prevValue;
});
}
return (
<div>
<li
onClick={handleClick}
style={{ textDecoration: clickedOn ? "line-through" : "none" }}
>
{props.item}
<button
onClick={() => {
props.delete(props.id);
}}
style={{ float: "right" }}
>
<span>Del</span>
</button>
</li>
</div>
);
}
export default ListItem;

As you already wrote, user events are propagated up the DOM tree. To stop the propagation, you can use event.stopPropagation() ref in your event handler
<button
onClick={(event) => {
event.stopPropagation();
props.delete(props.id);
}}
style={{ float: "right" }}
>

Related

onchange event should trigger at the time of onclick event in react

in that component I need to print onenumber as a text in the textbox in a way like I am selecting one from dropdown and typing number , so at a time we are able to type text in a text box and selecting text from the dropdown ,and in on change function triggered all time like if you type text and select the dropdown as well ,so what should I changed in the on Change event?
import { useState } from "react";
export default function App() {
const [show, setShow] = useState(true);
const [val, setVal] = useState("");
function handleClick(event) {
setVal(event.target.innerHTML);
setShow(false);
}
function handleChange(event) {
setVal(event.target.value);
console.log(event.target.value);
}
return (
<div className="App">
<input
type="text"
value={val}
onChange={handleChange}
onKeyUp={handleClick}
/>
{show && (
<div
style={{
width: "180px",
height: "80px",
background: "pink"
}}
onClick={handleClick}
>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</div>
)}
</div>
);
}
Try this:
import React from 'react'
import { useState } from "react";
function Test() {
const [show, setShow] = useState(true);
const [val, setVal] = useState("");
function handleClick(event, what) {
if (what === 'click') { setVal(event.target.innerHTML) }
setShow(false);
}
function handleChange(event) {
setVal(event.target.value);
}
return (
<div className="App">
<input
type="text"
value={val}
onChange={handleChange}
onKeyUp={(e) => handleClick(e, 'keyup')}
/>
{show && (
<div
style={{
width: "180px",
height: "80px",
background: "pink"
}}
onClick={(e) => handleClick(e, 'click')}
>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</div>
)}
</div>
);
}
export default Test
you can check the condition, That show state is true of false in handleChange and then print number as a text in the textbox

Remove element from Select Antd

I use Ant-design component Select/Custom dropdown (codesandbox) but it does not have a function to delete an item from a special dropdown menu and if you add a button for each item Option then when you click on it the item Select closes. Who faced such a problem?
The trick here is to use event.stopPropagation() which will just stop event bubbling to HTML elements that are higher in the hierarchy.
The delete handler that stops propagation.
const deleteItem = (e, index) => {
e.stopPropagation();
e.preventDefault();
const updated = [...items];
updated.splice(index, 1);
setItems(updated);
};
The whole code with a button that deletes an item from a dropdown.
import React from "react";
import "antd/dist/antd.css";
import "./index.css";
import { PlusOutlined } from "#ant-design/icons";
import { Divider, Input, Select, Space, Typography, Button } from "antd";
import { useState } from "react";
const { Option } = Select;
let index = 0;
const App = () => {
const [items, setItems] = useState(["jack", "lucy"]);
const [name, setName] = useState("");
const onNameChange = (event) => {
setName(event.target.value);
};
const addItem = (e) => {
e.preventDefault();
setItems([...items, name || `New item ${index++}`]);
setName("");
};
const deleteItem = (e, index) => {
e.stopPropagation();
e.preventDefault();
const updated = [...items];
updated.splice(index, 1);
setItems(updated);
};
return (
<Select
style={{
width: 300
}}
placeholder="custom dropdown render"
dropdownRender={(menu) => (
<>
{menu}
<Divider
style={{
margin: "8px 0"
}}
/>
<Space
align="center"
style={{
padding: "0 8px 4px"
}}
>
<Input
placeholder="Please enter item"
value={name}
onChange={onNameChange}
/>
<Typography.Link
onClick={addItem}
style={{
whiteSpace: "nowrap"
}}
>
<PlusOutlined /> Add item
</Typography.Link>
</Space>
</>
)}
>
{items.map((item, index) => (
<Option key={item}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}
>
<div>{item}</div>
<Button onClick={(e) => deleteItem(e, index)} danger size="small">
Delete
</Button>
</div>
</Option>
))}
</Select>
);
};
export default App;

How to properly control focus and blur events on a React-Bootstrap InputGroup?

I have a single input element from react-bootstrap that will allow the user to change the field value and 2 buttons will appear, one to accept the changes and the other will cancel the changes leaving the original value in.
I manage to control the focus and blur events by delegating the listeners to the wrapping component, my thinking is that since the focus is still within the wrapping component, I won't lose its focus, but pressing the inner buttons seems to blur the focus, therefore the Accept and Cancel buttons don't fire any events...
Here is my code example:
import { useState, useEffect, useRef } from "react";
import { InputGroup, Button, FormControl } from "react-bootstrap";
import "./styles.css";
const InputField = ({ title }) => {
const formRef = useRef(null);
const [value, setValue] = useState(title);
const [toggleButtons, setToggleButtons] = useState(false);
const onChange = (e) => {
setValue(e.target.value);
};
const onFocus = () => {
setToggleButtons(true);
};
const onBlur = () => {
setToggleButtons(false);
};
const acceptChange = () => {
console.log("Accept");
setToggleButtons(false);
};
const cancelChange = () => {
console.log("Cancel");
setToggleButtons(false);
};
useEffect(() => {
const form = formRef.current;
form.addEventListener("focus", onFocus);
form.addEventListener("blur", onBlur);
return () => {
form.removeEventListener("focus", onFocus);
form.removeEventListener("blur", onBlur);
};
}, []);
return (
<div className="App">
<InputGroup className="m-3" style={{ width: "400px" }}>
<FormControl
ref={formRef}
value={value}
onChange={onChange}
// onFocus={onFocus}
// onBlur={onBlur}
/>
{toggleButtons ? (
<InputGroup.Append>
<Button variant="outline-secondary" onClick={() => acceptChange()}>
Accept
</Button>
<Button variant="outline-secondary" onClick={() => cancelChange()}>
Cancel
</Button>
</InputGroup.Append>
) : null}
</InputGroup>
</div>
);
};
export default function App() {
return (
<>
<InputField title={"Input 1"} />
<InputField title={"Input 2"} />
<InputField title={"Input 3"} />
<InputField title={"Input 4"} />
</>
);
}
A couple of changes are needed to make this work:
The toggle buttons need to always be in the DOM, so hide them rather than only rendering if the focus is there.
To avoid hiding the buttons when the blur occurs from the input to one of the buttons you can check if the newly focused element is a sibling of the input by using the event's relatedTarget and the currentTarget.parentNode.
For example:
import { useState } from "react";
import { InputGroup, Button, FormControl } from "react-bootstrap";
import "./styles.css";
const InputField = ({ title }) => {
const [value, setValue] = useState(title);
const [toggleButtons, setToggleButtons] = useState(false);
const onChange = (e) => {
setValue(e.target.value);
};
const onFocus = () => {
setToggleButtons(true);
};
const onBlur = (e) => {
if (!e.currentTarget.parentNode.contains(e.relatedTarget)) {
setToggleButtons(false);
}
};
const acceptChange = () => {
console.log("Accept");
setToggleButtons(false);
};
const cancelChange = () => {
console.log("Cancel");
setToggleButtons(false);
};
return (
<div className="App">
<InputGroup className="m-3" style={{ width: "400px" }}>
<FormControl
value={value}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>
<InputGroup.Append className={toggleButtons ? "d-flex" : "d-none"}>
<Button
onBlur={onBlur}
variant="outline-secondary"
onClick={() => acceptChange()}
>
Accept
</Button>
<Button
onBlur={onBlur}
variant="outline-secondary"
onClick={() => cancelChange()}
>
Cancel
</Button>
</InputGroup.Append>
</InputGroup>
</div>
);
};
export default function App() {
return (
<>
<InputField title={"Input 1"} />
<InputField title={"Input 2"} />
<InputField title={"Input 3"} />
<InputField title={"Input 4"} />
</>
);
}
https://codesandbox.io/s/input-group-focus-slwoh

How to initiate an index for an object being created in React?

I am making an app where one can create an object called an item. I want to access this object's index in the array of items for another method I am writing. However, when I console.log the item's index, I always get undefined.
I am assigning each item a unique id using nanoid, and I am wondering if there is some kind of similar tool for assigning each object an index? This is my addItem function which handles instantiating a new item:
function addItem(name) {
const newItem = { id: "item-" + nanoid(), name: name, //index: ? };
setItems([...items, newItem]);
}
This is defined in my App component:
import React, { useState } from "react";
import Form from "./components/Form";
import Item from "./components/Item";
import { nanoid } from "nanoid";
import './App.css';
function App(props) {
const [items, setItems] = useState(props.items);
function deleteItem(id) {
const remainingItems = items.filter(item => id !== item.id);
setItems(remainingItems);
}
function moveLeft(index) {
console.log(index)
}
const itemList = items
.map(item => (
<Item
id={item.id}
index={item.index}
name={item.name}
key={item.id}
deleteItem={deleteItem}
moveLeft={moveLeft}
/>
));
function addItem(name) {
const newItem = { id: "item-" + nanoid(), name: name, //index: ? };
setItems([...items, newItem]);
}
return (
<div className="form">
<Form addItem={addItem} />
<ul className="names">
{itemList}
</ul>
</div>
);
}
export default App;
and passed down to my item componenet:
import React from "react";
import { Button, Card, CardContent, CardHeader } from 'semantic-ui-react'
export default function Item(props) {
return (
<Card>
<CardContent>
<CardHeader> {props.name}</CardHeader>
<Button onClick={() => props.deleteItem(props.id)}>
Delete <span className="visually-hidden"> {props.name}</span>
</Button>
</CardContent>
<CardContent style={{ display: 'flex' }}>
<i className="arrow left icon" onClick={() => props.moveLeft(props.index)} style={{ color: 'blue'}}></i>
<i className="arrow right icon" style={{ color: 'blue'}}></i>
</CardContent>
</Card>
);
}
I am attempting to access the index so that I can eventually write a method to move the element of the array to the left or right, which is why that moveLeft method is coded in.
Any advice would be appreciated thank you
When you create itemList using map() you get access not only to every element in the list but also their corresponding index in that list. Check map() docs.
With that in mind, itemList creation would be something like this:
const itemList = items
.map((item, index) => (
<Item
id={item.id}
index={index}
name={item.name}
key={item.id}
deleteItem={() => deleteItem(index)}
moveLeft={() => moveLeft(index)}
/>
));

React. onClick event not firing

In my navbar, I have a button that will display a submenu (list of items) when clicked. Each item is their own child component and when clicked I want them to fire an event. The onClick event listener is not responding at all. However, other mouse events do apply (onMouseEnter, onMouseOut etc). Anyone might know what's up?
Child Component: NotificationItem.js
import React from "react"
import { connect } from "react-redux"
import { updateNotification } from "../../actions/notificationActions"
class NotificationItem extends React.Component{
constructor(props){
super(props)
this.handleOnClick = this.handleOnClick.bind(this)
}
handleOnClick = (event) => {
console.log("clicked")
// let notificationId = this.props.notification._id
// this.props.updateNotification(notificationId)
}
render(){
let {avatar, description, seen} = this.props.notification
return(
<div
onClick={this.handleOnClick}
className="d-flex notification-wrapper"
style={ seen ? (
{ width: "250px", whiteSpace: "normal", padding: "0.5rem" }
):( { width: "250px", whiteSpace: "normal", padding: "0.5rem", backgroundColor: "#d7e2f4" }
)
}
>
<div>
<img src={avatar} style={{ width: "25px"}} className="mr-2 rounded-circle"/>
</div>
<div>
{description}
</div>
</div>
)
}
}
Parent component: NotificationFeed.js
import React from "react"
import { connect } from "react-redux"
import NotificationItem from "./NotificationItem"
class NotificationFeed extends React.Component{
constructor(props){
super(props)
}
render(){
let notifications = this.props.notification.notifications
return(
<div className="dropdown-menu">
{notifications.map((notification, index) => {
return(
<div key={index}>
<NotificationItem notification={notification}/>
</div>
)
})}
</div>
)
}
}
const mapStateToProps = (state) => {
return{
notification: state.notification
}
}
export default connect(mapStateToProps)(NotificationFeed)
Edit: Something I noticed that might be of help. I'm using a bootstrap class to create this dropdown toggle-effect. When clicking on one of the items, the submenu closes immediately, without firing my desired event handler on the component.
<span className="dropdown" id="notifications-dropdown">
<Link to="#" className="nav-link text-light dropdown-toggle" data-toggle="dropdown">
<span
key={Math.random()}
>
<i className="fa fa-bell"></i>
</span> { windowWidth < 576 && "Notifications"}
<NotificationFeed/>
</Link>
</span>
For those still interested, this was a problem with Bootstrap. Because the elements were created inside a Bootstrap dropdown it had some logic I couldn't see. Whenever I would click on an element, the dropdown closes before the event-handler would even fire.
Opted, to create my own dropdown instead. Thanks all!
You created an arrow function, you do not need to bind it in the constructor
import React from "react"
import { connect } from "react-redux"
import { updateNotification } from "../../actions/notificationActions"
class NotificationItem extends React.Component{
state = {}
handleOnClick = (event) => {
console.log("clicked")
}
//or do not use arrow function then bind in the constructor
//constructor(props) {
//super(props);
//this.handleOnClick = this.handleOnClick.bind(this)
//}
// handleOnClick(event) {
// console.log("clicked")
// }
render(){
let {avatar, description, seen} = this.props.notification
return(
<div
onClick={this.handleOnClick}
className="d-flex notification-wrapper"
style={ seen ? (
{ width: "250px", whiteSpace: "normal", padding: "0.5rem" }
):( { width: "250px", whiteSpace: "normal", padding: "0.5rem", backgroundColor: "#d7e2f4" }
)
}
>
<div>
<img src={avatar} style={{ width: "25px"}} className="mr-2 rounded-circle"/>
</div>
<div>
{description}
</div>
</div>
)
}
try this
onClick={ (e) => this.handleOnClick(e)}
Try change your code, now it's like method:
handleOnClick(event){
console.log("clicked")
}

Categories

Resources