원클릭으로
add-optimistic-command
Add optimisticCommand for blocking one-time actions with immediate UI updates and automatic rollback on failure
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Add optimisticCommand for blocking one-time actions with immediate UI updates and automatic rollback on failure
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Add custom error handling with catchError to a mix() call for logging, error conversion, or suppression
Add internet connectivity checking before executing a Cubit method with optional retry
Add debounce to delay method execution until after a period of inactivity for search or validation
Add EffectQueue to state for triggering multiple sequential one-time UI effects
Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing
Add error state handling to widgets using context.isFailed() and context.getException()
| name | add-optimistic-command |
| description | Add optimisticCommand for blocking one-time actions with immediate UI updates and automatic rollback on failure |
This skill adds optimisticCommand for one-time actions with immediate UI updates and automatic rollback on failure.
Implements optimistic updates for blocking, one-time operations like:
The UI updates immediately, the server operation runs, and on failure the UI rolls back.
Use optimisticCommand when:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class TodoCubit extends Cubit<TodoState> {
TodoCubit() : super(const TodoState());
void addTodo(Todo newTodo) => optimisticCommand(
key: (AddTodo, newTodo.id),
// 1. Return the optimistic value (new list with item added)
optimisticValue: () => state.todoList.add(newTodo),
// 2. Extract the value from current state
getValueFromState: (state) => state.todoList,
// 3. Apply a value back to state
applyValueToState: (state, value) =>
state.copyWith(todoList: value as List<Todo>),
// 4. Send the command to the server
sendCommandToServer: (optimisticValue) async {
await api.saveTodo(newTodo);
return null; // Return server response if needed
},
);
}
Rollback Safety: Rollback only occurs if the current state value still matches the optimistic value. This prevents overwriting concurrent changes made by other operations. If another update changed the value while the command was running, rollback is skipped to preserve that newer change.
| Parameter | Purpose |
|---|---|
key | Identifies the operation for state tracking and non-reentrant |
optimisticValue | Returns the immediate UI value |
getValueFromState | Extracts the relevant value from state |
applyValueToState | Applies a value back to state |
sendCommandToServer | Executes the server operation |
When the server returns data that should update the state:
void addTodo(Todo newTodo) => optimisticCommand(
key: (AddTodo, newTodo.id),
optimisticValue: () => state.todoList.add(newTodo),
getValueFromState: (state) => state.todoList,
applyValueToState: (state, value) =>
state.copyWith(todoList: value as List<Todo>),
sendCommandToServer: (optimisticValue) async {
final savedTodo = await api.saveTodo(newTodo);
return savedTodo; // Return server response
},
// Replace client-side todo with server-side todo
applyServerResponseToState: (state, serverResponse) {
final savedTodo = serverResponse as Todo;
return state.copyWith(
todoList: state.todoList
.where((t) => t.id != newTodo.id)
.toList()
..add(savedTodo),
);
},
);
Reload data after success or failure:
void deleteTodo(String todoId) => optimisticCommand(
key: (DeleteTodo, todoId),
optimisticValue: () =>
state.todoList.where((t) => t.id != todoId).toList(),
getValueFromState: (state) => state.todoList,
applyValueToState: (state, value) =>
state.copyWith(todoList: value as List<Todo>),
sendCommandToServer: (optimisticValue) async {
await api.deleteTodo(todoId);
return null;
},
// Reload after operation
reloadFromServer: () async {
return await api.loadTodoList();
},
// Control when to reload
shouldReload: ({
required currentValue,
required lastAppliedValue,
required optimisticValue,
required rollbackValue,
required error,
}) => error != null, // Only reload on failure
);
Customize how rollback works:
void addTodo(Todo newTodo) => optimisticCommand(
key: (AddTodo, newTodo.id),
// ... required params ...
// Control whether to rollback
shouldRollback: ({
required currentValue,
required initialValue,
required optimisticValue,
required error,
}) => true, // Default: always rollback on error
// Custom rollback state (e.g., show failed status instead of removing)
rollbackState: ({
required state,
required initialValue,
required optimisticValue,
required error,
}) => state.copyWith(
todoList: state.todoList.map((t) =>
t.id == newTodo.id ? t.copyWith(status: TodoStatus.failed) : t
).toList(),
),
);
Track loading state globally but prevent duplicates per item:
void addTodo(Todo newTodo) => optimisticCommand(
key: AddTodo, // isWaiting(AddTodo) shows any add in progress
nonReentrantKey: (AddTodo, newTodo.id), // Prevents duplicate for same todo
// ... rest of params ...
);
// Widget
if (context.isWaiting(AddTodo)) {
// Any add operation is in progress
}
void addItem(Item newItem) => optimisticCommand(
key: (AddItem, newItem.id),
optimisticValue: () => [...state.items, newItem],
getValueFromState: (state) => state.items,
applyValueToState: (state, value) =>
state.copyWith(items: value as List<Item>),
sendCommandToServer: (optimisticValue) async {
await api.createItem(newItem);
return null;
},
);
void deleteItem(String itemId) => optimisticCommand(
key: (DeleteItem, itemId),
optimisticValue: () =>
state.items.where((i) => i.id != itemId).toList(),
getValueFromState: (state) => state.items,
applyValueToState: (state, value) =>
state.copyWith(items: value as List<Item>),
sendCommandToServer: (optimisticValue) async {
await api.deleteItem(itemId);
return null;
},
);
void updateItem(Item updatedItem) => optimisticCommand(
key: (UpdateItem, updatedItem.id),
optimisticValue: () => state.items
.map((i) => i.id == updatedItem.id ? updatedItem : i)
.toList(),
getValueFromState: (state) => state.items,
applyValueToState: (state, value) =>
state.copyWith(items: value as List<Item>),
sendCommandToServer: (optimisticValue) async {
await api.updateItem(updatedItem);
return null;
},
);
Widget build(BuildContext context) {
final isSaving = context.isWaiting((AddTodo, todoId));
return ListTile(
title: Text(todo.title),
trailing: isSaving
? const CircularProgressIndicator()
: const Icon(Icons.check),
);
}
| Scenario | Use |
|---|---|
| Add/Delete/Update item | optimisticCommand |
| Form submission | optimisticCommand |
| Toggle (like/favorite) with fast taps | optimisticSync |
| Slider/rating value | optimisticSync |
| Settings that change rapidly | optimisticSync |
Rule: Use optimisticCommand for discrete actions, optimisticSync for values that may change rapidly.
Ask the user:
applyServerResponseToState)reloadFromServer)rollbackState)