Skip to content

Instantly share code, notes, and snippets.

@nashysolutions
Last active February 14, 2026 12:11
Show Gist options
  • Select an option

  • Save nashysolutions/781e3c8c1fd140849727dfba45f9e611 to your computer and use it in GitHub Desktop.

Select an option

Save nashysolutions/781e3c8c1fd140849727dfba45f9e611 to your computer and use it in GitHub Desktop.
Prevents user button smashing.
/// A button that ensures only a single async task executes at a time.
///
/// `SingleTaskButton` prevents double-execution of async operations by tracking the active task
/// and disabling itself while the operation is in progress. This is essential for critical
/// operations like purchases, payments, or irreversible actions where duplicate execution
/// would be harmful.
///
/// ## Example
/// ```swift
/// SingleTaskButton {
/// try await store.purchaseProduct(product)
/// } label: {
/// Text("Purchase")
/// }
/// ```
///
/// ## Features
/// - Guarantees only one task runs at a time (no race conditions)
/// - Self-disables during operation (visual feedback)
/// - Supports throwing async functions
/// - Generic over label content
///
/// ## Use Cases
/// - Financial transactions (purchases, payments)
/// - Irreversible destructive actions (permanent delete)
/// - Operations with side effects (sending emails, notifications)
struct SingleTaskButton<Label: View>: View {
/// The async action to perform when the button is tapped.
let action: () async throws -> Void
/// The button's label content.
@ViewBuilder let label: Label
/// Tracks the currently running task to prevent re-entry.
@State private var task: Task<Void, Never>?
var body: some View {
Button {
// Prevent creating a new task if one is already running
guard task == nil else { return }
task = Task {
defer { task = nil }
// Silently handle errors - callers should handle errors in their action
try? await action()
}
} label: {
label
}
.disabled(task != nil)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment