Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React and Flux: "dispatch in the middle of a dispatch" to show error message from an API call

Tags:

reactjs

flux

Im using Flux, React and I have the components Simple and Messages:

  • Simple: it'is a simple component that calls an API by an Action. The Action do a ajax request and dispatch the result in jqXHR.done(). Simple have a change listener to wait the dispatch of the result. If the result is null, I would like to show an error using my Messages component, so I call my MessagesAction.addError('My result is null').
  • Messages: a component that show errors for the application. This component have a change listener waiting for new messages to display. It's placed in the header of my application.

The problem occur when I receive the null result and immediately calls the MessagesAction.addError inside the Simple component. In fact, I know that this can result in "Dispatch in the middle of a dispatch" error, but I don't know how refactor this code to show the error message using Flux.

Disclaimer 1: I can't use setTimeout function to resolve this problem. This is not the right solution.

Disclaimer 2: The Simple component represents any other component from app that will be show a message using the Messages component too.

Simple code:

findUser: function (value) {
  UserAction.find(value);
},

componentDidMount: function () {
  UserStore.addChangeListener(this.updateUser);
},

updateUser: function(){
  var user = UserStore.getUser();
  if (user == null){
     MessagesAction.addError('My result is null!'); //here occur the error!
  } else {
     //set my user with setState
  }
},

Messages code:

componentDidMount: function () {
    MessagesStore.addChangeListener(this.addMessage);
},

addMessage: function () {
    this.setState({
        messages: MensagensStore.getMessages()
    });
},

Thanks!

like image 378
Dherik Avatar asked Mar 09 '16 12:03

Dherik


2 Answers

Well, the problem is that (at least in Facebook's Dispatcher implementation) you must not trigger any action inside the store callbacks, which would lead to undesired/unpredictable behaviour, like infinite dispatching, or inconsistent state changes (e.g. race conditions). This is due to the nature of a single broadcasting dispatcher.

The IMHO cleanest solution (without smelling waitFor()) is to introduce an internal state in the triggering component. Using the state you trigger your message action in the next update cycle. This way, you don't have the problem of non-finished dispatch cylces.

// your component's implementation

getInitialState : function(){
  return { user : undefined };
}


componentWillMount: function () {
  UserStore.addChangeListener(this.updateUser);
},

componentWillUnmount: function () {
  UserStore.removeChangeListener(this.updateUser);
},


componentDidUpdate : function(){
  if(this.state.user == null){
    MessagesAction.addError('My result is null!'); // no error anymore!
  }
},

updateUser: function(){
  this.setState({ user: UserStore.getUser(); });
},
like image 163
ohager Avatar answered Nov 16 '22 03:11

ohager


Your top level container should listen for changes on both the UserStore and the MessageStore.

The MessageStore should 'waitFor' the Userstore, derive it's state (i.e update it's messages property) from the UserStore and then emit a change.

Something like this: Top level UserContainer component

findUser(value) {
   UserAction.find(value);
}

componentDidMount () {
  UserStore.addChangeListener(this.updateState);
  MessageStore.addChangeListener(this.updateState);
}

updateState(){
  this.setState({
     user: UserStore.getUser(),
     messages: MessageStore.getMessages()
  });
}

renderMessages() {
   if(!this.state.messages) return null;

   return (
    <Messages messages={messages} />
   );
}

renderUser() {
   if(!this.state.user) return null;

   return (
    <User {...this.state.user} />
   );
}

render() {
  return(
     <div>
       {this.renderMessages()}
       {this.renderUser()}
     </div>
  );
}
like image 27
Mark Avatar answered Nov 16 '22 02:11

Mark