Why does the onClick only work the first time? - reactjs

After the first onClick the modal displays. It is then removed after when clicking outside of it. However, despite the button not being a child of the modal, its onClick fails to update this.state.changed and remove the div. Why?
Codepen: https://codepen.io/Matt-dc/pen/KJYxqv
class Modal extends React.Component {
setWrapperRef = (node) => {
this.wrapperRef = node;
}
componentWillMount = () => {
document.addEventListener('mouseup', this.handleClickOutside);
}
componentDidMount = () => {
document.addEventListener('mouseup', this.handleClickOutside);
}
handleClickOutside = (e) => {
if (this.wrapperRef && !this.wrapperRef.contains(e.target)) {
this.props.close()
}
}
render() {
if(!this.props.changed) {
return null
}
return(
<div id="modal">
<div
id="ref"
style={myDiv}
ref={this.setWrapperRef}
>
{this.props.children}
</div>
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
changed: false,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange = () => {
this.setState({
changed: !this.state.changed,
});
}
render(){
return(
<div id="app">
<input type="button" value="show" onClick={this.handleChange} />
<Modal changed={this.state.changed} close={this.handleChange} />
</div>

This is because of document.addEventListener('mouseup', this.handleClickOutside) in you Modal Component. Try by commenting -
document.addEventListener('mouseup', this.handleClickOutside) and it will work.
Also its better if you can render {this.state.changed.toString()} to get more details while debugging :)

It only looks like it does not close on button click, but actually it does close the modal and then change it immediately, again, because the handleChange function is fired two times.
The problem lies in your handleClickOutside logic, which that you trigger the handleChange function (passed via close props):
<Modal changed={this.state.changed} close={this.handleChange} />
The button you are clicking is outside the modal - that's why it is fired, again.

Related

How does React class component method's parameters work?

I'm learning React and am having a hard time understanding how a class component's method parameters work. In the code below the handleAddOption method (which helps with adding items upon clicking a button, it's a to-do app) - it takes 'option' as parameter - but I do not see the arguments supplied in the render method.
Similarly in the AddOption component the handleAddOption has an argument 'option' - where is this coming from?
I'm a newbie to React and to stackoverflow as well, any norms I may not have followed please point out. Thanks for the help.
class App extends React.Component {
constructor(props) {
super(props);
this.handleAddOption = this.handleAddOption.bind(this);
this.state = {
options: []
};
}
handleAddOption(option) {
if (!option) {
return 'Enter valid value to add item';
} else if (this.state.options.indexOf(option) > -1) {
return 'This option already exists';
}
this.setState((prevState) => {
return {
options: prevState.options.concat(option)
};
});
}
render() {
return (
<div>
<div>{this.state.options.map((option) => <p>{option}</p>)}</div>
<AddOption handleAddOption={this.handleAddOption} />
</div>
);
}
}
class AddOption extends React.Component {
constructor(props) {
super(props);
this.handleAddOption2 = this.handleAddOption2.bind(this);
this.state = {
error: undefined
};
}
handleAddOption2(e) {
e.preventDefault();
const option = e.target.elements.option.value.trim();
const error = this.props.handleAddOption(option);
this.setState(() => {
return { error };
});
}
render() {
return (
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.handleAddOption2}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
</div>
);
}
}
render(<App />, document.getElementById('app'));
The arguments are being passed by the submit handler attached to the form.
You provide a function that you want called whenever there is a submit event. The form will call whatever function you provide with the arguments it usually passes in.
This happens the same way as it happens in plain JS:
const form = document.getElementById("form");
form.addEventListener("submit", e => {
e.preventDefault();
console.log("submit 1");
});
const submitHandler = e => {
e.preventDefault();
console.log("submit 2");
};
form.addEventListener("submit", submitHandler);
<form id="form">
<input type="submit" />
</form>
Consider the React example:
class MyForm extends React.Component {
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
foo:""
}
}
handleSubmit(e) {
e.preventDefault();
console.log("MyForm Submit 1");
this.setState(state => ({
foo: "foo"
}));
}
render() {
/*
onSubmit will always call the function that is provided
with a submit event argument.
*/
return (
<form onSubmit={this.handleSubmit}>
<div>{this.state.foo}</div>
<input type="submit"/>
</form>
)
}
}
class MyOtherForm extends React.Component {
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
foo:""
}
}
handleSubmit(e) {
e.preventDefault();
console.log("MyForm Submit 2");
this.setState(state => ({
foo: "bar"
}));
}
render() {
// Here we will pass the argument explicitly
return (
<form onSubmit={e => this.handleSubmit(e)}>
<div>{this.state.foo}</div>
<input type="submit"/>
</form>
)
}
}
const App = () => {
return(
<div>
<MyForm/>
<MyOtherForm/>
</div>
);
};
ReactDOM.render(<App/>, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
According to the code you provide, handleAddOption(option) is called from the handleAddOption2 function in the AddOption component.
handleAddOption2(e) {
e.preventDefault();
const option = e.target.elements.option.value.trim();
const error = this.props.handleAddOption(option);
this.setState(() => {
return { error };
});
}
You can see that option is e.target.elements.option.value.trim(). Now, where does e comes from? Tracking the source of the function above, you can see that handleAddOption2(e) is a event handler called from here:
<form onSubmit={this.handleAddOption2}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
In React, event handlers (e.g. onClick/onSubmit) by default has a default event parameter embedded to its handler function. This parameter can be any name (defined by you), and the author of the code you provide named it e.
Giving you another example, say you have a button:
<button onClick={this.sayHello}>
Click me!
</button>
There is already an event param embedded to sayHello, but you can choose not to use it.
sayHello = () => {
alert('Hello!');
}
Or if you want to use it, I'll name it event instead of e here:
sayHello = (event) => {
event => alert(event.target.value)
}
Or you can choose define the event handler inline like this:
<button value="hello!" onClick={event => alert(event.target.value)}>
Click me!
</button>

Why does clicking expand button closes the side panel in react?

i have a side panel with items listed. when the list item content overflows expand button appears and clicking that expand btn would show the entire content of list item
For this i have created a expandable component. this will show arrow_down when list item content overflows and clicking arrow_down shows up arrow_up.
However with the below code, clicking button 1 just makes the sidpanel disappear instead of arrow_up appearing. could some one help me solve this. thanks.
export default class Expandable extends React.PureComponent{
constructor(props) {
super(props);
this.expandable_ref = React.createRef();
this.state = {
expanded: false,
overflow: false,
};
}
componentDidMount () {
if (this.expandable_ref.current.offsetHeight <
this.expandable_ref.current.scrollHeight) {
this.setState({overflow: true});
}
}
on_expand = () => {
this.setState({expanded: true});
console.log("in expnad");
};
on_collapse = () => {
this.setState({expanded: false});
};
render () {
return (
<div className={(this.state.overflow ?
this.props.container_classname : '')}>
<div className={(this.state.overflow ?
this.props.classname : '')} style={{overflow: 'hidden',
display: 'flex', height: (this.state.expanded ? null :
this.props.base_height)}}
ref={this.expandable_ref}>
{this.props.children}
</div>
{this.state.overflow && this.state.expanded &&
<div className={this.props.expand}>
<button onClick={this.on_collapse}>
{this.props.arrow_up}</button>
</div>}
{this.state.overflow && !this.state.expanded &&
<div className={this.props.expand}>
<button onClick={this.on_expand}>
{this.props.arrow_down}</button>
</div>}
</div>
);
}
}
In the above code i pass the base_height to be 42px.
Edit:
i have realised for the side panel component i add eventlistener click to close the side panel if user clicks anywhere outside sidepanel. When i remove that eventlistener it works fine....
class sidepanel extends React.PureComponent {
constructor(props) {
super(props);
this.sidepanel_ref = React.createRef();
}
handle_click = (event) => {
if (this.sidepanel_ref.current.contains(event.target)) {
return;
} else {
this.props.on_close();
}
};
componentDidMount() {
document.addEventListener('click', this.handle_click, false);
}
componentWillUnmount() {
document.removeEventListener('click', this.handle_click, false);
}
render() {
return (
<div>
<div className="sidepanel" ref=
{this.sidepanel_ref}>
{this.props.children}
</div>
</div>
);
}
}
when i log the event.target and sidepanel_ref.current i see the button element in both of them but svg seems different in both of them.
How can i fix this?
Probably it is because click events bubble up the component tree as they do in the DOM too. If you have an element with an onClick handler inside an element with another onClick handler it will trigger both. Use event.stopPropagation() in the handler of the inner element to stop the event from bubbling up:
export default class Expandable extends React.PureComponent{
constructor(props) {
super(props);
this.expandable_ref = React.createRef();
this.state = {
expanded: false,
overflow: false,
};
}
componentDidMount () {
if (this.expandable_ref.current.offsetHeight <
this.expandable_ref.current.scrollHeight) {
this.setState({overflow: true});
}
}
toggleCollapse = event => {
// use preventDefault here to stop the event from bubbling up
event.stopPropagation();
this.setState(({expanded}) => ({expanded: !expanded}));
};
render () {
const {className, container_classname, base_height, expand, arrow_up, arrow_down} = this.props;
const {overflow, expanded} = this.state;
return (
<div className={overflow ? container_classname : ''}>
<div
className={overflow ? classname : ''}
style={{
overflow: 'hidden',
display: 'flex',
height: expanded ? null : base_height
}}
ref={this.expandable_ref}
>
{this.props.children}
</div>
{overflow && (
<div className={expand}>
<button onClick={this.toggleCollapse}>
{expanded ? arrow_up : arrow_down}
</button>
</div>
)}
</div>
);
}
}

How to make click event handler functions with TypeScript

I'm trying to add an event listener for clicks but it's saying that classList doesn't exist on type EventTarget.
class UIModal extends React.Component<Props> {
handleClick = (e: Event) => {
if ((e.target as EventTarget).classList.contains('modal-mask')) {
this.props.close();
}
}
componentDidMount() {
window.addEventListener('click', this.handleClick);
}
componentWillUnmount() {
window.removeEventListener('click', this.handleClick);
}
render() {
return (
<div className="modal-mask">
<div className="modal">
{this.props.children}
</div>
</div>
);
}
}
Maybe you should try e.target as Element?
e.target should be e.currentTarget
There's a bit of a discussion about this here https://github.com/Microsoft/TypeScript/issues/299

React Components: Pass data between siblings on state change

I am sharing an event in App comp between two child components
App Comp
class App extends Component {
constructor(props) {
super(props);
this.state = { A : 'good' };
}
SharedEvent () {
var newvalue = this.setState({A:'update'}, () => {
console.log(this.state);
alert(this.state.A)
});
}
render() {
return (
<div className="App">
<Content child1Event = {this.SharedEvent} text = {this.state.A}
child2Event = {this.SharedEvent} />
</div>
);
}
}
Parent comp
render(props) {
return (
<div className = "App">
<Subcontent whatever = {this.props.child1Event} name = {this.props.text} />
<Subcontent2 whatever = {this.props.child2Event} />
</div>
);
}
}
Child Comp
constructor(props) {
super(props);
this.state = { };
}
render() {
return (
<button id = 'btn' onClick = {this.props.whatever.bind(this , 'shared')} > {this.props.name} </button>
);
}
}
subcontent2 is same as subontent
I can successfully trigger sharedEvent from both components but it should change the name of button on setstate which it does not where am i wrong ???
the problem can be from one of these two issues:
First, you should replace your SharedEvent(){} function with SharedEvent = ()=>{...you function code} and it's because the scope has changed and if you are referring to this in your component for calling one of its functions, you should either use arrow functions or define them like you have done and bind them in your constructor to this which you have not done.
Second, the onClick event on button, restarts the page by its default behavior and everything refreshes as does your component state, and this might be the cause that you do not see the text change because the page refreshes and the state gets back to 'good', so try replacing your onClick function with this:
<button onClick={e => {e.preventDefault(); this.props.whatever();}}> {this.props.name} </button>

