1

Is there an established pattern used to manage user interactions with individual components, such as displaying loader spinners, disabling input fields while a form is saving/loading, etc.?

I'm finding myself doing the following in my stores in order to keep components somewhat decoupled from any implied state:

function CampaignStore() {
    EventEmitter.call(this);

    AppDispatcher.register(payload => {
        switch (payload.type) {
            // [#1] ---------------v  (main action)
            case CampaignContants.SAVE:
                // [#2] ------------------------v  (prepare for the main action)
                this.emit(CampaignContants.WILL_SAVE);

                const data = payload.data;
                if (data.id) {
                    // [#3] ---v  (perform main action in store)
                    updateCampaign(payload.data).then(_ => {
                       // [#4] ------------------------v  (after main action)
                       this.emit(CampaignContants.DID_SAVE, 0)
                    });
                } else {
                    insertCampaign(payload.data).then(campaignId => this.emit(CampaignContants.DID_SAVE, campaignId));
                }
                break;
            // ...
        }
    }
}

Basically, I just fire an event saying that some action is about to take place, then I perform the action (make API call, etc.), then emit another event when the action completes.

Inside a component, I can just subscribe to a WILL_<action> event, render all the spinners and such, then clear up the screen when the DID_<action> is fired. While this seems to work, it does feel pretty boilerplattie and repetitive, as well as super messy (way too much state that only exists to tweak the UI based on where an action is (between WILL_<action> and *DID_<action>.

// some component
var MyComponent = React.createClass({
   getInitialState: function () {
       return {
           items: [],
           loading: false,
           saving: false,
           checkingPasswordStrength: fase,
           // ...
       };
   },
   render: function(){
      return (
          <div>
             {this.state.loading && (
                 <p>Loading...</p>
             )}

             {!this.state.loading && (
                 // Display component in not-loading state
             )}
          </div>
      );
   }
});
double-beep
  • 5,031
  • 17
  • 33
  • 41
rodrigo-silveira
  • 12,607
  • 11
  • 69
  • 123

3 Answers3

1

I think you would be better off using the lifecycle methods such as componentWillMount, componentDidMount, componentWillUpdate, and componentWillUnmount. Using those methods you can inspect the previous/current/next props/state (depending on the method) and respond to that. That way your store only handles your state, and your components become more pure.

Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128
1

we have found a simple loading container component helps here. so something like this:

const LoadingContainer = React.createClass({
    getDefaultProps: function() {
      return {isLoadedCheck:(res) => res.data!=null }
    },
    getInitialState: function() {
      return {isLoaded:false, errors:[]}
    },
    componentDidMount: function() {
      if(this.props.initialLoad) { this.props.initialLoad(); }
      if(this.props.changeListener) { this.props.changeListener(this.onChange); }
    },
    onChange: function() {
      let res = this.props.loadData();
      this.setState({errors: res.errors, isLoaded: this.props.isLoadedCheck(res)});
    },
    render: function() {
      if(!this.state.isLoaded) {
        let errors = this.state.errors && (<div>{this.state.errors.length} errors</div>)
        return (<div>{errors}<LoadingGraphic /> </div>)
      }
      return <div>{this.props.children}</div>
    }
  });

  const Wrapper = React.createClass({
    getDefaultProps: function() {
      return {id:23}
    },
    render: function() {
      let initialLoad = () => someActionCreator.getData(this.props.id);
      let loadData = () => someStore.getData(this.props.id);
      let changeListener = (fn) => someStore.onChange(fn);
      return (<div><LoadingContainer initialLoad={initialLoad}
        changeListener={changeListener}
        loadData={loadData}
        isLoadedCheck={(res) => res.someData != null}><SomeComponent id={this.props.id} /></LoadingContainer></div>)
    }
  });

while it adds another stateless wrapper, it gives a clean way to make sure your components dont just load on mount and a common place to show api feedback etc. and with react 14 these sort of pure stateless wrappers are getting a bit of a push, with perf improvements to come, so we've found it scales nicely

steve
  • 778
  • 1
  • 8
  • 18
-1

This is the pattern which will help you in getting your individual components to manage user interactions

var MyComponent = React.createClass({
getInitialState: function() {
  return {
    item: [],
    loading: true,
  };
},
componentDidMount: function() {
  //Make your API calls here
  var self = this;
  $.ajax({
    method: 'GET',
    url: 'http://jsonplaceholder.typicode.com/posts/1',
    success: function(data) {
      if (self.isMounted()) {
        self.setState({
          item: data,
          loading: false
        });
      }
    }
  });
},
render: function() {
  var componentContent = null;
  if (this.state.loading) {
    componentContent = (<div className="loader"></div>);
  } else {
    componentContent = (
      <div>
        <h4>{this.state.item.title}</h4>
        <p>{this.state.item.body}</p>
      </div>
    );
  }
  return componentContent;
}});
Nafiul Islam
  • 1,220
  • 8
  • 20
  • How is this different from what I posted? – rodrigo-silveira Jul 25 '15 at 18:10
  • What you've posted (which is a good approach) will work just fine most of the times, but there's a chance of dependent actions (if any) to be executed first and completely change your ui operations. On a large scale, Reflux comes in handy, but you need to be careful in using your stores. On the other hand, not decoupling component specific logic in stores will really help you in overcoming these sort of problems – Nafiul Islam Jul 25 '15 at 20:01