Why does State remains unchanged after setState - javascript

I am trying to show/hide a modal when a user clicks on a item inside a list. The modal shows up as planned but cannot be hidden. When _dismiss() is called, setState executes but when I console.log the state inside the callback, the parameter show is still true.
Why is this happening?
Message.jsx
export default class Message extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false
};
this._onClick = this._onClick.bind(this);
this._dismiss = this._dismiss.bind(this);
}
_onClick(e) {
e.preventDefault();
this.setState({
show: true
})
}
_dismiss() {
this.setState({
show: false
}, function () {
console.log(this.state) // logs: Object {show: true}
})
}
render() {
return (
<div onClick={this._onClick} className="message">
<Modal show={this.state.show} close={this._dismiss}>
<h1>...</h1>
</Modal>
</div>
);
}
}
Modal.jsx
export default class Modal extends React.Component {
constructor(props) {
super(props);
this._onClose = this._onClose.bind(this);
}
_onClose() {
this.props.close()
}
render() {
if (this.props.show) {
return (
<div className="modal" onClick={this._onClose} >
<div className="content">
{this.props.children}
</div>
</div>
);
} else {
return null
}
}
}

The div is still getting on onClick events event when its children are clicked. I suspect _dismiss is being called and then _onClick is being called. React batches setState calls so it ends up setting show back to true.
Remedies.
If the close callback of handler give you the event as an argument. Call e.stopPropagation() or From #richsilv in the comments e.stopImmediatePropagation().
Or if it doesn't pass the event. Check in the _onClick if show is true or false. If it is true, don't setState

Related

React Update Child's state from Parents Component

When chartValue: true an object appears on the chart.
class Chart extends Component {
constructor(props) {
super(props);
this.state = {
chartValue: false,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
chartValue: nextProps.chartValue
});
}
render() {
return (
<div>{this.state.chartValue}</div>
);
}
}
I want to change the value to true via a button in my app component.
class App extends Component {
constructor(props) {
super(props);
this.state = ({
chartValue: false,
});
}
callChartValue() {
this.setState({
chartValue: true
})
}
render() {
return (
<div>
<button onClick={this.callChartValue}>call</button>
<Chart chartValue={this.state.chartValue} />
</div>
)}
}
}
It seems like changing the state in my App.js doesnt translate to a change in the state of the chart component. I've tried without using componentWillReceiveProps but it hasn't worked either.
You have two main issues in your code:
1. You should bind your methods
Otherwise, you won't have access to this inside them.
constructor(props) {
...
// Add this to your constructor
this.callChartValue = this.callChartValue.bind(this);
...
}
2. You shouldn't use strings for referencing the methods
render() {
return (
...
// Wrong
<button onClick="callChartValue">call</button>
// Right
<button onClick={this.callChartValue}>call</button>
...
)}

setState() does not change state when called from element that depends on state to render

I have a little component like this (Code below is simplified to the parts needed) that behaves very strange when it comes to updating the state.
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: false};
}
render() {
return(
//Markup
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} />
)}
// More Markup
);
}
}
The state gets updated somewhere else in the component, so the prop is true when the button is clicked.
A click also triggers the setState function (callback gets executed), however the state does not update.
My guess is that it does not update because the function is called by an element that directly depends on the state prop to be visible.
I figured out that adding another prop test: true to the state and changing that property to false when the button is clicked also triggers the showStuff prop to change to false. So it works when I make strange hacks.
Can someone explain this weird behavior to me? I can't gasp why the above snippet does not work like intended.
Here is the entire component:
class ElementAdd extends React.Component {
constructor(props) {
super(props);
this.defaultState = {
showElementWheel: false,
test: true
};
this.state = this.defaultState;
}
handleAddCardClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.card,
position: this.props.index
});
}
}
handleAddKnowledgeClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.knowledge,
position: this.props.index
});
}
}
handleTabPress(e) {
if (e.key === 'Tab') {
e.preventDefault();
let target = null;
if (e.shiftKey) {
if (e.target.previousSibling) {
target = e.target.previousSibling;
} else {
target = e.target.nextSibling;
}
} else {
if (e.target.nextSibling) {
target = e.target.nextSibling;
} else {
target = e.target.previousSibling;
}
}
target.focus();
}
}
hideElementWheel() {
// This is somehow the only option to trigger the showElementWheel
this.setState({ test: false });
}
render() {
return (
<div
className="element-add"
style={{ opacity: this.props.invisible ? 0 : 1 }}
onClick={() => this.setState(prevSate => ({ showElementWheel: !prevSate.showElementWheel }))}
>
<PlusIcon className="element-add__icon" />
{this.state.showElementWheel && (
<React.Fragment>
<div className="element-add__wheel">
<button
autoFocus
className="element-add__circle"
onClick={this.handleAddCardClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New element"
>
<ViewModuleIcon className="element-add__element-icon" />
</button>
<button
className="element-add__circle"
onClick={this.handleAddKnowledgeClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New knowledge-element"
>
<FileIcon className="element-add__element-icon" />
</button>
</div>
<div
className="element-add__close-layer"
onClick={() => {
this.hideElementWheel();
}}
/>
</React.Fragment>
)}
</div>
);
}
}
By writing onClick={this.setState({showStuff: false})} you are actually calling setState as soon as your button is rendered.
You want to give a function reference to onClick, not call it immediately on render.
<button onClick={() => this.setState({showStuff: false})} />
If your button is inside another element with a click listener that you don't want to run on the same click, you must make sure that the click event doesn't propagate to the parent.
<button
onClick={(event) => {
event.stopPropagation();
this.setState({showStuff: false});
}}
/>
Actually the onClick prop expects a function, you are already providing a function call, so the setState will be called each time the component is rendered, not when clicked.
Try this:
<button onClick={() => this.setState({showStuff: false})} />
Should behave as you expect :)
Works perfectly fine when I update showStuff true (see updated code below.). My guess is the code that is supposed to set showStuff: true is not working. I also added some text in the button.
import React from 'react'
import ReactDOM from 'react-dom'
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: true};
}
render() {
return(
<div>
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} > This is a button</button>
)}
</div>
);
}
}
ReactDOM.render(<Componenent />,
document.getElementById('root')
);
Before clicking
After clicking

