Reputation: 308
I am trying to implement a button which switches between two displayed forms. However, this is not working, as no change occurs on button click. I have the following code:
import React, {Component} from "react";
import ShortenForm from "./ShortenForm";
import UnshortenForm from "./UnshortenForm";
class FormSelector extends Component {
constructor(props) {
super(props);
this.state = {
shorten: this.props.shorten // Pass true or false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({shorten: event.target.value});
}
render() {
let form;
let button;
if (this.state.shorten) {
form = <ShortenForm placeholder='Enter URL to shorten'/>;
button = <button onClick={this.handleChange} value={false}>Change</button>;
} else {
form = <UnshortenForm placeholder='Enter URL to unshorten'/>;
button = <button onClick={this.handleChange} value={true}>Change</button>;
}
return (
<React.Fragment>
{form}
{button}
</React.Fragment>
);
}
}
export default FormSelector;
Upvotes: 0
Views: 54
Reputation: 480
I see a few answers here, but I think your key question could still be addressed. So I'm going to try and do that here, and maybe consolidate all this information just a bit.
The value
prop that you're passing is actually a string, not a boolean value. Hence, you're assigning the string "true"
when you mean to be assigning the boolean true
- this is what messes up your if condition, as in Javascript (as in other languages) a non-empty string is what we call a truthy value, and hence your if condition will always evaluate to true. This is your key issue here, above all else. Replacing this with a boolean value and negating it (as the other answers show) most definitely works, but this won't work when you have multiple forms. If this is the case, I would use string identifiers, embracing the fact that value passes a string, and render different forms accordingly.
I've modified your class, this should give you a good idea of what I mean. You can also find a CodeSandbox here, which should give you a slightly more interactive idea
import React, { Component } from "react";
class FormSelector extends Component {
constructor(props) {
super(props);
this.state = {
shorten: this.props.shorten
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
console.log("Setting: " + event.target.value);
this.setState({ shorten: event.target.value });
}
render() {
// This bit is just for illustrative purposes
console.log(this.state.shorten);
console.log(typeof this.state.shorten);
if (this.state.shorten === "short") {
return (
<React.Fragment>
<span>Shortened</span>
<button onClick={this.handleChange} value={"unshort"}>
Change
</button>
</React.Fragment>
);
} else {
return (
<React.Fragment>
<span>Unshortened</span>
<button onClick={this.handleChange} value={"short"}>
Change
</button>
</React.Fragment>
);
}
}
}
export default FormSelector;
Upvotes: 1
Reputation: 19863
You should use updater
argument of setState in this case:
this.setState((state, props) => {
return {shorten: !state.shorten};
});
As, from docs:
updater argument:
(state, props) => stateChange
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.
Upvotes: 1
Reputation: 2964
In your handleChange
function, you're setting state to the value of event.target.value
, but the value is a string, so both 'true'
and 'false'
will return true
.
This means that this.state.shorten
will always be true, so the ShortenForm
will always render.
You shouldn't be using the value of a button to determine state, as the value attribute is treated differently across browsers.
You also don't need to create two different buttons and choose which one to render based on state. Just render the same button that calls the same function every time. All the function does is invert the current state value:
handleChange = () => this.seState(prev => ({ shorten: !prev.shorten })
You don't need to provide a value to the button:
return (
<>
{form}
<button onClick={this.handleChange}>Change</button>
</>
)
Upvotes: 1