2
votes

showing a toast

My async actions tend to look something like this:

anAsyncAction: process(function* anAsyncAction() {
    self.isLoading = true;
    const service = getEnv<IMyMarksPageStoreEnv>(self).myService;
    try
    {
        yield service.doSomething();
    }        
    finally
    {
        self.isLoading = false;
    }
}),

Then I let the view handle what toasts to show:

toaster = Toaster.create({
    position: Position.TOP
});

render() {
    return <button disabled={this.props.store.isLoading} onClick={this.handleButtonClicked}>Do Async Thing</button>
}

handleButtonClicked = () => {
    const store = this.props.store;
    try
    {
        await store.anAsyncAction();
        toaster.show({ message: "Saved!", intent: Intent.SUCCESS });
    }
    catch(e)
    {
        toaster.show({ message: "Whoops an error occured: "+e, intent: Intent.DANGER });
    }
}

But im starting to think that the toasts handling should live in the async try-catch of the store and not the view, but then its mixing business logic with view, so im not sure.

Any suggestions?

2

2 Answers

1
votes

I'd argue that messages are part of the application. In my app I have an array at root level

export default types.model('AppStore', {
  ....
  flashMessages: types.optional(types.array(FlashMessage), []),
})
  .actions((self) => {
    /* eslint-disable no-param-reassign */
    const addFlashMessage = (message) => {
      self.flashMessages.push(FlashMessage.create({
        ...message,
        details: message.details.toString(),
      }));
    };

    function addErrorMessage(text, details = '') {
      addFlashMessage({ type: 'error', text, details });
    }

    function addInfoMessage(text, details = '') {
      addFlashMessage({ type: 'info', text, details });
    }

    function addSuccessMessage(text, details = '') {
      addFlashMessage({ type: 'success', text, details });
    }

Then

@inject('appStore')
@observer
class App extends Component {
  render() {
    const app = this.props.appStore;

    return (
      <BlockUI tag="div" blocking={app.blockUI}>
        <Notifications messages={app.unseenFlashMessages} />
    ...

And in a component

this.props.appStore.addSuccessMessage(`User ${name} saved`);

This will also allow you to implement a 'last 5 messages' sort of thing which might be useful if you've missed a to

0
votes

Guess that's not specific to mobx or mobx-state-tree, but I'd probably consider adding a dedicated NotificationStore to the picture. Service try/catch/finally would be one producer of notifications with a source of service, another might be a fetch/xhr wrapper with a source of transport. It would be up to the business logic to decide how to present/handle those.