I have this tricky problem:
I have a text input field and a ul tag with a list of suggestions which when pressed should populate that input field.
export default function SimpleDemo() {
const [show, setShow] = useState(false)
const [text, setText] = useState("")
const updateText = (new_text) => {
setText(new_text)
setShow(false)
}
return (
<>
<input
type="text"
value={text}
onChange={(e) => { setText(e.target.value) }}
onClick={() => { setShow(true) }}
onBlur={() => { setShow(false) }} // because of this my updateText() handler never runs
/>
{show && (
<ul>
<li onClick={() => { updateText("Some Text") }}>Some Text</li>
<li onClick={() => { updateText("Suggestion 2") }}>Suggestion 2</li>
<li onClick={() => { updateText("Hello World") }}>Hello World</li>
</ul>
)}
</>
)
}
It works as expected until I add onBlur handler to my input field. (because I don't need to show suggestions when I'm not in that field)
When I add onBlur to my text input, my li tag onClick handler updateText(); never runs, thus never populating my input. They don't run because my ul tag gets deleted first thus there are no li tag elements with onClick handlers that could be clicked.
The only hack I found so far is wrapping contents of my onBlur in a setTimeout() with an arbitrary timeout duration. But it proved to be unreliable with a small timeout, and a high timeout makes it seem laggy on the front end.
Whats a reliable solution?
I'm trying to clone HTML datalist tag functuanality (because it lets me style it).
Replace your onClick handlers with onMouseDown. They fire before the blur event.
Related
What I have achieved is when I click on the input field to show the Calendar component, and when I click outside of it to hide it. However, the problem is when I click on the icon (Calendar component) itself, I lose the focus, how to fix that? I do not want to lose the focus of the icon when I click on it.
const [isFocused, setIsFocused] = useState(false);
<form onSubmit={addTask}>
<div>
<input
id="task"
placeholder=' Add task to "Inbox" on "Tomorrow"'
className={styles.input}
onChange={handleChange}
value={taskName}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
></input>
{isFocused && <Calendar />}
</div>
Calendar component
const Calendar = () => {
const [isShown, setIsShown] = useState(false);
const clickIcon = (e) => {
e.preventDefault();
setIsShown((current) => !current);
};
return (
<div>
<CalendarMonthIcon
onClick={clickIcon}
className={styles.calendar}
></CalendarMonthIcon>
{isShown && <Datepicker />}
</div>
);
};
I currently can't stop the Calendar icon from losing the focus when click on it.
You loose the focus of your input, because the click on the icon triggers the onBlur function of the input. Therefor the input looses focus and the Calendar component is beeing closed.
By adding the following function to your onBlur event, it should check if there item pressed is a child element. If that's the case the input should remain focused and the Calendar should stay open.
IMPORTANT: you have to set that onBlur function on the parent div that includes the input and calendar, otherwise the function won't work, because it looks if the click contains a child. This is also a good way to keep track if the click was outside of your component.
const closeDropdown = (event) => {
if (!event.currentTarget.contains(event.relatedTarget)) {
setIsFocused(false);
}
}
const ProductScreen = () => {
const [qty, setQty] = useState(0);
const handleAddtoCart = () => {
console.log(qty);
};
return (
<div className="productScreen">
{product.countInStock > 0 && (
<div className="productScreen__details__qty">
<span>Qty : </span>
<select
id="qty"
name="qty"
value={qty}
onChange={(e) => setQty(e.target.value)}
>
{[...Array(product.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>
{x + 1}
</option>
))}
</select>
</div>
)}
{product.countInStock > 0 ? (
<div className="productScreen__details__price__details__cart">
<button
className="productScreen__details__price__details__toCart"
onClick={handleAddtoCart()}
>
Add to Cart
</button>
</div>
</div>
</div>
);
};
Here handleAddtoCart gets triggered when selecting options but doesnt trigger when button is pressed(handleAddtoCart is added to button), when I change handleAddtoCart() to handleAddtoCart in onClick attribute of button it works properly.
Why when handleAddtoCart() is given as onclick attribute it is getting triggered by adjacent select option and is not getting triggered when button is pressed
You need to make a callback to that function, because every render of the component, will literally execute handleAddtoCart() and not as you expect it to happen only of the onClick trigger.
as react's Offical documents explained:
To save typing and avoid the confusing behavior of this, we will use the arrow function syntax for event handlers here and further below:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => console.log('click')}>
{this.props.value}
</button>
);
}
}
Notice how with onClick={() => console.log('click')}, we’re passing a function as the onClick prop. React will only call this function after a click. Forgetting () => and writing onClick={console.log('click')} is a common mistake, and would fire every time the component re-renders.
for more details:
https://reactjs.org/tutorial/tutorial.html
Change
onClick={handleAddtoCart()}
by
onClick={handleAddtoCart}
Also try with :
onChange={(e) => setQty(e.currentTarget.value)}
instead of :
onChange={(e) => setQty(e.target.value)}
The currentTarget read-only property of the Event interface identifies
the current target for the event, as the event traverses the DOM. It
always refers to the element to which the event handler has been
attached, as opposed to Event.target, which identifies the element on
which the event occurred and which may be its descendant.
https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget
I have a function where I am looping through a list of labels.
I have a onClick event where I am calling a function.
This function sets the state for clicked to that of the id of the clicked item in the loop. The function also console.log's the state.
It's logging the state of clicked 8 times. There are current 4 items in my loop.
How can I make it so that I can set the state to clicked every time the user clicks on an item in the list?
Here is my loop:
{
filteredArray.map(link => {
return (
<div
key={link.id}
role="button"
style={{paddingBottom: 20}}
onClick={this.changeView(link.id)}
onKeyPress={this.changeView(link.id)}
tabIndex={0}
>
<Paragraph size="large">
<a className='heading__dropdown__link'
{link.label}
</a>
</Paragraph>
</div>
)
})
}
Here is my function changeView:
changeView(id) {
const { clicked } = this.state
console.log(clicked)
return (
() => this.setState({clicked: id})
)
}
Your calling the function for every element, set your function like so:
changeView = id => ev => this.setState({clicked: id});
remove onPress and add arrow function in onClick like this
onClick={()=>this.changeView(link.id)}
hope this will help
I was dealing with some problem these days ago. The problem is related with event bubbling with react.
I have implemented a drop-down with some react components. This is made with a div containing a ul and some li elements. I need to make the drop down accessible by keyboard so I fire the onblur, onfocus, onkeydown and onclick elements to show and hide the drop down and to use it with keyboard.
I fire a function passed by props to real with the show/hide stuff and when the div is focused or clicked I show the drop down and when is onblur I hide it changing the state of the component. The problem is that I have some li elements with onclick functions to select the desired option. However, when I click on an option, onblur event of parent fires, it changes the state and onclick event of the li element doesn't fire so I cannot choose any option.
I'm trying to solve this using event bubbling or propagation but I couldn't find any solution. Could you please help me?
Thanks a lot!
EDIT: Code of the problem:
const Filter = (props: FilterProps) => {
...
<div onBlur={(e) =>
{props.handleDropdown(e, props.isOpen)}} onKeyDown={(e) => {props.handleKeyDown(e)}} onFocus={(e) => props.handleDropdown(e, props.isOpen)} className={props.isOpen ? "Dropdown Dropdown--multiselection is-open" : "Dropdown Dropdown--multiselection"}>
<Button className="FilterField Dropdown__trigger Button--secondary" onClick={(e) => props.handleDropdown(e, props.isOpen)}>
<span className="Dropdown__label">{setLabels(ASSETS, props.selectedAssets)}</span>
<span className="Dropdown__caret"></span>
</Button>
<ul className="Dropdown__menu">
<li className={checkSelectedAsset(-1, props.selectedAssets).class} onClick={(e) => props.selectAsset(e, -1)}>
<Translate id="all"/>
{checkSelectedAsset(-1, props.selectedAssets).isSelected &&
<span className="Dropdown__menu-item-icon">
<IconCheck/>
</span>
}
</li>
<li className="Dropdown__menu-divider"></li>
{
(props.assetClasses && props.assetClasses.length > 0) &&
props.assetClasses.map((asset) => {
return (
<li className={checkSelectedAsset(asset, props.selectedAssets).class} onClick={(e) => props.selectAsset(e, asset)}>
{
<span>
<Translate id={`products.${Helper.getType(asset)}`}/>
</span>
}{checkSelectedAsset(asset, props.selectedAssets).isSelected &&
<span className="Dropdown__menu-item-icon">
<IconCheck/>
</span>
}
</li>
);
})
}
</ul>
</div>
interface PositionsContainerState {
...
isOpen: boolean;
}
class Container extends
React.Component<ContainerProps, ContainerState> {
openCloseDropdown = (event, isOpen: boolean) => {
event.stopPropagation();
if (event.type === "focus") {
this.setState({
dropdownExpanded: true,
focusTriggered: true
});
}
else if (event.type === "blur") {
this.setState({
dropdownExpanded: false,
focusTriggered: false
});
}
else if (event.type === "click") {
if (this.state.focusTriggered) {
this.setState({
dropdownExpanded: isOpen,
focusTriggered: false
});
}
else {
this.setState({
dropdownExpanded: !isOpen,
});
}
}
};
selectAsset = (event, asset: number) => {
//event.detail.keyboardEvent.preventDefault();
if (asset < 0) {
this.props.dispatch(setFilterAssets([]));
}
else {
let auxSelectedAssets = assign([], this.props.selectedAssets);
if (auxSelectedAssets.indexOf(asset) === -1)
auxSelectedAssets.push(asset);
else
auxSelectedAssets.splice(auxSelectedAssets.indexOf(asset), 1);
this.props.dispatch(setFilterAssets(auxSelectedAssets));
}
}
render() {
return (
<Filter
handleDropdown={props.openCloseDropdown}
isOpen={props.isOpen}
selectAsset={props.selectAsset}
/>
)
};
I think u should lift the state and event handlers of the menu up to the parent component and make the child ones stateless. so when u fire an event on the child to will trigger the handler on the parent through the prop so now you can put some flags to handle the events (like that)
parent (onBlur) (add flag on the state to check if it's blur and not clicked and vice-versa)
-child (click)
-child (keyboard).
please ping me if the answer not clear enough.
The code is as follows
<ul className="sb-modules-list">
<li key={index} className={this.getModuleClass(module)} onClick={() => { alert(127) }}>
<ul className="sb-module-steps-list">
{ module.steps && module.steps.map((stepInfo, stepIndex) =>
<li key={stepIndex} className={this.getStepClass(stepInfo)} onClick={() => { alert(456) }}>
<p>{stepInfo.short_title}</p>
</li>
)}
</ul>
</ul>
The problem is that when i click on the innermost child li tag, it triggers onclick event on child as well as the parent.
For example, in this case the click triggers alert(456) and also the parent function alert(123);
I don't want the parent method to run when the child is clicked. Any idea on how to fix this?
You can use event.stopPropagation() to prevent events from bubbling up. Consider this example of a click handler:
const handleClick = (event, value) => {
event.stopPropagation();
alert(value);
}
Then you can use it like this:
<li onClick={event => handleClick(event, 127)}></li>
You have to put the event object in your parent's parameter list and use it to prevent what's going to happen:
You can do this:
onClick={(e) => { e.target === e.currentTarget && alert(127) }
You can (and probably should) use an if, which is a little more verbose but easier to understand:
onClick={(e) => { if (e.target !== e.currentTarget) return; alert(127) }