We wanted the component to display the initial render data on load. If the user makes changes by editing then we want to present with an option to SAVE, if the user clicks save then we make POST call and update the save button to be SAVING and disable it, once the update is successful the button label reverted back to SAVE but kept in disable status until the next edit. In short, the button will only be enabled if the input field is dirty.
The main problems are ( i ). Keeping an eye for changing input values & enable/disable the save button. ( ii ). Enable/Disable the save button while dealing with API request.
1. Exploring onChange for input field
import React from "react";
class Form extends React.Component {
constructor(props) {
super(props);
const { data } = this.props; // Bob is passed from parent component
const { firstName: defaultFirstName } = data;
this.state = {
firstName: defaultFirstName,
};
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
const value = event.target.value;
this.setState({
firstName: value,
});
// displaying it on screen for visualization
document.getElementById("insertChange").innerHTML = value;
};
render() {
var divStyle = {
color: "blue",
fontSize: "15px",
margin: "30px",
};
const { firstName } = this.state;
return (
<form>
<label> First Name : </label>
<input
type="text"
name="firstName"
value={firstName}
onChange={this.handleOnChange}
/>
<br />
<div id="insertChange" style={divStyle} />
</form>
);
}
}
export default Form;
Looking at the code you can see the initial data is displayed when the app is loaded ( i.e Bob ). If the user changes then name to Bobby then the form is dirty and SAVE button is enabled. Great, but the problem with this is onChange gets triggered even when the user changes the name back to Bob ( let say Bobby to Bob ). We need something custom function to validate actual data for change and control the save button.
2. Custom on change for input field & controlling SAVE button
import React from "react";
class Form extends React.Component {
constructor(props) {
super(props);
const { data } = this.props; // Bob is passed from parent component
const { firstName: defaultFirstName } = data;
this.state = {
firstName: defaultFirstName,
};
this.handleOnChange = this.handleOnChange.bind(this);
}
// generic enough to handle multiple input fields
handleOnChange = (event) => {
const key = event.target.name;
const value = event.target.value;
this.setState({
[key]: value,
});
// displaying it on screen for visualization
document.getElementById("insertChange").innerHTML = value;
};
// checking if we need to enable/disable button
checkSaveButtton() {
const { firstName: defaultFname } = this.props.data;
const { firstName } = this.state;
const changed = firstName !== defaultFname;
return changed ? false : true;
}
render() {
var divStyle = {
color: "blue",
fontSize: "15px",
margin: "30px",
};
const { firstName } = this.state;
const findButtonStatus = this.checkSaveButtton();
const renderButton = (
<button
type="submit"
onClick={this.handleSave}
disabled={findButtonStatus}
>
SAVE
</button>
);
return (
<form>
<label> First Name : </label>
<input
type="text"
name="firstName"
value={firstName}
onChange={this.handleOnChange}
/>
<br />
{renderButton}
<div id="insertChange" style={divStyle} />
</form>
);
}
}
export default Form;
3. Managing button during API call
handleSave = (e) => {
e.preventDefault(); // preventing default submit
// mocking the delay for 2 seconds
setTimeout(() => {
this.setState((preState) => ({
disableButton: !preState.disableButton,
}));
}, 2000);
};
At last, we need to make sure enabling/disabling button is done when there is no change in data and during API call. I mean, when the user doesn’t really change the data ( though form might be dirty ) we don’t want to enable the SAVE button, also we want to keep the disabled status and update the text from SAVE to SAVING… until the api call is complete. Below is the complete gist.
import React from "react";
class Form extends React.Component {
constructor(props) {
super(props);
const { data } = this.props;
const { firstName: defaultFname, lastName: defaultLname } = data;
this.state = {
firstName: defaultFname,
lastName: defaultLname,
showData: false,
showButton: false,
};
this.handleOnchage = this.handleOnchage.bind(this);
this.handleSave = this.handleSave.bind(this);
}
handleOnchage(e) {
const key = e.target.name;
const value = e.target.value;
this.setState({
[key]: value,
});
/** Hiding if user changes the form data */
const { showData } = this.state;
if (showData) {
this.setState((prevState) => ({
showData: !prevState.showData,
showButton: !prevState.showButton,
}));
}
}
checkSaveButtton() {
const { firstName: defaultFname, lastName: defaultLname } = this.props.data;
const { firstName, lastName } = this.state;
const changed = firstName !== defaultFname || lastName !== defaultLname;
return changed ? false : true;
}
handleSave(e) {
e.preventDefault();
this.setState({
showButton: true,
});
/** Mocking we updating the API and using the response to update the state */
setTimeout(() => {
this.setState({
showData: true,
});
}, 3000);
}
render() {
const { firstName, lastName, showData, showButton } = this.state;
const findButtonStatus = showButton || this.checkSaveButtton();
const renderButton = (
<button
type="submit"
onClick={this.handleSave}
disabled={findButtonStatus}
>
SAVE
</button>
);
const fullName = (
<p className="showData">
{firstName} {lastName}
</p>
);
const displayFullName = showData ? fullName : null;
return (
<div>
<form>
<label> First Name : </label>
<input
type="text"
name="firstName"
value={firstName}
onChange={this.handleOnchage}
/>
<label> Last Name : </label>
<input
type="text"
name="lastName"
value={lastName}
onChange={this.handleOnchage}
/>
<br />
{renderButton}
</form>
{displayFullName}
</div>
);
}
}
export default Form;
4. Demo
Enable/Disable onChange Button - CodeSandbox
Finally, “Ease of use may be invisible, but its absence sure isn’t.” — IBM
My past three blogs