A straightforward, powerful, and modern method for managing state in JavaScript and TypeScript applications. Staction supports Promises, Generators, Async/Await, and Async Generators (asyncIterable) out of the box, providing a flexible way to handle both simple and complex state logic.
Because sometimes, all you really need is well-managed state and clearly defined actions.
Key Features:
- Simple & Predictable: Clear separation of state and actions.
- Async Powerhouse: Seamlessly handle asynchronous operations with Promises,
async/await, Generators, and Async Generators. - TypeScript First: Excellent TypeScript support for robust, type-safe state management.
- Middleware: Extend functionality with
preandpostaction middleware for logging, API calls, etc. - Auxiliary Data (
aux): Actions can return custom auxiliary data alongside state changes. This data can be of any type and is defined per action, perfect for things like new IDs, operation-specific status messages, or metadata. - Lightweight: No unnecessary boilerplate, keeping your focus on your application logic.
- Full Usage Guide: Comprehensive guide with examples and best practices.
- API Reference: Detailed API documentation.
import Staction, { ActionParams } from 'staction';
// 1. Define your State and Actions types
type AppState = {
count: number;
lastMessage?: string;
};
// Optional: Define AuxData types for specific actions if they use `aux`
type IncrementAux = { incrementedBy: number; timestamp: number };
type SetMessageAux = { messageLength: number; setAt: number };
// Define your action signatures
type AppActions = {
increment: (params: ActionParams<AppState, AppActions, IncrementAux>, amount?: number) => AppState;
setMessage: (params: ActionParams<AppState, AppActions, SetMessageAux>, newMessage: string) => Promise<AppState>;
};
// 2. Create a Staction instance
const staction = new Staction<AppState, AppActions>();
// 3. Implement your actions
const actionsImpl: AppActions = {
increment: ({ state, aux }, amount = 1) => {
aux({ incrementedBy: amount, timestamp: Date.now() });
return { ...state(), count: state().count + amount };
},
setMessage: async ({ state, aux }, newMessage) => {
await new Promise(resolve => setTimeout(resolve, 50)); // Simulate async work
aux({ messageLength: newMessage.length, setAt: Date.now() });
return { ...state(), lastMessage: newMessage };
},
};
// 4. Define your initial state function
const getInitialState = (/* wrappedActions */): AppState => {
// You can use wrappedActions here if needed for initial setup actions
return { count: 0 };
};
// 5. Define your state update callback (e.g., for React setState)
const onStateUpdate = (newState: AppState, wrappedActions: AppActions) => {
console.log('State Updated:', newState);
// In React: setCurrentAppState(newState);
// wrappedActions are the same as staction.actions, useful for triggering follow-up actions from callback if necessary
};
// 6. Initialize Staction (it's async!)
async function main() {
try {
console.log('Initializing Staction...', staction.initState);
await staction.init(actionsImpl, getInitialState, onStateUpdate);
console.log('Staction Initialized! Current State:', staction.state);
console.log('Is initialized:', staction.initialized);
// 7. Dispatch actions
const { state: stateAfterIncrement, aux: auxFromIncrement } = await staction.actions.increment(5);
console.log('After increment:', stateAfterIncrement);
console.log('Aux data from increment:', auxFromIncrement?.incrementedBy); // 5
const { state: stateAfterMessage, aux: auxFromMessage } = await staction.actions.setMessage('Hello Staction!');
console.log('After setMessage:', stateAfterMessage);
console.log('Message set timestamp:', auxFromMessage?.setAt);
console.log('Final state from instance:', staction.state); // Access current state directly
} catch (error) {
console.error('Error during Staction setup or action:', error);
console.error('Staction initialization state:', staction.initState);
}
}
main();Actions are the core of state changes in Staction. They can be plain functions, async functions, or even generator functions (both sync and async).
- Return Value: Actions should always yield or return data that Staction can use to determine the new state (or a Promise that resolves to it).
paramsObject: The first argument to every action is aparamsobject containing:state: () => State: A function to get the current state.actions: WrappedActions: Access to other actions.aux: (valueOrUpdater) => void: Function to set or update the auxiliary data for the action. Its type depends on theAuxDatadefined for the action.name: string: The name of the current action.
For a deep dive into actions, including handling different return types (Promises, Generators, Async Generators), see the Actions In-Depth section in the Usage Guide.
Staction is built with TypeScript and offers robust typing. Define types for your State and Actions. Auxiliary data (AuxData) types are defined on a per-action basis within ActionParams for a fully type-safe experience.
Refer to the TypeScript Usage section in the Usage Guide for detailed examples.
A common pattern involves using React Context to provide the Staction instance to your component tree.
See the React Integration Example in the Usage Guide for a complete example.
Once you have const staction = new Staction<MyState, MyActions>():
staction.init(actions, initFunc, stateSetCallback): (Async) Initializes the store. See Initialization Lifecycle.staction.actions: An object containing your wrapped actions. Callingstaction.actions.myAction(...args)returnsPromise<{ state: NewState, aux: AuxDataForThatAction | undefined }>.staction.state: The current, read-only state.staction.getState(): Method to get the current state.staction.initialized: Boolean getter,trueif init was successful.staction.initState: String getter for detailed initialization status ('uninitialized','initializing','initialized','initerror').- Logging Methods:
enableLogging(),disableLogging(),enableStateWhenLogging(),disableStateWhenLogging(). staction.setMiddleware(middlewares): Addpreorpostaction middleware.
For the complete list of methods and properties, check the API Reference.
Extend Staction's functionality using middleware. Middleware functions can run before (pre) or after (post) an action, allowing you to log, modify state, or trigger side effects.
const myMiddleware = {
type: 'pre', // or 'post'
method: ({ state, name, args, meta }) => {
console.log(`Action '${name}' is about to run.`);
// Middleware can also return new state, Promises, or be generators
},
meta: { /* your custom metadata */ }
};
staction.setMiddleware([myMiddleware]);Learn more about Middleware in the Usage Guide.
- Full Usage Guide: For comprehensive explanations, examples, and best practices.
- API Reference: For detailed information on all classes, methods, and types.
Contributions, issues, and feature requests are welcome!