Use ember-concurrency in submit buttons throughout the app
See original GitHub issueIssue Description
Problem
Many instances of the app where you’re POST
-ing to a server you can hit the submit button multiple times. The button is not disabled and the form submission is a repeatable process.
This is bad because the app can get into a weird state, like in onboarding where multiple PATCH
requests may come through and the state_transition
is outdated by the time later requests hit the server.
Subtasks
- Identify all instances where a button does not become disabled or show proper loading state to the user
- Create a proper async button (or use an external library)
- Replace instances of these buttons with the new behavior
- Test, test, test
Identified instances where we don’t handle it:
start/{hello, interests, skills, expertise}.hbs
- can spam click next step, will fail due to trying to transition into invalid state. Should probably binddisable
tomodel.isSaving
, or we can just have the continue action return if model is currently saving. Other option might even be better, since short flashes of a disabled state on the button can be perceived badly. Thecontinue
action is defined inmixins/onboarding-controller.js
components/create-comment-form
- has just a class binding:class="default small right {{if comment.isSaving "disabled"}}"
which is not enough. User can still click, it just looks like they can’t. Pretty sure you can end up creating multiple identical comments by spam-clicking here.components/editor-with-preview
- can spam click the “preview” tab, causing multiple previews to get generated. Doesn’t really affect much, but no point in making overhead, so we should fix it. One cleaner way I can think of is to either render different set of tab headers based on current state (editing or previewing), where only one of the headers is bound to an action in each state. The other is to simply return from the action ifisLoading
(which we already have as a flag) istrue
. I don’t think binding to adisabled
state is a great idea, since the button is styled as a tab, not a regular UI button.components/login-form
bind to a form action, instead of binding the form button directly. There is no model to deal with, so our best bet is aisLoggingIn
flag, to disable/enable the button.components/member-list-item
- organization admins can approve or deny a pending membership here. Neither of the two buttons are being handled. We have the membership model, so we can binddisabled
tomodel.isSaving
components/organization-settings-form
- save button is not handled. It’s an update, so there’s little danger of doing something wrong, but we should prevent spam none-the-less, probably by bindingdisabled
toorganization.isSaving
components/project-details
- non-member can join organization here. The “Join Project” button will quickly switch to “Membership Pending”, but there is a small window where the user could conceivably spam-click and issue multiple “create” requests. Since it’s a create request, we don’t have a model, so anisLoading
flag would work. Model could work as well, if we assign is as a component property when initialising.components/project-long-description
- the “Save” button beneath the markdown editor is not handled. Since it does an update, we have the projectsmodel.isSaving
to rely on. This is another one of those where spamming will likely work fine, but better to prevent it anyway.components/project-settings-form
- same as previous. “Save” button needs handling. Can do it by binding toproject.isSaving
.components/project-tasks-list
- most filter buttons aren’t being handled properly, but I don’t really think it’s necessary in this casecomponents/signup-form
- no handling at all. We need a flag here, since there’s no model, or we need to assign the newly created user as a component property and binddisabled
touser.isSaving
components/task-details
- “Save” (saveTaskBody) isn’t being handled. Not really important since it’s an update, but should prevent spam anyway. We have a task model, so we bind totask.isSaving
components/task-new-form
- “Submit” is not handled. Creates a new task, so we either need anisLoading
flag, or we need to assign the newly created task to the component and bind tonewTask.isSaving
components/task-title
- Save button is not handled. We should bind totask.isSaving
to prevent spam, though spam won’t cause errors, just unnecessary to have it.components/user-settings-form
- Save button is not handled. We can bind touser.isSaving
to prevent spam, though spam probably won’t cause errors.
Where we do handle it
components/category-item
- we have anisLoading
flag on the component. Button to add/remove category is disabled if the flag is oncomponents/comment-item
- (for displaying and editing an existing comment) - Has an{{#if comment.isSaving}}
block to hide the “save” and “cancel” buttons while the save is in progress.components/role-item
- handled by bindingdisabled
to anisLoading
flag, which we also use to show a spinner.components/skill-button
- handled by bindingdisabled
to anisLoading
flag, which we also use to show a spinner.
Where it seems like it does, but doesn’t need handling
components/task-status-button
- Toggles between closed or open. Doesn’t need handling because the state changes before save.
Anything else
This description was written awhile ago, so it’s possible there are more places to implement this.
Issue Analytics
- State:
- Created 6 years ago
- Comments:7 (7 by maintainers)
Top Results From Across the Web
ember-concurrency
ember -concurrency is an Ember Addon that makes it easy to write concise, robust, and beautiful asynchronous code. It provides you with a...
Read more >Ember Concurrency - nullvoxpopuli.com
Let's take a look at what ways that ember-concurrency makes things easier, ... onSubmit}}> <button type='submit' disabled={{this.
Read more >ember-concurrency/UPGRADING-2.x.md at master - GitHub
ember -concurrency 1.x is still available for those apps needing support back to Ember ... Use of the computed property-based task(function* () {})...
Read more >Using Asynchronous Tasks in Ember.js With Ember ... - YouTube
In this video I discuss the Ember Concurrency addon and how to use it. ember - concurrency is a small but powerful library...
Read more >A Day in the Life of an Ember Concurrency Task... | Blog
If necessary, we can get around this duplication effect by defining our tasks in singletons. In Ember, routes, controllers and services are all...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Happy to PR something this weekend 👍 . I love this hacktoberfest stuff! I just come across this repo and as an Elixir/Ember dev, this sounds great!
Identified instances where we don’t handle it:
start/{hello, interests, skills, expertise}.hbs
- can spam click next step, will fail due to trying to transition into invalid state. Should probably binddisable
tomodel.isSaving
, or we can just have the continue action return if model is currently saving. Other option might even be better, since short flashes of a disabled state on the button can be perceived badly. Thecontinue
action is defined inmixins/onboarding-controller.js
components/create-comment-form
- has just a class binding:class="default small right {{if comment.isSaving "disabled"}}"
which is not enough. User can still click, it just looks like they can’t. Pretty sure you can end up creating multiple identical comments by spam-clicking here.components/editor-with-preview
- can spam click the “preview” tab, causing multiple previews to get generated. Doesn’t really affect much, but no point in making overhead, so we should fix it. One cleaner way I can think of is to either render different set of tab headers based on current state (editing or previewing), where only one of the headers is bound to an action in each state. The other is to simply return from the action ifisLoading
(which we already have as a flag) istrue
. I don’t think binding to adisabled
state is a great idea, since the button is styled as a tab, not a regular UI button.components/login-form
bind to a form action, instead of binding the form button directly. There is no model to deal with, so our best bet is aisLoggingIn
flag, to disable/enable the button.components/member-list-item
- organization admins can approve or deny a pending membership here. Neither of the two buttons are being handled. We have the membership model, so we can binddisabled
tomodel.isSaving
components/organization-settings-form
- save button is not handled. It’s an update, so there’s little danger of doing something wrong, but we should prevent spam none-the-less, probably by bindingdisabled
toorganization.isSaving
components/project-details
- non-member can join organization here. The “Join Project” button will quickly switch to “Membership Pending”, but there is a small window where the user could conceivably spam-click and issue multiple “create” requests. Since it’s a create request, we don’t have a model, so anisLoading
flag would work. Model could work as well, if we assign is as a component property when initialising.components/project-long-description
- the “Save” button beneath the markdown editor is not handled. Since it does an update, we have the projectsmodel.isSaving
to rely on. This is another one of those where spamming will likely work fine, but better to prevent it anyway.components/project-settings-form
- same as previous. “Save” button needs handling. Can do it by binding toproject.isSaving
.components/project-tasks-list
- most filter buttons aren’t being handled properly, but I don’t really think it’s necessary in this casecomponents/signup-form
- no handling at all. We need a flag here, since there’s no model, or we need to assign the newly created user as a component property and binddisabled
touser.isSaving
components/task-details
- “Save” (saveTaskBody) isn’t being handled. Not really important since it’s an update, but should prevent spam anyway. We have a task model, so we bind totask.isSaving
components/task-new-form
- “Submit” is not handled. Creates a new task, so we either need anisLoading
flag, or we need to assign the newly created task to the component and bind tonewTask.isSaving
components/task-title
- Save button is not handled. We should bind totask.isSaving
to prevent spam, though spam won’t cause errors, just unnecessary to have it.components/user-settings-form
- Save button is not handled. We can bind touser.isSaving
to prevent spam, though spam probably won’t cause errors.Where we do handle it
components/category-item
- we have anisLoading
flag on the component. Button to add/remove category is disabled if the flag is oncomponents/comment-item
- (for displaying and editing an existing comment) - Has an{{#if comment.isSaving}}
block to hide the “save” and “cancel” buttons while the save is in progress.components/role-item
- handled by bindingdisabled
to anisLoading
flag, which we also use to show a spinner.components/skill-button
- handled by bindingdisabled
to anisLoading
flag, which we also use to show a spinner.Where it seems like it does, but doesn’t need handling
components/task-status-button
- Toggles between closed or open. Doesn’t need handling because the state changes before save.