|
|
|
@@ -1,12 +1,12 @@
|
|
|
|
|
# @push.rocks/smartstate
|
|
|
|
|
a package that handles state in a good way
|
|
|
|
|
A package for handling and managing state in applications
|
|
|
|
|
|
|
|
|
|
## Install
|
|
|
|
|
|
|
|
|
|
To install `@push.rocks/smartstate`, you can use npm (Node Package Manager). Run the following command in your terminal:
|
|
|
|
|
To install `@push.rocks/smartstate`, you can use pnpm (Performant Node Package Manager). Run the following command in your terminal:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm install @push.rocks/smartstate --save
|
|
|
|
|
pnpm install @push.rocks/smartstate --save
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This will add `@push.rocks/smartstate` to your project's dependencies.
|
|
|
|
@@ -31,17 +31,30 @@ import { Smartstate, StatePart, StateAction } from '@push.rocks/smartstate';
|
|
|
|
|
const myAppSmartState = new Smartstate<YourStatePartNamesEnum>();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Understanding Init Modes
|
|
|
|
|
|
|
|
|
|
When creating state parts, you can specify different initialization modes:
|
|
|
|
|
|
|
|
|
|
- **`'soft'`** - Allows existing state parts to remain (default behavior)
|
|
|
|
|
- **`'mandatory'`** - Fails if there's an existing state part with the same name
|
|
|
|
|
- **`'force'`** - Overwrites any existing state part
|
|
|
|
|
- **`'persistent'`** - Enables WebStore persistence using IndexedDB
|
|
|
|
|
|
|
|
|
|
### Defining State Parts
|
|
|
|
|
|
|
|
|
|
State parts represent separable sections of your state, making it easier to manage and modularize. For example, you may have a state part for user data and another for application settings.
|
|
|
|
|
|
|
|
|
|
Define an enum for state part names for better management:
|
|
|
|
|
Define state part names using either enums or string literal types:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// Option 1: Using enums
|
|
|
|
|
enum AppStateParts {
|
|
|
|
|
UserState,
|
|
|
|
|
SettingsState
|
|
|
|
|
UserState = 'UserState',
|
|
|
|
|
SettingsState = 'SettingsState'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Option 2: Using string literal types (simpler approach)
|
|
|
|
|
type AppStateParts = 'UserState' | 'SettingsState';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Now, let's create a state part within our `myAppSmartState` instance:
|
|
|
|
@@ -54,8 +67,11 @@ interface IUserState {
|
|
|
|
|
|
|
|
|
|
const userStatePart = await myAppSmartState.getStatePart<IUserState>(
|
|
|
|
|
AppStateParts.UserState,
|
|
|
|
|
{ isLoggedIn: false } // Initial state
|
|
|
|
|
{ isLoggedIn: false }, // Initial state
|
|
|
|
|
'soft' // Init mode (optional, defaults to 'soft')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Note: Persistent state parts are automatically initialized internally
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Subscribing to State Changes
|
|
|
|
@@ -93,27 +109,110 @@ const loginUserAction = userStatePart.createAction<ILoginPayload>(async (statePa
|
|
|
|
|
|
|
|
|
|
// Dispatch the action to update the state
|
|
|
|
|
loginUserAction.trigger({ username: 'johnDoe' });
|
|
|
|
|
// or await the result
|
|
|
|
|
const newState = await loginUserAction.trigger({ username: 'johnDoe' });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Persistent State
|
|
|
|
|
### Dispatching Actions
|
|
|
|
|
|
|
|
|
|
`Smartstate` supports the concept of persistent states, where you can maintain state across sessions. To utilize this, specify a persistent mode when getting a state part:
|
|
|
|
|
There are two ways to dispatch actions:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
const settingsStatePart = await myAppSmartState.getStatePart<AppStateParts, ISettingsState>(
|
|
|
|
|
// Method 1: Using trigger on the action (returns promise)
|
|
|
|
|
const newState = await loginUserAction.trigger({ username: 'johnDoe' });
|
|
|
|
|
// or fire and forget
|
|
|
|
|
loginUserAction.trigger({ username: 'johnDoe' });
|
|
|
|
|
|
|
|
|
|
// Method 2: Using dispatchAction on the state part (returns promise)
|
|
|
|
|
const newState = await userStatePart.dispatchAction(loginUserAction, { username: 'johnDoe' });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Both methods return a Promise with the new state, giving you flexibility in how you handle the result.
|
|
|
|
|
|
|
|
|
|
### Additional State Methods
|
|
|
|
|
|
|
|
|
|
`StatePart` provides several useful methods for state management:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// Wait for a specific state condition
|
|
|
|
|
await userStatePart.waitUntilPresent();
|
|
|
|
|
|
|
|
|
|
// Wait for a specific property to be present
|
|
|
|
|
await userStatePart.waitUntilPresent(state => state.username);
|
|
|
|
|
|
|
|
|
|
// Setup initial state with async operations
|
|
|
|
|
await userStatePart.stateSetup(async (statePart) => {
|
|
|
|
|
// Perform async initialization
|
|
|
|
|
const userData = await fetchUserData();
|
|
|
|
|
return { ...statePart.getState(), ...userData };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Defer notification to end of call stack
|
|
|
|
|
userStatePart.notifyChangeCumulative();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Persistent State with WebStore
|
|
|
|
|
|
|
|
|
|
`Smartstate` supports persistent states using WebStore (IndexedDB-based storage), allowing you to maintain state across sessions:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
const settingsStatePart = await myAppSmartState.getStatePart<ISettingsState>(
|
|
|
|
|
AppStateParts.SettingsState,
|
|
|
|
|
{ theme: 'light' }, // Initial state
|
|
|
|
|
'persistent' // Mode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Note: init() is called automatically for persistent mode
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This mode ensures that the state is saved and can be reloaded even after the application restarts, providing a seamless user experience.
|
|
|
|
|
Persistent state automatically:
|
|
|
|
|
- Saves state changes to IndexedDB
|
|
|
|
|
- Restores state on application restart
|
|
|
|
|
- Manages storage with configurable database and store names
|
|
|
|
|
|
|
|
|
|
### Performance Optimization
|
|
|
|
|
|
|
|
|
|
`Smartstate` includes built-in performance optimizations:
|
|
|
|
|
|
|
|
|
|
- **State Change Detection**: Detects actual state changes to prevent unnecessary notifications when state values haven't truly changed
|
|
|
|
|
- **Cumulative Notifications**: Batch multiple state changes into a single notification using `notifyChangeCumulative()`
|
|
|
|
|
- **Selective Subscriptions**: Use selectors to subscribe only to specific state properties
|
|
|
|
|
|
|
|
|
|
### RxJS Integration
|
|
|
|
|
|
|
|
|
|
`Smartstate` leverages RxJS for reactive state management:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// State is exposed as an RxJS Subject
|
|
|
|
|
const stateObservable = userStatePart.select();
|
|
|
|
|
|
|
|
|
|
// Automatically starts with current state value
|
|
|
|
|
stateObservable.subscribe((state) => {
|
|
|
|
|
console.log('Current state:', state);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Use selectors for specific properties
|
|
|
|
|
userStatePart.select(state => state.username)
|
|
|
|
|
.pipe(
|
|
|
|
|
distinctUntilChanged(),
|
|
|
|
|
filter(username => username !== undefined)
|
|
|
|
|
)
|
|
|
|
|
.subscribe(username => {
|
|
|
|
|
console.log('Username changed:', username);
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Comprehensive Usage
|
|
|
|
|
|
|
|
|
|
Putting it all together, `@push.rocks/smartstate` offers a flexible and powerful pattern for managing application state. By modularizing state parts, subscribing to state changes, and controlling state modifications through actions, developers can maintain a clean and scalable architecture. Combining these strategies with persistent states unlocks the full potential for creating dynamic and user-friendly applications.
|
|
|
|
|
|
|
|
|
|
Remember to leverage TypeScript for its excellent support for types and interfaces, enhancing your development experience with type checking and IntelliSense, ensuring a more reliable and maintainable codebase.
|
|
|
|
|
Key features:
|
|
|
|
|
- **Type-safe state management** with full TypeScript support
|
|
|
|
|
- **Reactive state updates** using RxJS observables
|
|
|
|
|
- **Persistent state** with IndexedDB storage
|
|
|
|
|
- **Performance optimized** with state hash detection
|
|
|
|
|
- **Modular architecture** with separate state parts
|
|
|
|
|
- **Action-based updates** for predictable state modifications
|
|
|
|
|
|
|
|
|
|
For more complex scenarios, consider combining multiple state parts, creating hierarchical state structures, and integrating with other state management solutions as needed. With `@push.rocks/smartstate`, the possibilities are vast, empowering you to tailor the state management approach to fit the unique requirements of your project.
|
|
|
|
|
|
|
|
|
|