How to Double Click Toggle visibility of React Component? - javascript

Currently have a popup component showing up double click using the onDoubleClick() handler.But I'd like to close that popup on double click of the popup component but I can't seem to get it to work. Here is what I have been trying, the thought process was to just to set toggleModal to false and it should work.
const [selectedImageId, setSelectedImageId] = useState(-1);
const [toggleModal, setToggleModal] = useState(false);
const handleModalPopupOnClick = (id) => {
setSelectedImageId(id);
setToggleModal(true);
};
return (
<div>
{toggleModal && <PopupModal onDoubleClick={setToggleModal(false)}/>}
<div onDoubleClick{()=> handleModalPopupOnClick(image.id)>Open Popup</div>
</div>
)
Any ideas? Thank you for any suggestions or guidance.

The line
{toggleModal && <PopupModal onDoubleClick={setToggleModal(false)}/>}
Is immediately calling setToggleModal with an argument of false when the component is rendered, and I believe undefined becomes the value of onDoubleClick. (Not 100% on if setState has a return value or not)
To fix your problem you should provided this as a prop:
{toggleModal && <PopupModal onDoubleClick={() => setToggleModal(false)}/>}
This is providing a function definition rather than calling the function.

Maybe PopupModal is your custom component, so it doesn't provide onDoubleClick event.
onDoubleClick in this line:
{toggleModal && <PopupModal onDoubleClick={setToggleModal(false)}/>}
is just a prop.
So you must call props.onDoubleClick in the handler of onDoubleClick event inside the PopupModal.

Related

Handling an onClick on a component in React

Suppose I have a react app, and I have component A and component CountryCard, suppose that component A displays several times component CountryCard, as a deck of cards, by using a map instruction as in
{this.props.countries.map(country => <CountryCard country={country}...
How do I handle a click on one CountryCard?, is it enough that I use onClick, as
{this.props.countries.map(country => <CountryCard country={country} onClick={this.handleCountryClick(country)}...
or do I have to add an event listener, how do I do that?
Thanks in advance
Rafael
You should just pass this.handleCountryClick to CardComponent
{this.props.countries.map(country => <CountryCard country={country} onCountryClick={this.handleCountryClick}...
then call props.onCountryClick inside of CountryCard, example:
<div onClick={() => this.props.onCountryClick(this.props.country)}></div>
Events should be used as there is a call back on events:
onClick={e => this.handleCountryClick(country)}
or
onClick={this.handleCountryClick}
onClick goes on Dom elements
const CountryCard = ({onClick}) => (<div onclick = {onClick}></div>)
/*maybe do a data attribute if you don't want `country` in perantasis*/
const handleCountryClick = (e) => {
var attr = e.currentTarget.getAttribute("country")
}
Here's an example of a functional component
const CountryCard = ({onClick, country}) => (<div country = {country} onclick = {onClick}></div>)
I'm just giving you an option to use data attribute so you don't have to call an event inline.
And reminding you that click events go on Dom Elements.
I don't use React classes in my work.

Changing the value of a react input component with JS doesn't fire the inputs onChange event

I have a range input that has a few things happening onChange. This works as I'd expect with manual click/drag usage. However, when I try to change the value with JavaScript, my onChange event doesn't seem to fire.
Here is my code:
const App = () => {
const [currentValue, setCurrentValue] = useState(0);
const setRangeValue = () => {
const range = document.querySelector("input");
range.value = 50;
range.dispatchEvent(new Event("change", { bubbles: true }));
};
return (
<div className="App">
<h1>Current Value: {currentValue}</h1>
<input
type="range"
min={0}
max={100}
step={10}
onChange={e => {
console.log("Change!");
setCurrentValue(+e.target.value);
}}
defaultValue={0}
/>
<button onClick={setRangeValue}>Set current value to 50</button>
</div>
);
};
And here it is (not) working in CodeSandbox:
https://codesandbox.io/s/divine-resonance-rps1n
NOTE:
Just to clarify. My actual issue comes from testing my component with jest/react testing library. The button demo is just a nice way to visualize the problem without getting into the weeds of having to duplicate all of my test stuff too.
const getMessage = (value, message) => {
const slider = getByRole('slider');
fireEvent.change(slider, { target: { value } });
slider.dispatchEvent(new Event('change', { bubbles: true }));
return getByText(message).innerHTML;
};
When the fireEvent changes the value, it doesn't run the onChange events attached to the input. Which means that getByText(message).innerHTML is incorrect, as that will only update when a set hook gets called onChange. (All of this works when manually clicking/dragging the input slider, I just can't "test" it)
Any help would be great!
The issue is that React has a virtual DOM which doesn't connect directly to the DOM. Note how the events from React are SyntheticEvents. They are not the actual event from the DOM.
https://github.com/facebook/react/issues/1152
If this is for a unit test, create a separate component for the slider and text and make sure they perform as expected separate from each other with props.
For a more in-depth article on how to specifically test a range slider, checkout https://blog.bitsrc.io/build-and-test-sliders-with-react-hooks-38aaa9422772
Best of luck to ya!

How to select the clickable parent in my React component even if I click on its children or anywhere else inside the parent

I have started an application which I want to work same as weather.com next 36 hours section. The idea is when you click on each weatherCard which has a seperate component in my app you will update the below section which is my weatherDetails component based on the selected weatherCard /weather box. So I made the entire component clickable by giving it the click event via props from my stateful component which is my weatherLocation component. This is my WeatherCard component:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={props.clicked}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
And here in render method in WeatherLocation component I loop through data coming from state and give props the WeatherCard component:
const WeatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
{...report}
clicked={() => this.handleCardClick(event)}
/>
);
});
And this is the handleCardClick that I added for it just for testing:
handleCardClick = event => {
// const { reports , selectedCardInfo , activeCard } = this.state;
const selectedDate = document.getElementById(event.target.id);
console.log(event.target.id);
}
I don't want to use anchor tag as I don't need href. The click works fine by itself. But because I need to get the id of the parent which is the div with the class of weatherCard. At the moment when I click on other elements inside the card I cannot get the id because they are not the parent. The reason I need its id is when I get data with from the API I need a unique value for each card so that when you click on the card the data for that card will be shown in the other component which is the WeatherDetails component. But for now I need to be able to somehow choose that selected card and pull out the state for that unique card. Could someone help me out? Thanks.
You just need to pass the Parent component ID to your onClick function in Weather Card.
Here is your WeatherCard - Component
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
You can see that I have added props.id to your onClick function and with help of event now you can access that id from the parent component.
Now here is your Parent Component- WeatherCards
const WeatherCards = this.state.reports.map( (report, i) => {
return(
<WeatherCard
key={report.id}
id={i}
{...report}
clicked={this.handleCardClick}
/>
);
});
You can see in the code I am passing index number as id to your child component.
So this will give you an id (for now it's an index number) of the card in your onClick handler.
and Finally, here is your on click handler.
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
As of now, I am using the index as id if you want to use a unique identifier, you can change that easily.
General JavaScript solution is to differentiate the elements and .stopPropogation after you've captured the event you are targeting. A nested unordered list, <ul>would be an example. Tag the containing <li> with an .opened class upon rendering/displaying each level of nesting, tag those <li> elements accordingly, e.g. a dataset attribute such as data-make, then data-model, then data-option. You then attach and fire event listeners on the different level <li>'s.
Thank you #RutulPatel. I made your answer as the answer. But I changed your code a bit as I got your point so I wrote an answer as it is long. I think we might not need to change the WeatherCard at all and I don't pass event or any logic there. so it will be intact:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
But I use your tip changing my weatherCards array to look like this:
const weatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
id={report.date}
{...report}
clicked={() => this.handleCardClick(event, report.date)}
/>
);
});
So I use the report.date which is a unique value as my id. Also I don't pass event as a parameter to the arrow function I just pass it with the report.date to the handler:
clicked={() => this.handleCardClick(event, report.date)}
And the handler will be the same as you did:
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
I might even remove event later on from both if there was no need fo that.
Thank you again.

React: Dropdown component shown once

I have a drop down component that looks like this:
{...}
this.state = {
isVisible: false
}
}
toggleDisplay() {
this.setState({isVisible: !this.state.isVisible});
}
render() {
return (
<div>
<button onClick={this.toggleDisplay()}>click</button>
{this.state.isVisible ? <MenuElements toggleDisplay={this.toggleDisplay} /> : '' }
</div>
)
}
}
"MenuElements" is just a ul that has a li. On another page i am using this component multiple times, so whenever i click on the button, "MenuElements" is shown for each click. The problem is that i want only one component to be displayed. So if a MenuElements component is already displayed, if i click on another button, it closes the previous component, and opens the second one.
How could this be implemented in my code?
Thanks.
You will somehow need to have a single state that defines which MenuItem is displayed. You could go with a global state with something like Redux, but if you are trying to build a reusable component, I guess it'd be best to wrap all of the MenuItem components in a parent component and keep a state there. That, I think, is the React way of doing it. Read this for an idea of how to design components: https://facebook.github.io/react/docs/thinking-in-react.html.
BTW, I think there is an error in the Button onClick handler. It should be:
<button onClick={this.toggleDisplay.bind(this)}> // or bind it somewhere else
Also, the correct way to change state based on previous state is this:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
I'd say this is du to the context of your callbacks. Have you tried forcing the context ?
<div>
<button onClick={this.toggleDisplay.bind(this)}>
click
</button>
{this.state.isVisible ?
<MenuElements toggleDisplay={this.toggleDisplay.bind(this)} />
: '' }
</div>

