I have a React app with modal, that pop-ups with rules of the game when one clicks a button. What I want to do is make it so when I click anywhere outside this pop up window it will close. i have three files. app.js, dialog.js, and outsidealerter.js . In my main app.js when I click a button it sets a state to visible, so my element takes it and renders based upon it. my outsideralerer.js basicly detects if there is a click outside anything wrapped with specific tags. Now the problem comes that i have a method that changes the state of visibility in app.js, so in order for outsderalerter.js to use it, I pass it to it so it can have access to my main state and change it so that when a click is outside the zone the pop up window disappears. Kind of works except it closes it down even if i click within a pop up window, because when i pass the value to outsidealerter it considers the whole body as a no click zone. My question is how can I prevent it from triggering and just pass it a value, or is it possible to change the state value of app.js from outsidealerter.js
App.js
updateState() {
this.setState({ isOpen: false });
}
<div id='rule-button'>
<button onClick={(e)=>this.setState({isOpen : true})} id="modalBtn" class="button">Open Rules</button>
</div>
<OutsideAlerter updateParent={ this.updateState.bind(this)}/>
<Dialog isOpen={this.state.isOpen} onClose={(e)=>this.setState({isOpen : false})}>
</Dialog>
outsidealerter.js
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
//alert('You clicked outside of me!');
{this.props.updateParent()};
}
}
I think it will be simpler to have the modal take the full space of the window height and width and just make it invisible except for the content of what you want to show.
We can wrap the modal with onClick={hideModal} and wrap the inner content with onClick={e => e.stopPropagation()} which will prevent our wrapper for triggering the hideModal handler.
class ModalWrapper extends React.Component {
state = { isModalOpen: true };
toggleModal = () => {
this.setState(({ isModalOpen }) => ({
isModalOpen: !isModalOpen
}));
};
render() {
const { isModalOpen } = this.state;
return (
<div className="App">
<button onClick={this.toggleModal}>Open Modal</button>
{isModalOpen && <Modal hideModal={this.toggleModal} />}
</div>
);
}
}
function Modal({ hideModal }) {
return (
<div onClick={hideModal} className="modal">
<div onClick={e => e.stopPropagation()} className="modal__content">
Modal content
</div>
</div>
);
}
Working example
Related
I have a toggle button that show and hides text. When the button is clicked I want it to hide another component and if clicked again it shows it.
I have created a repl here:
https://repl.it/repls/DapperExtrasmallOpposites
I want to keep the original show / hide text but I also want to hide an additional component when the button is clicked.
How to I pass that state or how do I create an if statement / ternary operator to test if it is in show or hide state.
All makes sense in the repl above!
To accomplish this you should take the state a bit higher. It would be possible to propagate the state changes from the toggle component to the parent and then use it in any way, but this would not be the preferred way to go.
If you put the state in the parent component you can use pass it via props to the needed components.
import React from "react";
export default function App() {
// Keep the state at this level and pass it down as needed.
const [isVisible, setIsVisible] = React.useState(false);
const toggleVisibility = () => setIsVisible(!isVisible);
return (
<div className="App">
<Toggle isVisible={isVisible} toggleVisibility={toggleVisibility} />
{isVisible && <NewComponent />}
</div>
);
}
class Toggle extends React.Component {
render() {
return (
<div>
<button onClick={this.props.toggleVisibility}>
{this.props.isVisible ? "Hide details" : "Show details"}
</button>
{this.props.isVisible && (
<div>
<p>
When the button is click I do want this component or text to be
shown - so my question is how do I hide the component
</p>
</div>
)}
</div>
);
}
}
class NewComponent extends React.Component {
render() {
return (
<div>
<p>When the button below (which is in another component) is clicked, I want this component to be hidden - but how do I pass the state to say - this is clicked so hide</p>
</div>
)
}
}
I just looked at your REPL.
You need to have the visibility state in your App component, and then pass down a function to update it to the Toggle component.
Then it would be easy to conditionally render the NewComponent component, like this:
render() {
return (
<div className="App">
{this.state.visibility && <NewComponent />}
<Toggle setVisibility={this.setVisibility.bind(this)} />
</div>
);
}
where the setVisibility function is a function that updates the visibility state.
I tried solutions provided on the internet but no luck. Here is the codepen link. This is how the code looks like:
//JSX:
<div className="drawer-component" onClick={this.closeDrawer}>
<div className="drawer-opener">
<button onClick={this.toggleDrawer}>Toggle</button>
</div>
{this.state.isOpen && <div class="drawer">Hello I am a drawer. Cliking on me should not close myself.</div>}
</div>
// Event Handlers:
toggleDrawer(e) {
e.stopPropagation();
this.setState((state, props) => {
return {isOpen: !state.isOpen};
})
}
closeDrawer(e) {
e.stopPropagation();
this.setState((state, props) => {
return {isOpen: false};
})
}
No idea what I am doing wrong, any help would be appreciated.
The behaviour matches the code written.
You have your text in a div(class='drawer') without any click handler, so the click handler of the parent (div class='drawer-component') gets executed, and in this case it is setting your state variable isOpen to false and closing your drawer. The e.stopPropagation() in this does not affect anything since that div does not have any further parent with a click handler to which the event might get propagated.
If what you intended was to have your text clickable without closing your drawer, add a click handler for the div (with class='drawer') which just prevents propagation.
closeDrawer(e) {
// e.stopPropagation(); This isn't required as there's nowhere further the event can get propagated
this.setState((state, props) => {
return {isOpen: false};
})
}
// Prevent click handler of parent being called and closing the drawer
preventClosingOnTextClick(e) {
e.stopPropagation();
}
render() {
return (
<div className="drawer-component" onClick={this.closeDrawer}>
<div className="drawer-opener">
<button onClick={this.toggleDrawer}>Toggle</button>
</div>
{
this.state.isOpen &&
<div class="drawer"
onClick={this.preventClosingOnTextClick}> // This prevents the closing
Hello I am a drawer. Cliking on me should not close myself.
</div>
}
</div>
);
}
};
);
I am trying to recreate a tabs component in React that someone gave me and I am getting stuck while getting the onClick method to identify the target.
These are the snippets of my code that I believe are relevant to the problem.
If I hardcode setState within the method, it sets it appropriately, so the onClick method is running, I am just unsure of how to set the tab I am clicking to be the thing I set the state to.
On my App page:
changeSelected = (event) => {
// event.preventDefault();
this.setState({
selected: event.target.value
})
console.log(event.target.value)
};
<Tabs tabs={this.state.tabs} selectedTab={this.state.selected}
selectTabHandler={() => this.changeSelected}/>
On my Tabs page:
{props.tabs.map(tab => {
return <Tab selectTabHandler={() => props.selectTabHandler()} selectedTab={props.selectedTab} tab={tab} />
})}
On my Tab page:
return (
<div
className={'tab active-tab'}
onClick={props.selectTabHandler(props.tab)}
>
{props.tab}
</div>
When I console.log(props.tab) or console.log(event.target.value) I am receiving "undefined"
There are a few issues causing this to happen. The first issue is that you wouldn't use event.target.value in the Content component because you aren't reacting to DOM click event directly from an onClick handler as you are in Tab, instead you are handling an event from child component. Also keep in mind that event.target.value would only be applicable to input or similar HTML elements that have a value property. An element such as <div> or a <span> would not have a value property.
The next issues are that you aren't passing the tab value from Tabs to Content and then from within Content to it's changeSelected() handler for selectTabHandler events.
In addition the onClick syntax in Tab, onClick={props.selectTabHandler(props.tab)} is not valid, you will not be able to execute the handler coming from props and pass the props.tab value. You could instead try something like onClick={() => props.selectTabHandler(props.tab)}.
Content - need to pass tab value coming from child to changeSelected():
render() {
return (
<div className="content-container">
<Tabs
tabs={this.state.tabs}
selectedTab={this.state.selected}
selectTabHandler={tab => this.changeSelected(tab)}
/>
<Cards cards={this.filterCards()} />
</div>
);
}
Tabs - need to pass tab coming from child to selectTabHandler():
const Tabs = props => {
return (
<div className="tabs">
<div className="topics">
<span className="title">TRENDING TOPICS:</span>
{props.tabs.map(tab => {
return (
<Tab
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
);
})}
</div>
</div>
);
};
export default Tabs;
Also don't forget the unique key property when rendering an array/list of items:
<Tab
key={tab}
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
Here is a forked CodeSandbox demonstrating the functionality.
I have an Alerts component which is responsible for rendering alerts from JSON supplied to it's props:
alert.js (cut down for brevity)
createAlert(alerts) {
return alerts.map(content => (
<Col key={content.ID} s={12}>
<div className="chip">
<Icon className="alert">error_outline</Icon>
<p>{content.Comment}</p>
<span onClick={() => this.props.onRead(content.ID)}>
<Icon className="closeAlert">close</Icon>
</span>
</div>
</Col>
));
}
render() {
let content = {};
if (!this.props.content) {
//Alerts are null so they are still loading.. show loader
content = this.createLoader();
} else if (!this.props.content.success){
//Error getting alerts
content = this.createAlertError(this.props.content.error);
}
else if (this.props.content.alerts.length === 0) {
//Alert array is null, therefor no alerts
content = this.createNoAlerts();
} else {
//Render Alerts
content = this.createAlert(this.props.content.alerts);
}
return <div>{content}</div>;
}
}
In the above snippit, you can see that if
this.props.alerts
is an array with elements, then it will run
createAlert()
which will create an array of React Components (in this case its just React-Materialize component which is just a <div></div>)
the part I am interested in is the span with the onClick event
<span onClick={() => this.props.onRead(content.ID)}>
<Icon className="closeAlert">close</Icon>
</span>
This run an event from the parent component.
The method that is run in the parent is as follows:
alertRead(id) {
this.props.deleteAlert(id);
}
What I would like, is some way to add a spinning loader icon into the button on the click, in jQuery it would be:
$(button).on("click", function(){
this.html("<i class='fa fa-spin fa-spinner'></i>"); //Spinner Icon
});
The question is, how do I edit the HTML of the button that is clicked on click?
No Redux version
I don't see any redux relation in the code so I will assume that you are not using it or not using it in this particular flow.
What you need to do is to add state to the Alert component and do two things in onClick handler:
() => { this.props.onRead(content.ID); this.setState({clicked: true});}
Of course you need to have state initialization with clicked: false. Second thing is to use this state in rendering:
{this.state.clicked && <Loader />}
{!this.state.clicked && <YourButton />}
So when clicked show loader when not clicked show button. Above code examples are only for showing you the right path.
Version assuming of Redux using.
If you are using redux then alert needs to have connection with the store like:
connect((state) => ({ isClicked: getIsButtonClicked(state)}), { dispatchClick })(AlertComponent)
And you need to dispatch click action after click ( it will set the store state responsible for that - clicked on true.
() => { this.props.onRead(content.ID); this.props.dispatchClick();}
Also finnaly you need to use this prop in rendering:
{this.props.isClicked && <Loader />}
{!this.props.isClicked && <YourButton />}
This solution will cover all instances of Alert component. I am not covering here the redux part.
I'm sure this is something trivial but I can't seem to figure out how to access the value of my button when the user clicks the button. When the page loads my list of buttons renders correctly with the unique values. When I click one of the buttons the function fires, however, the value returns undefined. Can someone show me what I'm doing wrong here?
Path: TestPage.jsx
import MyList from '../../components/MyList';
export default class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick(event) {
event.preventDefault();
console.log("button click", event.target.value);
}
render() {
return (
<div>
{this.props.lists.map((list) => (
<div key={list._id}>
<MyList
listCollection={list}
handleButtonClick={this.handleButtonClick}
/>
</div>
))}
</div>
);
}
}
Path: MyListComponent
const MyList = (props) => (
<div>
<Button onClick={props.handleButtonClick} value={props.listCollection._id}>{props.listCollection.title}</Button>
</div>
);
event.target.value is for getting values of HTML elements (like the content of an input box), not getting a React component's props. If would be easier if you just passed that value straight in:
handleButtonClick(value) {
console.log(value);
}
<Button onClick={() => props.handleButtonClick(props.listCollection._id)}>
{props.listCollection.title}
</Button>
It seems that you are not using the default button but instead some sort of customized component from another libray named Button.. if its a customezied component it wont work the same as the internatls might contain a button to render but when you are referencing the event you are doing it throug the Button component