ReactJS: auto save feature for any input field
Tags: react, autosave, vanillajs
Published: 2021-02-04 06:15:47

I picked up ReactJS recently after a few years away from coding in Javascript at all. Since ReactJS is a powerful and useful framework with a lot of bells and whisles, in the midst of so many new things to learn, I lost sight of it being just a *javascript* framework. Yes, I can still use vanilla JS in my ReactJS app! (duh)

Here is one example:

My team wanted to implement a simple debounced auto-save function for our text box input. At first we used lodash's debounce to make it work. It seemed to be working and we launched it. But customers came back with a complaint that typing in the textbox appeared to be jittery. So, here is the old code:

```javascript
import { debounce } from "lodash";
import RichText from "../controls/rich_text";

...

class TextQuestion extends React.Component {
  constructor(props) {
    super(props);
    this.update = debounce(this._update.bind(this), 500);
  }

  _update(text) {
    this.setState({ text });

    callAPIToSaveText({ answer: text });
  }
 
  ...
  
  render() {
    ...
    
    <RichText.NoToolbar editorClass="small" value={this.state.text} onChange={this.update} />
  }
}

```

This works when it doesn’t :) e.g. when a user is typing with an interval of about half a second between letters. The app would save the text, and re-render the textbox with the saved text while discarding what they have typed *after* the saving.

To fix the bug, I opted to using vanilla JS’s setTimeout as follow:

```javascript
import RichText from "../controls/rich_text";

const SavingState = Object.freeze({
  NOT_SAVED: 0,
  SAVING: 1,
  SAVED: 2
});

class TextQuestion extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      text: "",
      saving: SavingState.NOT_SAVED
    };

    this.update = this.update.bind(this);
  }

  componentDidMount() {
    this.timer = null;
  }

  update(text) {
    clearTimeout(this.timer);
    this.setState({ text, saving: SavingState.NOT_SAVED });

    this.timer = setTimeout(() => {
      this.setState({ text, saving: SavingState.SAVING });

      // calls API
      callAPIToSaveText({ answer: text })
      .then(() => this.setState({ saving: SavingState.SAVED }));
    }, 500);
  }

  render() {
    <RichText.NoToolbar editorClass="small" value={this.state.text} onChange={this.update} />
    <AutoSaveDisplay saving={this.state.saving} />
  }
}
  
const AutoSaveDisplay = ({ saving }) => {
  let display;
  switch (saving) {
    case SavingState.SAVING:
      display = <em>saving...</em>;
      break;
    case SavingState.SAVED:
      display = (
        <>
          <Icon check /> <em>saved!</em>
        </>
      );
      break;
    default:
      display = <br />;
  }
  return <div className="auto-save-display">{display}</div>;
};
AutoSaveDisplay.propTypes = {
  saving: PropTypes.oneOf(Object.values(SavingState))
};

// styles I use for auto-save-display
// .auto-save-display {
//  display: block;
//  height: 25px;
//}
```

Here we go. Auto-save works now! The AutoSaveDisplay component you saw above is only a cherry of top bonus to display “saving…” and “saved” when it’s saving your text.

![1*rPa2hux64xMaP1tCoR8mgg](https://dev-to-uploads.s3.amazonaws.com/i/3ela20nnq06ogseu3rv3.png)