I have:
class SomeComponent extends Component {
state = {
outside: false,
inside: false,
}
onOutsideClick = () => this.setState({ outside: true })
onInsideClick = () => this.setState({ inside: true })
render() {
return (<div onClick={this.onOutsideClick}>Some text, <p onClick={this.onInsideClick}>some other text</p></div>)
}
}
When I click on some other text, the onOutsideClick handler will trigger as well and as such this.state.outside will change to true. If some other text is clicked, I don't want to trigger any other method.
I tried with e.stopPropagation and if (this.state.inside) return in onOutsideClick but none of those worked
You should use event.stopPropagation() to stop bubbling event. It's hard to say why stopPropagation() hasn't worked for you.
Working demo with stopPropagation(): https://codesandbox.io/s/wpPJ1LkXR
import React, { Component } from 'react';
import { render } from 'react-dom';
const INSIDE_CLICK = 'inside';
const OUTSIDE_CLICK = 'outside';
class App extends Component {
state = {
clicked: undefined,
};
onOutsideClick = () => {
this.setState({ clicked: OUTSIDE_CLICK });
};
onInsideClick = (event) => {
event.stopPropagation();
this.setState({ clicked: INSIDE_CLICK });
};
render() {
return (
<div onClick={this.onOutsideClick}>
Some text, <p onClick={this.onInsideClick}>some other text</p>
<div style={{ border: '2px solid red' }}>
Clicked: {this.state.clicked}
</div>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Check some boolean before calling setState and put it to false again in the setState callback.
Related
I have used a drop-down in my React project. When I select a value I want to close it. But it doesn't automatically close. How can I do it ?
Dropdown.js
import React from 'react';
import { PropTypes } from 'prop-types';
import { DropdownToggle, DropdownMenu, UncontrolledDropdown } from 'reactstrap';
import * as Icon from 'react-bootstrap-icons';
import './DropDown.scss';
/**
* This is a reusable dropdown
* onClick function and dropdown items come as props
*/
class DropDown extends React.Component {
render() {
const { dropDownItemArray, text, onClick } = this.props;
const dropdownItems = dropDownItemArray.map((item,key) => {
return (
<div className="dropdown-items" onClick={onClick} >
{item}
</div>
);
});
return (
<div>
<UncontrolledDropdown className="multi-select-wrapper text p4">
<DropdownToggle className="select-dropdown">
<div className="select-text text p4">{text}</div>
<Icon.CaretDownFill />
</DropdownToggle>
<DropdownMenu name='test'>{dropdownItems}</DropdownMenu>
</UncontrolledDropdown>
</div>
);
}
}
DropDown.propTypes = {
text: PropTypes.string,
onClick: PropTypes.func,
menuItemArray: PropTypes.array
};
export default DropDown;
This handles all the input values from the input fields and selected values from dropdowns.
handleChangeInputs = (event) => {
if (event[0] === '<') {
this.setState({
editorHtml: event
});
} else {
if (event.type === 'change') {
this.setState({
[event.target.name]: event.target.value
});
} else {
if (event.target.parentNode.innerText[0] === 'C') {
console.log(event);
this.setState({
ticketType: event.currentTarget.textContent
});
} else {
console.log("test");
this.setState({
ticketPriority: event.currentTarget.textContent
});
}
}
}
};
This part is related to drop-down handling
if (event.target.parentNode.innerText[0] === 'C') {
console.log(event);
this.setState({
ticketType: event.currentTarget.textContent
});
} else {
console.log("test");
this.setState({
ticketPriority: event.currentTarget.textContent
});
}
You could add a toggleDropdown function along with a property in your state dropdownOpen which will cause the dropdown to be open or closed:
toggleDropdown = () => {
const dropdownOpen = this.state.dropdownOpen ? false : true;
this.setState({ dropdownOpen: dropdownOpen})
};
Pass toggleDropdown and dropdownOpen in the props and reference them in your code:
const { toggleDropdown, dropdownOpen } = this.props;
[...]
<UncontrolledDropdown
isOpen={dropdownOpen || false}
toggle={toggleDropdown}
>
Your code references an onClick function but you've named your function handleChangeInputs. Here's an onClick function that would work.
onClick = (event) => {
this.setState({ ticketType: event.currentTarget.textContent }, () => this.toggleDropdown()}
Calling toggleDropdown inside of onClick only seems to work if it's in the callback of this.setState.
I created the below HOC which I can use to wrap a React component to add 2 inactivity timers: the first to show the user a warning; the other to log them out. I got the idea from here and it seems to work pretty well. That is, I can add withTimer functionality to a component by wrapping it like this:
export default withTimer(DragDropContext(HTML5Backend)(App));
The problem is the warning alert box halts the event loop (as alert boxes apparently always do), so the logout function is never reached.
I believe a modal (e.g., from react-bootstrap) would solve this, as it presumably would not halt the event loop, thus the logout would occur as intended if the user is still idle after the warning alert.
How would I change the below HOC to use a modal for the warning instead of an alert box? Is this possible? That is, can a HOC that's used to wrap another component include a component itself (i.e., the modal) so as to keep it decoupled from the wrapped component itself?
import React from 'react';
import { Modal } from 'react-bootstrap';
const withTimer = (WrappedComponent) => {
class WithTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
warningTime: 5000,
signoutTime: 10000
};
this.events = [
'load',
'mousemove',
'mousedown',
'click',
'scroll',
'keypress'
];
for (var i in this.events) {
window.addEventListener(this.events[i], this.resetTimeout);
}
this.setTimeout();
}
clearTimeoutFunc = () => {
if (this.warnTimeout) clearTimeout(this.warnTimeout);
if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
};
setTimeout = () => {
this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
};
resetTimeout = () => {
this.clearTimeoutFunc();
this.setTimeout();
};
warn = () => {
window.alert('You will be logged out soon. Click to stay logged in.');
};
logout = () => {
window.alert('You are being logged out!');
// log the user out here
};
render() {
console.log('HOC');
return <WrappedComponent {...this.props.children} />;
}
}
return WithTimer;
};
export default withTimer;
If you wanted to use a Modal, you could do something like this:
Live Demo
withTimer.js
import React from 'react';
import MyModal from './MyModal';
const withTimer = (WrappedComponent) => {
class WithTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
warningTime: 5000,
signoutTime: 10000,
showModal: false,
modalMessage: "",
modalButtonText: "",
};
this.events = [
'load',
'mousemove',
'mousedown',
'click',
'scroll',
'keypress'
];
for (var i in this.events) {
window.addEventListener(this.events[i], this.resetTimeout);
}
this.setTimeout();
}
clearTimeoutFunc = () => {
if (this.warnTimeout) clearTimeout(this.warnTimeout);
if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
};
setTimeout = () => {
this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
};
resetTimeout = () => {
this.clearTimeoutFunc();
this.setTimeout();
};
onModalClick = () => {
this.setState({
showModal: false,
}, () => this.resetTimeout())
}
warn = () => {
this.setState({
modalButtonText: "Stay Logged In",
modalHeader: "Warning!",
modalMessage: 'You will be logged out soon. Click to stay logged in.',
showModal: true,
});
};
logout = () => {
this.setState({
modalButtonText: "Ok",
modalHeader: "Session Timed Out",
modalMessage: 'You are being logged out!',
showModal: true,
});
// log the user out here
};
render() {
console.log('HOC');
return (
<>
<MyModal
show={this.state.showModal}
modalMessage={this.state.modalMessage}
modalHeader={this.state.modalHeader}
buttonText={this.state.modalButtonText}
onButtonClick={this.onModalClick} />
<WrappedComponent {...this.props.children} />
</>
);
}
}
return WithTimer;
};
export default withTimer;
MyModal.js
import React, { useState } from "react";
import { Modal, Button } from "react-bootstrap";
function MyModal({ show = false, modalMessage, modalHeader, onButtonClick, buttonText }) {
const handleClick = event => {
onButtonClick(event);
}
return (
<Modal show={show} onHide={handleClick} animation={false}>
<Modal.Header closeButton>
<Modal.Title>{modalHeader}</Modal.Title>
</Modal.Header>
<Modal.Body>{modalMessage}</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClick}>
{buttonText}
</Button>
</Modal.Footer>
</Modal>
);
}
export default MyModal;
Yes, you can render any components you'd like in the HOC. So in your case you can render a <Modal/>.
Of course, whether the modal is displayed or not is dynamic, so that's a perfect job for the component's state to come into play. Use conditional statements in your render function to either render or not render your modal.
import React from 'react';
import { Modal } from 'react-bootstrap';
const withTimer = (WrappedComponent) => {
class WithTimer extends React.Component {
constructor(props) {
super(props);
this.state = {
showWarning: false,
showLogout: false,
warningTime: 5000,
signoutTime: 10000
};
this.events = [
'load',
'mousemove',
'mousedown',
'click',
'scroll',
'keypress'
];
for (var i in this.events) {
window.addEventListener(this.events[i], this.resetTimeout);
}
this.setTimeout();
}
clearTimeoutFunc = () => {
if (this.warnTimeout) clearTimeout(this.warnTimeout);
if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
};
setTimeout = () => {
this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
};
resetTimeout = () => {
this.clearTimeoutFunc();
this.setTimeout();
};
warn = () => {
this.setState({ showWarning: true });
};
logout = () => {
this.setState({ showLogout: true });
// log the user out here
};
render() {
let modal;
if (this.state.showLogout) {
modal = <Modal>...</Modal>;
} else if (this.state.showWarning) {
modal = <Modal>...</Modal>;
} else {
modal = null;
}
return <React.Fragment>
<WrappedComponent {...this.props.children} />
{ modal }
</React.Fragment>;
}
}
return WithTimer;
};
export default withTimer;
I want to set the state and close the modal at the same time, both are done by passing props back to the parent component. The problem I'm having is that only 1 seems to want to work at a time. I can get both working by themselves, but as soon as they're both there it doesn't work. How can I solve this?
CHILD COMPONENT
useSelectedImage = () => {
this.props.saveChosenImage(this.state.imageChosen)
this.props.closeModal();
};
<button onClick={this.useSelectedImage}>INSERT IMAGE</button>
PARENT COMPONENT
state = {
imageModalOpen: false,
}
// open the image modal
openImageModal = () => {
this.setState({ ...this.state, imageModalOpen: true })
};
// close the image modal
handleImageClose = () => {
this.setState({ ...this.state, imageModalOpen: false })
};
<Modal
open={this.state.imageModalOpen}
onClose={this.handleImageClose}
>
<DialogContent className={classes.DialogContent}>
<SelectImageModal saveChosenImage={this.saveChosenImage} closeModal={this.handleImageClose}/>
</DialogContent>
<modal>
saveChosenImage = (image) => {
this.setState({ ...this.state, imageChosen: image })
};
try like this.
CHILD COMPONENT
useSelectedImage = () => {
this.props.saveChosenImage(this.state.imageChosen);
- this.props.closeModal(); //remove this
};
...
PARENT COMPONENT
...
saveChosenImage = (image) => {
this.setState({ ...this.state, imageChosen: image, imageModalOpen: false }); // modified here
};
You could call one function inside the other. This way you maintain the purity of your methods. You could still reuse handleImageClose on a close button without selecting an image.
See the example on codesandbox https://codesandbox.io/s/sweet-torvalds-3dyv5
import React, { Component } from "react";
import ReactDOM from "react-dom";
const Child = props => {
const useSelectedImage = () => {
console.log("clicked");
// DO THIS
return props.closeModal(props.saveChosenImage(props.theImage));
};
return (
<div>
<button onClick={useSelectedImage}>Select Image</button>
<button onClick={props.closeModal}>Cancel</button>
</div>
);
};
class Parent extends Component {
openImageModal = () => console.log("OPEN FIRED");
handleImageClose = () => {
console.log("CLOSED FIRED");
this.setState({ closed: true });
};
saveChosenImage = image => {
console.log(`SAVED!! ${image}`);
this.setState({ image });
};
render() {
console.log(this.state);
return (
<Child
saveChosenImage={this.saveChosenImage}
closeModal={this.handleImageClose}
theImage="cow.jpg"
/>
);
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
To open a bootstrap modal I'm setting state {isOpen: true} but setState doesn't update the state
I've used async/await , setTimeout but nothing works.Same modal is opening in my another component.
import React from 'react';
import Dialog from 'react-bootstrap-dialog'
import { Button } from 'react-bootstrap'
import { Modal } from 'react-bootstrap'
class EventComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
this.onClickdialog = this.onClickdialog.bind(this);
this.toggleModal = this.toggleModal.bind(this);
}
toggleModal = () => {
console.log('open', this.state.isOpen)
this.setState({ isOpen: !this.state.isOpen }, console.log('open',
this.state.isOpen));
}
onClickdialog() {
debugger
this.toggleModal();
// this.dialog.show('Hello Dialog!')
}
renderEvent(event) {
const eventType = event.EventType;
const name = event.Name;
const isApproved = event.IsApproved === 1 ? 'approved' : 'unapproved';
switch (eventType) {
case 'Birthday': return (<div className='birthday' title={eventType}>
<i className="fas fa-birthday-cake" ></i> {name}
</div>);
case 'Full Day': return (<div className={`fullday ${isApproved}`} title=
{eventType}>
<i className="fas fa-umbrella-beach"></i> {name}
<i style={{ marginTop: '-2px', position: 'absolute' }} >
<a onClick={this.onClickdialog.bind(this)} style={{ fontWeight:
'bold' }}>...</a></i>
</div>);
default: return (<div>{eventType}: {name}</div>);
}
}
render() {
return (
<div>
{this.renderEvent(this.props.event)}
<Modal className={"override-fade"} show={this.state.isOpen}
onClose={this.toggleModal}>
</Modal>
</div>
)
}
}
export default EventComponent;
Expecting isOpen state to change on updating state on click
Just as Yanis said, you´re logging it wrong. Second argument of setState needs to be a callback function, however you´re calling console.log right away. Instead do:
this.setState({ isOpen: !this.state.isOpen }, () => console.log(...))
btw, you don´t have to bind class properties that is defined using arrow functions
You can use previous state in setState api.
toggleModal = () => {
this.setState((state) => ({
isOpen: !state.isOpen
}))
}
here, state represents previous state.
and also please can you remove extra bind from the onClick event.
<a onClick={this.onClickdialog} style={{ fontWeight: 'bold' }}>...</a>
I think you might probably check it wrong way. Does this console.log works for you?
this.setState({ isOpen: !this.state.isOpen }, console.log('open',
this.state.isOpen))
I think instead you should try something like
this.setState(
{ isOpen: !this.state.isOpen },
() => {
console.log('open', this.state.isOpen))
}
Try This,
toggleModal = () => {
console.log('open', this.state.isOpen)
this.setState({ isOpen: !this.state.isOpen });
console.log(this.state.isopen);}
check below function,
toggleModal = () => {
const { isOpen } = this.state;
console.log('before ', isOpen);
this.setState((prevState) => {
// prevState before updating state
console.log('before changing state', prevState.isOpen);
return {
isOpen: !prevState.isOpen,
};
}, () => {
// callback after setting state
const { isOpen: afterIsOpen } = this.state;
console.log('after update ', afterIsOpen);
});
}
Your code actually works try adding something in your Modal like this:
return (
<div>
{this.renderEvent(this.props.event)}
<Modal className={"override-fade"} show={this.state.isOpen}
onClose={this.toggleModal}>
<h1>test</h1>
</Modal>
</div>
)
and then give it a try. Also I believe that you didn't import bootsrap's css file
you should import it.
This is my Accordion component
import React, {Component, Fragment} from 'react';
import ArrowTemplate from "./ArrowTemplate";
import ContentTemplate from "./ContentTemplate";
class Accordion extends Component {
constructor(props) {
super(props);
this.state = {isAccordionExpanded: false};
this.foldAccordion = this.foldAccordion.bind(this);
this.expandAccordion = this.expandAccordion.bind(this);
}
expandAccordion() {
console.log(1);
this.setState({isAccordionExpanded: true});
}
foldAccordion() {
console.log(2);
this.setState({isAccordionExpanded: false});
}
render() {
const {state} = this;
const {isAccordionExpanded} = state;
if (isAccordionExpanded === false) {
return (
<Fragment>
<ArrowTemplate
aria={`aria-expanded="true"`}
onClick={this.expandAccordion}
direction={'down'}
color={'black'}
styles={'background:yellow'}
/>
</Fragment>
);
} else if (isAccordionExpanded === true) {
return (
<Fragment>
<ArrowTemplate
aria={`aria-expanded="true"`}
onClick={this.foldAccordion}
color={'black'}
direction={'up'}
/>
<ContentTemplate/>
</Fragment>
);
}
}
}
export default Accordion;
this is the ArrowTemplate
import React from "react";
import BlackDownArrowSVG from './svgs/black-down-arrow.svg';
import WhiteDownArrowSVG from './svgs/white-down-arrow.svg';
import styled from 'styled-components';
import PropTypes from 'prop-types';
ArrowTemplate.propTypes = {
color: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired,
styles: PropTypes.string,
aria: PropTypes.string.isRequired,
};
function ArrowTemplate(props) {
const {color, direction, styles, aria} = props;
const StyledArrowTemplate = styled.img.attrs({
src: color.toLowerCase() === "black" ? BlackDownArrowSVG : WhiteDownArrowSVG,
aria,
})`
${direction.toLowerCase() === "up" ? "translate: rotate(180deg)" : ""}
${styles}
`;
return <StyledArrowTemplate/>;
}
export default ArrowTemplate;
And here are my related tests
describe("<Accordion/>",
() => {
let wrapper;
beforeEach(
() => {
wrapper = shallow(
<Accordion/>
);
}
);
it('should have one arrow at start',
function () {
expect(wrapper.find(ArrowTemplate)).toHaveLength(1);
}
);
it('should change state onClick',
function () {
wrapper.find(ArrowTemplate).simulate("click");
expect(wrapper.state().isAccordionExpanded).toEqual(true);
}
);
it('should call FoldAccordionMock onClick',
function () {
wrapper.setState({isAccordionExpanded: true});
wrapper.find(ArrowTemplate).simulate("click");
expect(wrapper.state().isAccordionExpanded).toEqual(false);
}
);
it('should display content if isAccordionExpanded = true',
function () {
wrapper.setState({isAccordionExpanded: true});
expect(wrapper.find(ContentTemplate).exists()).toEqual(true);
}
);
it('should hide content if isAccordionExpanded = false',
function () {
expect(wrapper.find(ContentTemplate).exists()).toEqual(false);
}
);
}
);
So the problem is that when I tun the tests .simulate(click) seems to work, and all tests pass. But when I click the component myself, nothing happens. Not even a console log. Changing the onClick to onClick={()=>console.log(1)} doesn't work either. Any ideas what's wrong?
StyledArrowTemplate inner component does not know anything about onClick.
ArrowTemplate doesn't know what onClick means, it's just another arbitrary prop to it.
But, if you do as #ravibagul91 said in their comment, you should pass down onClick again, StyledArrowTemplate might know what onClick means.
So just add <StyledArrowTemplate onClick={props.onClick}/>
Accordion Component
import React, { Component, Fragment } from "react";
import ArrowTemplate from "./ArrowTemplate";
import ContentTemplate from "./ContentTemplate";
class Accordion extends Component {
constructor(props) {
super(props);
this.state = { isAccordionExpanded: false };
}
toggleAccordian = () => {
console.log(1);
this.setState({ isAccordionExpanded: !isAccordionExpanded });
};
render() {
const { state } = this;
const { isAccordionExpanded } = state;
if (isAccordionExpanded) {
return (
<Fragment>
<ArrowTemplate
aria={`aria-expanded="true"`}
onClick={() => this.toggleAccordian()}
color={"black"}
direction={isAccordionExpanded ? "up" : "down"}
styles={`background:{isAccordionExpanded ? 'yellow' : ''}`}
/>
<ContentTemplate />
</Fragment>
);
}
}
}
export default Accordion;