React onChange with input field & button

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

  1. Inner workings of Map, Reduce and Filter

  2. Thinking in React

  3. React Context API with Higher Order Component

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics