No more (╯°□°)╯︵ ┻━┻ because now you have an Async Manager

Marc Cyr
hello JS
Published in
4 min readFeb 4, 2018

--

How annoying is it when you click “Save” and then nothing happens? You’re a developer, so you pop open the browser console and check to see if there are errors… nothing. So, you wait and watch the “Network” tab. Eventually, you see a resolution of an Ajax call come through and sure enough, you see a message in the UI that your save was successful. This doesn’t make you any less frustrated, and it doesn’t make your blood pressure un-spike :) haha. I kid a bit, of course, but this is definitely a frustrating user experience when it happens.

A better user experience is when you see “Save” become “Saving…” This is actually happening as I type, but a bit differently. In Medium, as you write a story, every so often you see something at the top of the page that says, “Saving…” and then it flips to “Saved” and then disappears. This is contextual labeling that makes for a much better experience than leaving the user in the dark about what is happening.

Yes, it may seem obvious to do this, but there are reasons why applications sometimes do not. One downside is that every UI element has its own APIs it uses. Yes, you likely have generic components, so you use the same Button everywhere… but it’s not the same API everywhere. In one place, you’re saving a search and in another you’re fetching an image (as examples). These talk to different APIs. So, if you want to have this behavior everywhere, you have to add it to a wrapper component everywhere. Or do you? …

Obviously, I wouldn’t be writing this if I didn’t have an answer to that question. No, you don’t have to do touch every component by writing a wrapper component. You can handle everything by creating a central location where things can be managed. I call this an Async Manager and it is pretty simple, while being incredibly useful (the best kind of code).

First off, as always, I’m talking about applications that use React + Redux. Also, in my example I am going to reference using ImmutableJS, but that is really optional. React + Redux is what makes this all work. Alright, admin items done :)

The Async Manager is a simple system: it is a List of Async IDs. When an async call happens, an id gets added to the List. When the call resolves, the id is removed from the List.

The reducer is pretty simple and only really needs to listen for a “begin” and an “end” action. Here is a code snippet:

For the Action Creators, the file is also amazingly simple:

See what I mean? It’s pretty simple. You’re just maintaining a list of ids. When the async call starts, add the id. When it resolves, remove it.

Next, you have to wire it into places in your application intentionally. For us, I added it to our centrally-located Ajax logic. We have a module that does everything for us, including error handling. In that module, it was a simple matter of adding the use of begin and the use of end in the right places. Specifically, add begin where the call originates, and end where it resolves with a success callback AND where it resolves with an error.

With that in place, you now have a pretty simple task: identify your core components (at least initially) that you want to take advantage of this architecture. Give those components the ability to make use of an Async ID. For us, the first and main place was our Button component. We use constants throughout our app and organize them in our own way. So, what we did was add a file to manage all constants related to our async manager. Its purposes are pretty simple: 1) store all async id constants used anywhere in the application so that engineers can look at one central location to see them and 2) store a mapping of active tense based upon i18n keys. For example, save maps to saving. We use i18n throughout our application, so everything gets translated before being displayed to the user, so this mapping just has to use our keys.

The Button component now has a simple job: if it has an async id, check the List of all async ids and see if the current instance’s async id is in the list. If it is, change the tense of what the user sees using the mapping discussed above.

The end result looks like this: the user clicks a button to save something, the button becomes disabled and the label changes to “Saving…” while the call is happening. Once the call resolves, the button returns to its original state and the user now sees a success message (not related to this discussion — this is a result of our core system messaging).

What does this mean for the engineer? It means that to get this desired behavior from a button, they simply add an async id to the constants, and use that constant as the asyncId prop for the button AND they pass it to the options for the async call (we use a core requestHandler which takes options). As a bonus, this works for forms and pretty much anything else in the UI. To add it to any other component, it’s a simple matter of adding a prop and then using it, so it’s very extensible.

The biggest win though, in my opinion, is that it is another way to make the Developer Experience awesome while also providing solid User Experience.

About me: I’m the Lead UI Architect at RedLock https://www.redlock.io Check us out and definitely check out our Career page at http://careers.redlock.io/ if you like solving awesome problems while working on the coolest tech ever.

--

--

Husband and father. Head of UI for Prisma Cloud at Palo Alto Networks running a large engineering team. Angel investor in Silicon Valley. https://20x.capital