DEV Community

loading...

ReactJS: auto save feature for any input field

mreigen
Ruby on Rails since 2011, noobie in Elixir since 2020
・2 min read

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:

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} />
  }
}

Enter fullscreen mode Exit fullscreen mode

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:

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;
//}
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)