I'm trying to highlight a element on click and on release of click event anywhere on the page I want to de-highlight the same element:
fidde:
https://jsfiddle.net/3jvpg9t1/2/
code:
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
activeState: false
};
}
onmouseup() {
this.setState({active: false});
}
onClick() {
this.setState({active:true});
}
render() {
const bgcolor = this.state.active ? "#e9e9e9" :""
return <div>
<span style={{backgroundColor: bgcolor}}onClick={this.onClick.bind(this)} onMouseUp={this.onmouseup.bind(this)}>helo world</span>
{this.state.active &&
<select><option>A</option><option>B</option></select>}
</div>
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
but for some reason I'm not getting the event that will trigger when the mouse click is release.
I tried: onMouseUp, onBlur
any ideas?
I've never found a good way of doing this with just React, so I've fallen back on vanilla JS event listeners.
document.body.addEventListener('click', this.handleBodyClick);
This will listen for a click on the body and will fire off the function which will set the active state to false.
Here it is in action: https://jsfiddle.net/sq2ajn8o/30/
If you want to have the active state toggle on click you should use a function that does something like this:
handleClick() {
const { activeState } = this.state;
this.setState({ activeState: !activeState });
}
If the active state is true, it will change to false and vice versa.
Related
I'm using React and I have the following situation.
I have one parent div with onClick event, which takes full width and inside that div I have an image. I want to be able to know where it is clicked. Thus, I want to know if image (child) or div(parent) is clicked.
My code is as follows:
class APP extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div className="parent" onClick={(e) => console.log("PARENT CLICK")}>
<img src="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
style={{maxWidth: "60%", maxHeight: "90%", pointerEvents: 'none', zIndex: 99999}}
onClick={e => console.log("IMAGE CLICK")}
/>
</div>
)
}
}
ReactDOM.render(<APP />, document.querySelector("#app"))
But it detects always the parent click. I tried to add z-index to the child, but I think that child can't be in front of parent.
Here is the fiddle.
class APP extends React.Component {
constructor(props) {
super(props)
}
handleclick(e){
e.stopPropagation();
console.log(e.target.tagName);
return false;
}
render() {
return (
<div className="parent" onClick={(e) => this.handleclick(e)}>
<img src="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
style={{maxWidth: "30%", maxHeight: "30%", zIndex: 99999}}
onClick={(e) => this.handleclick(e)}
/>
</div>
)
}
}
ReactDOM.render(<APP />, document.querySelector("#app"))
please note here I added e.stopPropagation() in the click event which only executes with the target element.. here you can read more about propogation
and also please remove the CSS pointerEvents: 'none' from the img tag, it works fine.
Working Fiddle
pointerEvents : none will block the pointer events.Remove that from your styling
You have pointerEvents set to none in your img's inline style object. Remove that. Can remove zIndex as well.
From CSS-Tricks:-
pointer-events:none prevents all click, state and cursor options on
the specified HTML element
You don't need e.stopPropagation() here. Just set the event handler only on parent like so (this is known as event delegation) :-
class APP extends React.Component {
constructor(props) {
super(props)
}
handleclick(e){
console.log(e.target.tagName);
}
render() {
return (
<div className="parent" onClick={this.handleclick}>
<img src="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
style={{maxWidth: "30%", maxHeight: "30%"}}
/>
</div>
)
}
}
ReactDOM.render(<APP />, document.querySelector("#app"))
Im not sure if this is the react way of doing this but in javascript you can use the event object properties to get the element that triggered it with event.target and the element that handled it with event.currentTarget.
document.querySelector('#parent').addEventListener('click', (e) => {
console.log(`clicked element is: ${e.target.id}`);
console.log(`event handler element is: ${e.currentTarget.id}`);
});
<div id='parent'>parent
<div id='child'>child</div>
</div>
As CodeBug noted above it should be enough to stop the propagation on the click event by calling (inside of the onClick function):
event.stopPropagation();
It is my understanding that refs are not defined outside the react lifecycle (source). The problem I am trying to solve is to capture a key press at the document level (i.e. trigger the event no matter what element is in focus), and then interact with a react ref. Below is a simplified example of what I am trying to do:
export default class Console extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
text: "",
};
}
print(output: string) {
this.setState({
text: this.state.text + output + "\n"
})
}
toggleVisible()
{
this.setState({visible: !this.state.visible});
}
render() {
const footer_style = {
display: this.state.visible ? "inline" : "none",
};
return (
<footer id="console-footer" className="footer container-fluid fixed-bottom" style={footer_style}>
<div className="row">
<textarea id="console" className="form-control" rows={5} readOnly={true}>{this.state.text}</textarea>
</div>
</footer>
);
}
}
class App extends React.Component {
private console: Console;
constructor() {
super({});
this.console = React.createRef();
}
keyDown = (e) =>
{
this.console.current.toggleVisible(); // <-- this is undefined
}
componentDidMount(){
document.addEventListener("keydown", this.keyDown);
},
componentWillUnmount() {
document.removeEventListener("keydown", this.keyDown);
},
render() {
return (
<div className="App" onKeyDown={this.keyDown}> // <-- this only works when this element is in focus
// other that has access to this.console that will call console.print(...)
<Console ref={this.console} />
</div>
);
}
}
My question is: is there a way to have this sort of document level key press within the lifesycle of react so that the ref is not undefined inside the event handler keyDown? I've seen a lot of solutions that involve setting the tabIndex and hacking to make sure the proper element has focus at the right time, but these do not seem like robust solutions to me.
I'm just learning React so maybe this is a design limitation of React or I am not designing my components properly. But this sort of functionality seems quite basice to me, having the ability to pass components from one to the other and call methods on eachother.
You're calling the onKeyDown callback twice, once on document and once on App.
Events bubble up the tree.
When the textarea is not in focus, only document.onkeydown is called.
When it is in focus, both document.onkeydown and App's div.onkeydown are called, effectively cancelling the effect(toggling state off and back on).
Here's a working example: https://codesandbox.io/s/icy-hooks-8zuy7?file=/src/App.js
import React from "react";
class Console extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
text: ""
};
}
print(output: string) {
this.setState({
text: this.state.text + output + "\n"
});
}
toggleVisible() {
this.setState({ visible: !this.state.visible });
}
render() {
const footer_style = {
display: this.state.visible ? "inline" : "none"
};
return (
<footer
id="console-footer"
className="footer container-fluid fixed-bottom"
style={footer_style}
>
<div className="row">
<textarea id="console" className="form-control" rows={5} readOnly>
{this.state.text}
</textarea>
</div>
</footer>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.console = React.createRef();
}
keyDown = (e) => {
this.console.current.toggleVisible(); // <-- this is undefined
};
componentDidMount() {
document.addEventListener("keydown", this.keyDown);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.keyDown);
}
render() {
return (
<div className="App" style={{ backgroundColor: "blueviolet" }}>
enter key to toggle console
<Console ref={this.console} />
</div>
);
}
}
Also, I recommend using react's hooks:
export default App = () => {
const console = React.createRef();
const keyDown = (e) => {
console.current.toggleVisible(); // <-- this is undefined
};
React.useEffect(() => {
// bind onComponentDidMount
document.addEventListener("keydown", keyDown);
// unbind onComponentDidUnmount
return () => document.removeEventListener("keydown", keyDown);
});
return (
<div className="App" style={{ backgroundColor: "blueviolet" }}>
press key to toggle console
<Console ref={console} />
</div>
);
};
I have a todos list that I am working on. I want to be able to mark the task complete by clicking the checkbox or anywhere in the div of that todo. However, I get a warning when I only have the onClick event on the parent component
Here is the component code that works but gives me a warning:
render(){
const {todo, handleClick} = this.props;
const className = this.getClassName(todo.complete)
return (
<div
className={className}
onClick={handleClick}
>
<input
type="checkbox"
className="todo-checkbox"
checked={todo.complete}
/>
<span
className='todo-text'>
{todo.text}
</span>
</div>
)
}
}
and here is the warning:
index.js:1375 Warning: Failed prop type: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.
To fix this I used the suggested e.stopPropagation(); and added an onChange event to the child element. However, now only the parent div is working, so I can change successfully mark a todo anywhere in the div except for the checkbox. I think this is because they share the same method that it's not separating them as two different events.
stopBubbling = (e) => {
e.stopPropagation();
}
handleChange = (e, key) => {
this.stopBubbling(e)
this.setCompletebyId(e, key)
}
setCompletebyId = (e, key) => {
const { todos } = this.state;
const index = key - 1;
const complete = todos[index].complete;
todos[index].complete = !complete;
this.setState({
todos
})
}
Any help is appreciated!
Have you tried putting the onClick in your first example on the input itself and not the div?
From React's perspective, it assumes the input is "uncontrolled" and the user cannot interact with it. So the warning is providing options if that is the case. But in this scenario, you want it to be controlled. It was working because the click event on the input checkbox would bubble up to the div and still invoke the click handler.
class Checkbox extends React.Component {
render() {
const { todo, handleClick } = this.props;
return (
<label>
<input
type="checkbox"
className="todo-checkbox"
onClick={handleClick}
checked={todo.complete}
/>
{todo.text}
</label>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todo: { complete: false, text: "TODO" }
};
}
handleClick = () => {
this.setState({ todo: { ...this.state.todo, complete: !this.state.todo.complete } });
};
render() {
const { todo } = this.state;
return <Checkbox todo={todo} handleClick={this.handleClick} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I think putting
onChange = {handleClick}
inside the input tag might help as a checkbox expects an onChange function.
I have a volume element that shows the volume bar when the user hovers over it. This all works great in desktop. However to get the same functionality on mobile, the user has clicks on the volume element which also toggles the mute click event.
I am wanting to stop that mute event when the user clicks (i.e taps) on that element on mobile.
I don't want to modify the Mute or VolumeBar classes to fix this because these are generic classes in my library that the developer uses.
https://jsfiddle.net/jwm6k66c/2145/
Actual: The mute click event gets fired and the volume bar opens.
Expected: The mute click event doesn't gets fired and the volume bar opens.
Open the console -> go into mobile view (CTRL + SHIFT + M on chrome) -> click the volume button and observe the console logs.
What I have tried:
Using volumeControlOnClick to stop propogation when the volume bar's height is 0 (i.e not visible), this does not cancel the onClick though.
What I want:
To cancel the mute click event if the user clicks on the volume icon for the first time in mobile. Instead it should only show the volume bar.
const volumeControlOnClick = (e) => {
const volumeBarContainer =
e.currentTarget.nextElementSibling.querySelector('.jp-volume-bar-container');
/* Stop propogation is for mobiles to stop
triggering mute when showing volume bar */
if (volumeBarContainer.clientHeight === 0) {
e.stopPropagation();
console.log("stop propogation")
}
};
class Volume extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="jp-volume-container">
<Mute onTouchStart={volumeControlOnClick}><i className="fa fa-volume-up" /></Mute>
<div className="jp-volume-controls">
<div className="jp-volume-bar-container">
<VolumeBar />
</div>
</div>
</div>
);
}
};
class Mute extends React.Component {
render() {
return (
<button className="jp-mute" onClick={() => console.log("mute toggled")} onTouchStart={this.props.onTouchStart}>
{this.props.children}
</button>
);
}
};
class VolumeBar extends React.Component {
render() {
return (
<div className="jp-volume-bar" onClick={() => console.log("bar moved")}>
{this.props.children}
</div>
);
}
};
React.render(<Volume />, document.getElementById('container'));
Here's what I ended up with. It works because onTouchStart is always calld before onClick if it's a touch event and if not's then the custom logic gets called anyway. It also fires before the hover has happened. This preserves the :hover event. e.preventDefault() did not.
let isVolumeBarVisible;
const onTouchStartMute = e => (
isVolumeBarVisible = e.currentTarget.nextElementSibling
.querySelector('.jp-volume-bar-container').clientHeight > 0
);
const onClickMute = () => () => {
if (isVolumeBarVisible !== false) {
// Do custom mute logic
}
isVolumeBarVisible = undefined;
};
<Mute
aria-haspopup onTouchStart={onTouchStartMute}
onClick={onClickMute}
>
<i className="fa">{/* Icon set in css*/}</i>
</Mute>
What you can do is use a flag to indicate you were in a touch event before being in your mouse event, as long as you are using bubble phase. So attach a listener to your container element like this:
let isTouch = false;
const handleContainerClick = () => isTouch = false;
const handleMuteClick = () => {
if (isTouch == false) {
console.log("mute toggled");
}
};
const volumeControlOnClick = () => {
isTouch = true;
};
class Volume extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="jp-volume-container" onClick={handleContainerClick}>
<Mute onTouchStart={volumeControlOnClick} onClick={handleMuteClick}><i className="fa fa-volume-up" /></Mute>
<div className="jp-volume-controls">
<div className="jp-volume-bar-container">
<VolumeBar />
</div>
</div>
</div>
);
}
};
class Mute extends React.Component {
render() {
return (
<button className="jp-mute" onTouchStart={this.props.onTouchStart} onClick={this.props.onClick}>
{this.props.children}
</button>
);
}
};
class VolumeBar extends React.Component {
render() {
return (
<div className="jp-volume-bar" onClick={() => console.log("bar moved")}>
{this.props.children}
</div>
);
}
};
render(<Volume />, document.getElementById('container'));
If you are not using bubble phase so you can register a timeout of 100ms with the same logic above, where after 100ms you make your flag variable false again. Just add to your touchStart handler:
setTimeout(() => {isTouch = false}, 100);
EDIT: Even though touch events are supposed to be passive by default in Chrome 56, you call preventDefault() from a touchEnd event to prevent the click handler from firing. So, if you can't modify the click handler of your Mute class in any way, but you can add a touchEnd event, than you could do:
const handleTouchEnd = (e) => e.preventDefault();
const volumeControlOnClick = () => console.log("volumeControlOnClick");
class Volume extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="jp-volume-container">
<Mute onTouchStart={volumeControlOnClick} onTouchEnd={handleTouchEnd}><i className="fa fa-volume-up" /></Mute>
<div className="jp-volume-controls">
<div className="jp-volume-bar-container">
<VolumeBar />
</div>
</div>
</div>
);
}
};
class Mute extends React.Component {
render() {
return (
<button className="jp-mute" onTouchStart={this.props.onTouchStart} onTouchEnd={this.props.onTouchEnd} onClick={() => console.log("mute toggled")}>
{this.props.children}
</button>
);
}
};
class VolumeBar extends React.Component {
render() {
return (
<div className="jp-volume-bar" onClick={() => console.log("bar moved")}>
{this.props.children}
</div>
);
}
};
render(<Volume />, document.getElementById('container'));
Try this
render() {
return (
<div className="jp-volume-container" onClick={handleContainerClick}>
<Mute onTouchStart={volumeControlOnClick} onClick={handleMuteClick}><i className="fa fa-volume-up" /></Mute>
<div className="jp-volume-controls">
<div className="jp-volume-bar-container">
<VolumeBar />
</div>
</div>
</div>
);
}
};
class Mute extends React.Component {
render() {
return (
<button className="jp-mute" onTouchStart={this.props.onTouchStart} onClick={this.props.onClick}>
{this.props.children}
</button>
);
}
};
class VolumeBar extends React.Component {
render() {
return (
<div className="jp-volume-bar" onClick={() => console.log("bar moved")}>
{this.props.children}
</div>
);
}
};
render(<Volume />, document.getElementById('container'));
I want to have a header where you can click it to hide/show it. But on this header, there will be a group of buttons that would be part of a child element. If you click these buttons, I don't want the whole thing collapse.
How can I achieve this in React? What I have so far is going to collapse everything, because Child is in Parent and under row
class Parent extends Component {
constructor() {
super();
this.state = {
show: false
}
}
render() {
return (
<div className="row" onClick={() => this.setState({show: !this.state.show})}>
<div className="col-2">
<Child/>
</div>
...
</div>
)
}
}
You should be able to use stopPropagation() in the event handler for the buttons to prevent it from bubbling further. See http://facebook.github.io/react/docs/events.html for API details.
class Child extends Component {
handleClick(event) {
event.stopPropagation();
doSomething();
}
render() {
return (
<button onClick={e => this.handleClick(e)}>
Click Me!
</button>
);
}
}
In Child's onClick event handler,
add this line
event.stopPropagation()