React event: get Key AND prevent event propagation - reactjs

I have a nested list in React. The LI elements have an onClick event handler. It needs to stop propagation or else all the higher lying event handlers will fire too.
I can do this by having:
<li key='myKey' onClick={this.onClick}/>
combined with
onClick (event) {
event.preventDefault()
}
I can also pass the key by doing:
<li key='myKey' onClick={this.onClick.bind(this, 'myKey'}/>
But how can I pass BOTH?
I have searched long but not found a way to extract the key from the event.

Try something like this:
<li
key={key}
onClick={e => {
e.preventDefault();
// invoke your onClick callback here
this.handleOnClick(key);
}}
/>

Related

React event order

Full reproduction: codesandbox link .
I am trying to create a custom search/select component with react. My problem lies in execution order of events.
When a user focuses in on the text input field, a list with options should be shown. Then when the user clicks on one of the items in the dropdown, the element name should be filled in the text input field and then the list should be hidden.
I achive this by creating a inputFocused boolean state, and rendering list only when inputFocused is true. I attach twop event handlers on the text input itself:
onFocus={() => setInputFocused(true)}
onBlur={() => setInputFocused(false)}
But when a user clicks on one of the items, input is not getting filled.
<input
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
}}
onBlur={() => setInputFocused(false)}
onFocus={() => setInputFocused(true)}
/>
{inputFocused && (
<ul>
<li onClick={() => setSearchTerm(element.name)}>{element.name}</li>
</ul>
}
Check codesanbox link for better understanding.
I think i understand the problem. onBlur() event triggers before the onClick() event. If i wrap the onBlur callback function inside a setTimout, it will work as expected:
onBlur={() =>
setTimeout(() => {
setInputFocused(false);
}, 100)
}
I would like to avoid defining a setTimeout.
Is there solution to make sure that onClick() on <li> executes before onBlur() on <input /> ?
You can change onClick to onMouseDown on the list items, and the ordering will be handled properly by the browser.
The events get fired in the order of mousedown -> mouseup -> click (see
https://javascript.info/mouse-events-basics#events-order)

Initiate function/event from child to parent

In my parent component I have a function/event that looks like this:
const onClick = e => {
e.preventDefault();
POST('/api', { data: data }).then(
async (response) => {
const json = await response.json()
setData(json.data)
}
)
}
On the parent component, this is initiated by the following:
<button type="button" onClick={onClick}>Click me</button>
However, I also have a child component, where I would like a click event in that to also initiate this function/event and a . How is that done ?
I tried just doing something like:
<ChildComponent onclick={onClick} data={data} setData={setData} />
And then in the child component just doing something like:
<div onClick={() => {props.setData(i); props.onClick;}}>
But that doesn't seem to work.
So any hints to what I'm doing wrong here ?
I am not sure if this will fix your problem, but it seems you are using incorrectly the function prop.
Try this:
<div onClick={(e) => {props.setData(i); props.onClick(e);}}>
You can use functions in onClick in 2 ways:
1 - Assign the prop function itself to the onClick:
<div onClick={props.onClick}>
2 - Assign a callback to the onClick which calls the function with the event:
<div onClick={(e) => props.onClick(e)}>
To summarize: the onClick needs to receive a function, but your problem was you were assigning a callback (this is correct) which was not calling the props.onClick one
[you were doing props.onClick instead of props.onClick()]
You should update the state in the parent, not in the child. I am not sure why you are calling props.setData in the child when you can handle that in the parent onClick function.

TypeScript send event type to get the target.id

I am trying to send the click event on an element but TypeScript does not like any and gives out a warning, so I am trying React.MouseEvent<HTMLElement> but then it throws an error.
`Property 'id' does not exist on type 'EventTarget'.`
const closeWindow = (e: React.MouseEvent<HTMLElement>) => {
if (e.target.id === 'modal-window') ...
}
return (
<div id='modal-window' onClick={closeWindow}>
<div id='modal-content'>...</div>
</div>
)
The problem is that e.target could be just about anything, because it's the innermost target of the event, not necessarily the element on which you set the event handler.
The element you hooked the event on is currentTarget, it works correctly:
const closeWindow = (e: React.MouseEvent<HTMLElement>) => {
if (e.currentTarget.id === 'modal-window') {
console.log("Match");
}
};
(Or you could use HTMLDivElement to be more specific.)
The reason is that if you clicked the span here:
<div onClick={handler}>
<span>Click me</span>
</div>
e.target would be the span, not the div. e.currentTarget is the div.
You've said you need to use e.target because you're using that to determine whether the click was on modal-window or modal-content. Although you could use a type assertion (they're both div elements), if you're differentiating between them anyway, perhaps have two handlers, one for each:
return (
<div id='modal-window' onClick={closeModalWindow}>
<div id='modal-content' onClick={closeModalContent}>...</div>
</div>
);
Then you wouldn't need the id values (unless you use them for something else) and the component would be reusable.
If you want clicks on modal-content to not trigger your handler, for instance:
return (
<div onClick={closeWindow}>
<div onClick={e => e.stopPropagation()}>...</div>
</div>
);
...then closeWindow doesn't need to use an if.

React: Best practice for event propagation (trigger parent event, not child event)

I have been searching for the best way to prevent the event for the CHILD element and trigger event for the PARENT element in react component.
Let's say the component has an array of item object in its state and ES6 map function renders a button with icon in it for each item. When a button is clicked, the button is removed.
{this.state.items.map(item => (
<button ...>...</button>
))}
So far, I have found of 3 solutions.
1 - event.stopPropagation() based on id or some other attribute. From (How to ONLY trigger parent click event when a child is clicked)
<button id='parent' onClick={removeItem}>
<i id='child' className='fas fa-times-circle'></i>
</button>
removeItem(event) {
const target = event.target;
const id = target.id;
event.stopPropagation();
event.preventDefault();
if(event.target === 'parent') {
this.setState({
items: this.state.items.map(item => item.id != id)
});
}
}
2 - Pass parameters to event handler instead of event itself
<button onClick={() => removeItem(item.id)}>
<i className='fas fa-times-circle'></i>
</button>
removeItem(id) {
this.setState({
items: this.state.items.map(item => item.id != id)
});
}
Cons: It is inefficient because a new reference to the event handler is recreated at every render.
3 - Repeat custom attribute on parent and all child elements
<button itemid={item.id} onClick={removeItem}>
<i itemid={item.id} className='fas fa-times-circle'></i>
</button>
removeItem(event) {
const id = event.target.getAttribute('itemid');
this.setState({
items: this.state.items.map(item => item.id != id)
});
}
Cons: must ensure that all child elements down the DOM tree have itemid={item.id}, which is difficult in this case (think of svg elements like polygon).
What is the best way? I've seen pointer-events: none; be used with some implementations, too.
I'm not sure any of those solutions is actually necessary. To elaborate assume the following:
{this.state.items.map(item => (
<button type="button" value={item.id} onClick={removeItem}>
<i className='fas fa-times-circle'></i>
</button>)
}
In the event handler you can use currentTarget which emulates the default Event.currentTarget behaviour, specifically:
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.
Your event handler can be:
removeItem(event) {
const id = event.currentTarget.value;
this.setState({
items: this.state.items.filter(item => item.id != id) //assuming you intended to filter here
});
}
Note: there's no need to prevent default or propagation since the default click event of a button (of type button) is to do nothing and also you do not need to stop propagation in case you need to attach other event handlers higher in the hierarchy.

How to make the <pre> code block interactive in React (see diagram)?

I have a block of code inside a <pre> element which I need it to show the explanation of few key parameters when mouse over. For example, when mouse over keyParameter a mouse over event will be triggered.
render() {
return (
<pre>
`{
keyParameter: ''
}`
</pre>
)
My question how to add an event to keyParameter in this case when the entire code-block is in the string form.
My initial thought is to do some dirty DOM manipulations and add the event listener to the target keyParameter, however, I think there might be the "right" way to implement this feature via the React way.
Below is the effect I would like to achieve,
Take a look at React's Synthetic Events.
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter
onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown
onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp
You can attach any of these as props to your <pre> elements to attach a handler. So you would do something like:
<pre onMouseOver={e => {/* your event handler here */}>
`{
keyParameter: ''
}`
</pre>

Resources