aedm aedm - 4 months ago 38
Javascript Question

Sync React state to Meteor collection using debounce

I have a textbox in my Meteor + React application. I want to sync its value to a Mongo collection. However, I don't want to update the collection after every keystroke, only when the user has stopped typing for a few seconds.

The textbox in my

render()
function looks like this:

<input type="text" ref="answer" onChange={this.onChange} value={this.state.someValue} />


I store the textbox value in
this.state
instead of
this.data
because
this.data
reflects the Mongo collection, which might have not been updated yet.

So far, all of this works.

The problem:

If another client updates the collection, I want the textbox to show the updated value. For this I have to update
this.state
inside the
getMeteorData()
function, but that's disallowed, and I get an error: "Calling setState inside getMeteorData can result in infinite loop".

Right now I have a workaround where I manually update the textbox value in
componentDidMount()
and
getMeteorData()
, but it feels hackish and I don't like it at all.

Is there a better way to do this? Can I maybe force state updates inside
getMeteorData()
if I promise I'll be a good boy and behave nicely?

Answer

I would get rid of getMeteorData at all and turn to createContainer. Data flow gets clear and simple most of the time, including this specific case. Here it goes.

First thing first, create a container to fetch data.

export default theContainer = createContainer(() => {
  // Subscribe to the publication which publishes the data.
  const subscription = Meteor.subscribe(...);
  // Derive the data for the input box and form the props to pass down.
  const props = {
    answer: getAnswer(subscription)
  };
  return props;
}, theComponent);

theContainer acts as a container component and transferes the contained data to the presentational component theComponent by props. Be noted that the function given to createContainer is responsive, meaning that changes to reactive data sources in that function trigger rerun and result in rerender of theComponent.

By now we are all armed. Since data in the Mongo collection (Minimongo exactly) is synced by the props passed down, theComponent is aware of the synchronization by a prop transition.

export default class theComponent extends React.Component {
  ...

  componentWillReceiveProps(nextProps) {
    if (this.props.answer !== nextProps.answer) {
      this.setState({
        answer: nextProps.answer
      });
    }
  }

  render() {
    return <input value={this.state.answer} onChange={this.onChange} />;
  }
}

While such transition occurs, the upcoming value is updated to the state, and this controlled component will render the input based on the updated new value.

On the other hand, while the user starts typing, the change handler this.onChange updates the user's input to the state with every key stoke for this is a controlled component. However, the handler updates the Mongo collection (again, Minimongo exactly) only when the preset duration has elapsed to save data transmission.