Tie an action to a state change in pure React

Learning React so might be a bit nooby question. Consider this code:
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle(on) {
this.setState({
isOn: on
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: false
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
});
this.props.handleToggle(on);
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
As you can see currently when the button inside the Container is clicked handleButtonToggle() is fired which changes the state of the button itself and then calls a function to change the state of the parent Container. Is this the React way to do it? At the moment Container state is changed when the function handleButtonToggle is fired. Ideally I would want Container state to be dependent on MyButton state directly (cuz maybe in future there will be ways to set button state other than through handleButtonToggle, and I don't want to manually call this.props.handleToggle every time the state changes.). In other words is there a way to do something like this.props.handleToggle(this.state.pressed) in the button component when its state changes.
Codepen
After reviewing the code, a better way to write the Button component is to make it a controlled component. If the container component requires to maintain the pressed state, a controlled button component would receive the pressed state from the container component as props.
<MyButton pressed={this.state.pressed} onToggle={this.handleToggle} />
And the render method of the Button component should be:
render() {
return (
<div>
<button onClick={this.props.onToggle}>
{this.props.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
The actual button toggle will be done in the handleToggle method of the container component:
handleButtonToggle() {
let { pressed } = this.state;
pressed = !pressed;
this.setState({
pressed
});
}
You can either pass a callback as the second argument to setState or implement componentDidUpdate on your component to wait for state changes. The smallest change would be the former:
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
}, () => {
this.props.handleToggle(on);
});
}
or with componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.on !== this.state.on) {
this.props.handleToggle(this.state.on);
}
}
Here is a working codepen: https://codepen.io/damien-monni/pen/XRwewV.
I tried to keep as much as I can of your code so you can focus on really needed changes.
You need to create a controlled component. The state of your button will be stored in the container and pass by props to the child MyButton component.
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.setState({
isOn: !this.state.isOn
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton pressed={this.state.isOn} handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: this.props.pressed
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
this.props.handleToggle();
}
componentWillReceiveProps(nextProps) {
if (nextProps.pressed !== this.props.pressed) {
this.setState({ pressed: nextProps.pressed });
}
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
You need to do it because you want to share a state between two of your components. This is documented here.
I let you look at the codepen and ask your questions about it. ;)

React components only setting state for one key in constructor function. ES6

I have this component, but it's not setting show in the the state constructor. I can console.log the props and they show the correct params, but for some reason, show is not getting set.
class SubstitutionPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
suggestions: this.props.synonyms_with_levels,
show: this.props.show
}
}
handleToggleShow() {
this.setState({
show: false
})
}
render() {
console.log("sub panel")
console.log(this.state)
console.log(this.props)
if (this.props.synonyms_with_levels.length > 0 && this.state.show) {
return(
<div className="substitution-panel">
<div onClick={() => this.handleToggleShow()} className="glyphicon glyphicon-remove hover-hand"></div>
{this.props.synonyms_with_levels}
</div>
);
} else {
return (
<span>
</span>
);
}
}
}
The parent that renders this child component looks like this:
<SubstitutionPanel synonyms_with_levels= {this.props.synonyms_with_levels} show={this.state.showSubPane} />
I'm really just trying to make a "tooltip" where the parent can open the tooltip.
Is everything ok when you console.log(this.props)?
It may be just a typo here, but in the parent component you have
show={this.state.showSubPane}
and maybe it should be 'showSubPanel' with an L at the end?

react JS render a component after a click

I'm using React and when a user clicks on the <li> tag, the popup method is fired, but the component inside the method is not shown, the popup component does not get fired, why is that?
export default class Main extends React.Component {
constructor(props) {
super(props);
}
popup(value) {
console.log('fired ok');
//call popup component
<Popup value={value} />
}
render() {
return (
<ul>
<li key={0} onClick={() => this.popup(value)} />
</ul>
)
}
}
export default class Popup extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('this is not fired');
const { value } = this.props;
return (
<div class="popup">
<p>{value}</p>
</div>
)
}
}
You would need to actually render the Popup element, something along the lines of:
export default class Main extends React.Component {
constructor(props) {
super(props);
// save the popup state
this.state = {
visible: false, // initially set it to be hidden
value: '' // and its content to be empty
};
}
popup(value) {
console.log('fired ok');
this.setState({
visible: true, // set it to be visible
value: value // and its content to be the value
})
}
render() {
// conditionally render the popup element based on current state
const popup = (this.state.visible ? <Popup value={this.state.value} /> : null);
return (
<ul>
{popup}
<li key={0} onClick={() => this.popup('Hello World')}>Click Me!</li>
</ul>
)
}
}
Here's a fiddle of it in action. Click on the black "Click Me!" text.
I hope that helps!

Categories

Resources