React Testing: Event handlers in React Shallow Rendering unit tests

Background
I am trying to learn how to use the React Shallow Rendering TestUtil and had the tests passing until I added an onClick event handler to both; It seems that there must be some difference with the Accordion.toggle function I am trying to use in Accordion.test.js vs this.toggle in Accordian.js...but I can't figure it out.
Question
How can I get the two highlighted tests in Accordian.test.js to pass?
Steps to reproduce
Clone https://github.com/trevordmiller/shallow-rendering-testing-playground
npm install
npm run dev - see that component is working when you click "Lorem Ipsum"
npm run test:watch - see that tests are failing
There are a number of issues preventing your tests from passing.
Looking at the test "should be inactive by default":
Accordion.toggle in your test is a property of the Accordion class, and this.toggle in your code is a property of a instance of the Accordion class - so in this case you are comparing two different things. To access the 'instance' method in your test you could replace Accordion.toggle with Accordion.prototype.toggle. Which would work if it were not for this.toggle = this.toggle.bind(this); in your constructor. Which leads us to the second point.
When you call .bind() on a function it creates a new function at runtime - so you can't compare it to the original Accordion.prototype.toggle. The only way to work around this is to pull the "bound" function out of the result from render:
let toggle = result.props.children[0].props.onClick;
assert.deepEqual(result.props.children, [
<a onClick={toggle}>This is a summary</a>,
<p style={{display: 'none'}}>This is some details</p>
]);
As for your second failing test "should become active when clicked":
You try calling result.props.onClick() which does not exist. You meant to call result.props.children[0].props.onClick();
There is a bug in React that requires a global "document" variable to be declared when calling setState with shallow rendering - how to work around this in every circumstance is beyond the scope of this question, but a quick work around to get your tests passing is to add global.document = {}; right before you call the onClick method. In other words where your original test had:
result.props.onClick();
Should now say:
global.document = {};
result.props.children[0].props.onClick();
See the section "Fixing Broken setState()" on this page and this react issue.
Marcin Grzywaczewski wrote a great article with a workaround for testing a click handler that works with shallow rendering.
Given a nested element with an onClick prop and a handler with context bound to the component:
render() {
return (
<div>
<a className="link" href="#" onClick={this.handleClick}>
{this.state.linkText}
</a>
<div>extra child to make props.children an array</div>
</div>
);
}
handleClick(e) {
e.preventDefault();
this.setState({ linkText: 'clicked' });
}
You can manually invoke the function value of the onClick prop, stubbing in the event object:
it('updates link text on click', () => {
let tree, link, linkText;
const renderer = TestUtils.createRenderer();
renderer.render(<MyComponent />);
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
// initial state set in constructor
expect(linkText).to.equal('Click Me');
// manually invoke onClick handler via props
link.props.onClick({ preventDefault: () => {} });
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
expect(linkText).to.equal('Clicked');
});
For testing user events like onClick you would have to use TestUtils.Simulate.click. Sadly:
Right now it is not possible to use ReactTestUtils.Simulate with Shallow rendering and i think the issue to follow should be: https://github.com/facebook/react/issues/1445
I have successfully tested my click in my stateless component. Here is how:
My component:
import './ButtonIcon.scss';
import React from 'react';
import classnames from 'classnames';
const ButtonIcon = props => {
const {icon, onClick, color, text, showText} = props,
buttonIconContainerClass = classnames('button-icon-container', {
active: showText
});
return (
<div
className={buttonIconContainerClass}
onClick={onClick}
style={{borderColor: color}}>
<div className={`icon-container ${icon}`}></div>
<div
className="text-container"
style={{display: showText ? '' : 'none'}}>{text}</div>
</div>
);
}
ButtonIcon.propTypes = {
icon: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired,
color: React.PropTypes.string,
text: React.PropTypes.string,
showText: React.PropTypes.bool
}
export default ButtonIcon;
My test:
it('should call onClick prop when clicked', () => {
const iconMock = 'test',
clickSpy = jasmine.createSpy(),
wrapper = ReactTestUtils.renderIntoDocument(<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>);
const component = findDOMNode(wrapper).children[0];
ReactTestUtils.Simulate.click(component);
expect(clickSpy).toHaveBeenCalled();
expect(component).toBeDefined();
});
The important thing is to wrap the component:
<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>
Hope it help!

Categories