Migration guide: v0.12.x → v0.13.0
This guide will help you migrate your codebase from use-less-react v0.12.x to v0.13.0. This release introduces the new createRollbackScope utility for transactional state management and includes important breaking changes to the Memento Pattern implementation.
Breaking Changes
1. getMemento() No Longer Takes Parameters
The getMemento() method in BaseOriginator has been simplified and no longer accepts a state parameter.
Before (v0.12.x):
class MyOriginator extends BaseOriginator<MyState> {
// ...
override getState() {
return {
field1: this.field1,
field2: this.field2,
};
}
override getMemento(state: MyState) {
return state; // or some transformation of state
}
override restoreMemento(memento: MyState) {
this.field1 = memento.field1;
this.field2 = memento.field2;
}
}
After (v0.13.0):
class MyOriginator extends BaseOriginator<MyState> {
// ...
// getState() is no longer required for BaseOriginator
override getMemento() {
return {
field1: this.field1,
field2: this.field2,
};
}
override restoreMemento(memento: MyState) {
this.field1 = memento.field1;
this.field2 = memento.field2;
}
}
Migration steps:
- Remove the
stateparameter from yourgetMemento()implementation - Move the logic from
getState()intogetMemento() - Remove the
getState()method from yourBaseOriginatorimplementations
2. getState() Removed from AbstractOriginatorProps
The getState() method has been removed from the AbstractOriginatorProps interface and is now only available in DiffOriginator where it's needed for diff tracking.
Impact:
BaseOriginatorimplementations no longer requiregetState()- Only
DiffOriginatorimplementations needgetState() - This change provides cleaner separation between Base and Diff originator hierarchies
Before (v0.12.x):
// Both BaseOriginator and DiffOriginator required getState()
class MyBaseOriginator extends BaseOriginator<State> {
override getState() { /* ... */ }
override getMemento(state: State) { /* ... */ }
override restoreMemento(memento: State) { /* ... */ }
}
class MyDiffOriginator extends DiffOriginator<State> {
override getState() { /* ... */ }
override getMemento(state: State) { /* ... */ }
override restoreMemento(memento: State) { /* ... */ }
}
After (v0.13.0):
// BaseOriginator doesn't need getState()
class MyBaseOriginator extends BaseOriginator<State> {
override getMemento() { /* ... */ }
override restoreMemento(memento: State) { /* ... */ }
}
// DiffOriginator still requires getState()
class MyDiffOriginator extends DiffOriginator<State> {
override getState() { /* ... */ }
override getMemento() { /* ... */ }
override restoreMemento(memento: State) { /* ... */ }
}
New Features
createRollbackScope - Transactional State Management
Version 0.13.0 introduces createRollbackScope, a powerful utility that brings database-style transaction semantics to your client-side state management.
Key features:
- Atomic operations - all state changes succeed or all are rolled back
- Automatic rollback - on exceptions or explicit rollback calls
- Nested transactions - compose complex workflows
- Configurable notifications - batched or standard notification modes
- Type-safe - full TypeScript support
Basic usage:
import { createRollbackScope, rollback } from '@dxbox/use-less-react/classes';
const scope = createRollbackScope(myOriginator)
.withBatchedNotifications();
await scope.begin(async () => {
myOriginator.value = 'new value';
const result = await apiCall();
if (!result.success) {
rollback(); // Revert all changes
}
});
Notification modes:
.withBatchedNotifications()- Batches all notifications, single render.withStandardNotifications()- Sends notifications immediately as changes occur
For complete documentation, see the createRollbackScope API reference.
Step-by-Step Migration
Step 1: Update Dependencies
Update your package.json:
npm install @dxbox/use-less-react@0.13.0
Step 2: Update BaseOriginator Implementations
For each BaseOriginator in your codebase:
-
Remove the
stateparameter fromgetMemento():// Before
override getMemento(state: MyState) {
return state;
}
// After
override getMemento() {
return {
field1: this.field1,
field2: this.field2,
};
} -
Remove
getState()method:// Before
override getState() {
return { field1: this.field1, field2: this.field2 };
}
// After
// Just remove this method entirely -
Merge logic: Put your
getState()logics intogetMemento():// Before
override getState() {
return {
field1: this.field1,
field2: this.transformField(this.field2),
};
}
override getMemento(state: MyState) {
return state;
}
// After
override getMemento() {
return {
field1: this.field1,
field2: this.transformField(this.field2),
};
}
Step 3: Verify DiffOriginator Implementations
For DiffOriginator implementations, ensure they still have getState():
class MyDiffOriginator extends DiffOriginator<State> {
override getState() {
return { /* current state */ };
}
override getMemento() { // No state parameter
return { /* memento data */ };
}
override restoreMemento(memento: State) {
// Restore logic
}
}
Step 4: Test Your Implementation
Run your tests to ensure:
- All originator implementations compile without errors
- State snapshots are created correctly
- Undo/redo functionality still works as expected
- Memento restoration works properly
Common Migration Patterns
Pattern 1: Simple State Capture
Before:
class CounterOriginator extends BaseOriginator<{ count: number }> {
private _count = 0;
get count() { return this._count; }
set count(v: number) {
this._count = v;
this.notify('count');
}
override getState() {
return { count: this._count };
}
override getMemento(state: { count: number }) {
return state;
}
override restoreMemento(memento: { count: number }) {
this._count = memento.count;
}
}
After:
class CounterOriginator extends BaseOriginator<{ count: number }> {
private _count = 0;
get count() { return this._count; }
set count(v: number) {
this._count = v;
this.notify('count');
}
override getMemento() {
return { count: this._count };
}
override restoreMemento(memento: { count: number }) {
this._count = memento.count;
}
}
Pattern 2: Complex State with Transformation
Before:
class FormOriginator extends BaseOriginator<FormState> {
private _data: FormData = {};
private _errors: Errors = {};
override getState() {
return {
data: { ...this._data },
errors: { ...this._errors },
timestamp: Date.now(),
};
}
override getMemento(state: FormState) {
// Only save data, not errors or timestamp
return { data: state.data };
}
override restoreMemento(memento: { data: FormData }) {
this._data = { ...memento.data };
}
}
After:
class FormOriginator extends BaseOriginator<FormState> {
private _data: FormData = {};
private _errors: Errors = {};
override getMemento() {
// Only save data, not errors or timestamp
return { data: { ...this._data } };
}
override restoreMemento(memento: { data: FormData }) {
this._data = { ...memento.data };
}
}
Troubleshooting
TypeScript Errors
Error: "Property 'getState' does not exist on type 'BaseOriginator'"
Solution: Remove calls to getState() on BaseOriginator instances. If you need to access the current state, access the properties directly or create a custom getter method.
Error: "Expected 0 arguments, but got 1" on getMemento()
Solution: Remove the state parameter from your getMemento() implementation.
Runtime Errors
Issue: Memento is null or contains incorrect data
Solution: Ensure your getMemento() implementation correctly captures all necessary state directly from instance properties, not from a state parameter.
Issue: State not restoring correctly after undo
Solution: Verify that getMemento() captures the same fields that restoreMemento() expects.
Need Help?
If you encounter issues during migration:
- Check the createRollbackScope documentation
- Review the Memento Pattern documentation
- Open an issue on GitHub