React multiple button on page, to know which one clicked

I have multiple buttons on my screen and and inside same container I have another label, on click I want to show the label and then hide after few seconds.
I am controlling through this.state problem is when event fires it shows all labels and then hides all. I found few solutions like assign ids etc and array for buttons.
But issue is there can be unlimited buttons so thats not the way to go to set state for each button. Or if there is any other possible way.
export default class App extends React.Component {
constructor(props) {
super();
this.state = {
visible: false
}
}
_handleClick = () => {
this.setState({
visible: !this.state.visible
});
setTimeout(() => {
this.setState({
visible: false
});
},2000);
}
render() {
return (
<div>
<button onClick={this._handleClick}>Button 1</Text></button>
{this.state.visible && <span style={styles.pic}>
Pic
</span>
}
</div>
<div>
<button onClick={this._handleClick}>Button 2</Text></button>
{this.state.visible && <span style={styles.pic}>
Pic
</span>
}
</div>
<div>
<button onClick={this._handleClick}>Button 3</Text></button>
{this.state.visible && <span style={styles.pic}>
Pic
</span>
}
</div>
</div>
);
}
}
You need each button to have its own state... So make each button a Component!
class App extends React.Component {
render() {
return <div>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>4</Button>
</div>
}
}
class Button extends React.Component {
constructor(props) {
super();
this.state = {
visible: false
}
}
_handleClick = () => {
this.setState({
visible: !this.state.visible
});
setTimeout(() => {
this.setState({
visible: false
});
}, 2000);
}
}
If there are unlimited buttons, then you can set the state like this regarding which button is clicked.
_handleClick = (id) => {
this.setState({ [id]: true })
}
id will be the unique id of each button.
Here is a simple example to show how to set the state.
https://codesandbox.io/s/k38qyv28r

Resources