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

2

8

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!

Emmen answered 9/3, 2016 at 12:45 Comment(0)
B
5

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(); });
},
Breadwinner answered 24/3, 2016 at 22:25 Comment(0)
A
4

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>
  );
}
Additive answered 9/3, 2016 at 14:33 Comment(3)
The Simple component represents any other component from app that will be show a message using the Messages component too. A lot of components, in different situations, will need to show a message.Emmen
In this case, The 'Messages' component should be a smart component and listen for changes on the message store itself... all you should then do is render the component wherever you want messages to show and it will handle the displaying of them. hope that helpsAdditive
Yes, he already listening the store (MessagesStore) for new messages added for another components by the MessageAction. But when I do this occur the problem that I related.Emmen

© 2022 - 2024 — McMap. All rights reserved.