Functions that modify observable state, providing performance optimizations through batching and supporting asynchronous operations through generators. Actions ensure that all state changes are tracked and batched for optimal performance.
Wraps functions as actions that modify observable state, providing performance optimizations and debugging benefits.
/**
* Creates an action that can modify observable state
* @param fn - Function to wrap as action
* @returns Action-wrapped function
*/
function action<T extends Function>(fn: T): T;
/**
* Creates a named action for better debugging
* @param name - Debug name for the action
* @param fn - Function to wrap as action
* @returns Action-wrapped function
*/
function action<T extends Function>(name: string, fn: T): T;Usage Examples:
import { observable, action, autorun } from "mobx";
const state = observable({
count: 0,
items: []
});
// Simple action
const increment = action(() => {
state.count++;
});
// Named action for better debugging
const addItem = action("addItem", (item) => {
state.items.push(item);
state.count = state.items.length;
});
// Multiple state changes are batched in actions
const batchUpdate = action(() => {
state.count = 0;
state.items.clear();
state.items.push("first", "second", "third");
state.count = state.items.length;
// Only triggers reactions once at the end
});
autorun(() => {
console.log(`Count: ${state.count}, Items: ${state.items.length}`);
});
increment(); // Logs once
batchUpdate(); // Logs once despite multiple changesDecorator for marking class methods as actions.
/**
* Decorator interface for actions in classes
* Can be used as @action, @action(name), or @action.bound
*/
interface IActionFactory extends Annotation, PropertyDecorator {
/** Named action decorator */
(customName: string): PropertyDecorator & Annotation;
/** Auto-bound action decorator */
bound: Annotation & PropertyDecorator;
}
declare const action: IActionFactory;Usage Examples:
import { makeObservable, observable, action } from "mobx";
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action,
removeTodo: action.bound, // Auto-bound method
clearCompleted: action
});
}
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
completed: false
});
}
removeTodo = action("removeTodo", (id) => {
const index = this.todos.findIndex(todo => todo.id === id);
if (index !== -1) {
this.todos.splice(index, 1);
}
});
clearCompleted() {
this.todos = this.todos.filter(todo => !todo.completed);
}
}
// With automatic detection
class CounterAuto {
count = 0;
constructor() {
makeAutoObservable(this); // Automatically detects methods as actions
}
increment() {
this.count++;
}
decrement() {
this.count--;
}
reset() {
this.count = 0;
}
}Runs code blocks as actions without requiring function wrapping.
/**
* Runs a function as an action
* @param fn - Function to run as action
* @returns Result of the function
*/
function runInAction<T>(fn: () => T): T;
/**
* Runs a named function as an action
* @param name - Debug name for the action
* @param fn - Function to run as action
* @returns Result of the function
*/
function runInAction<T>(name: string, fn: () => T): T;Usage Examples:
import { observable, runInAction, autorun } from "mobx";
const state = observable({
loading: false,
data: null,
error: null
});
// Direct usage for immediate actions
async function fetchData(url) {
runInAction(() => {
state.loading = true;
state.error = null;
});
try {
const response = await fetch(url);
const data = await response.json();
runInAction("fetch success", () => {
state.data = data;
state.loading = false;
});
} catch (error) {
runInAction("fetch error", () => {
state.error = error.message;
state.loading = false;
});
}
}
// Useful for event handlers
const handleClick = (event) => {
runInAction(() => {
state.data = null;
state.error = null;
state.loading = !state.loading;
});
};Creates async actions using generator functions, providing a clean way to handle asynchronous state changes.
/**
* Creates an async action using generator functions
* @param generator - Generator function that yields promises
* @returns Function that returns a Promise
*/
function flow<R, Args extends any[]>(
generator: (...args: Args) => Generator<any, R, any>
): (...args: Args) => Promise<R>;Usage Examples:
import { observable, flow, autorun } from "mobx";
const state = observable({
loading: false,
users: [],
error: null
});
// Flow for async operations
const fetchUsers = flow(function* () {
state.loading = true;
state.error = null;
try {
// Yield promises - MobX handles the async flow
const response = yield fetch("/api/users");
const users = yield response.json();
// State changes after yield are automatically wrapped in actions
state.users = users;
state.loading = false;
return users;
} catch (error) {
state.error = error.message;
state.loading = false;
throw error;
}
});
// Flow with parameters
const createUser = flow(function* (userData) {
state.loading = true;
try {
const response = yield fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData)
});
const newUser = yield response.json();
state.users.push(newUser);
state.loading = false;
return newUser;
} catch (error) {
state.error = error.message;
state.loading = false;
throw error;
}
});
// Usage
fetchUsers().then(users => {
console.log("Loaded users:", users.length);
});
createUser({ name: "Alice", email: "alice@example.com" });Unwraps flow results for proper TypeScript typing and cancellation support.
/**
* Unwraps the result of a flow for proper typing
* @param flowResult - Result from calling a flow
* @returns Promise with proper typing
*/
function flowResult<T>(flowResult: T): T extends Promise<infer R> ? Promise<R> : T;Usage Examples:
import { flow, flowResult } from "mobx";
const myFlow = flow(function* () {
yield new Promise(resolve => setTimeout(resolve, 1000));
return "completed";
});
// Without flowResult - TypeScript sees generic return type
const result1 = myFlow(); // Type: any
// With flowResult - TypeScript sees Promise<string>
const result2 = flowResult(myFlow()); // Type: Promise<string>
result2.then(value => {
console.log(value); // "completed" with proper typing
});Flows can be cancelled, providing clean cancellation semantics for async operations.
/**
* Error thrown when a flow is cancelled
*/
class FlowCancellationError extends Error {
name: "FlowCancellationError";
}
/**
* Checks if an error is a flow cancellation error
* @param error - Error to check
* @returns True if error is from flow cancellation
*/
function isFlowCancellationError(error: any): error is FlowCancellationError;Usage Examples:
import {
flow,
flowResult,
FlowCancellationError,
isFlowCancellationError
} from "mobx";
const longRunningFlow = flow(function* () {
for (let i = 0; i < 10; i++) {
yield new Promise(resolve => setTimeout(resolve, 1000));
console.log(`Step ${i + 1}`);
}
return "completed";
});
// Start the flow
const promise = flowResult(longRunningFlow());
// Cancel after 3 seconds
setTimeout(() => {
promise.cancel();
}, 3000);
// Handle cancellation
promise.catch(error => {
if (isFlowCancellationError(error)) {
console.log("Flow was cancelled");
} else {
console.error("Flow failed:", error);
}
});Checks if a function is wrapped as an action.
/**
* Checks if a function is an action
* @param fn - Function to check
* @returns True if function is an action
*/
function isAction(fn: any): boolean;Checks if a function is a flow.
/**
* Checks if a function is a flow
* @param fn - Function to check
* @returns True if function is a flow
*/
function isFlow(fn: any): boolean;Usage Examples:
import { action, flow, isAction, isFlow } from "mobx";
const normalFunction = () => {};
const actionFunction = action(() => {});
const flowFunction = flow(function* () {});
console.log(isAction(normalFunction)); // false
console.log(isAction(actionFunction)); // true
console.log(isAction(flowFunction)); // false
console.log(isFlow(normalFunction)); // false
console.log(isFlow(actionFunction)); // false
console.log(isFlow(flowFunction)); // trueActions provide context information for debugging and introspection.
interface IActionRunInfo {
name: string;
arguments: any[];
context: any;
}
/**
* Low-level action tracking functions
*/
function _startAction(name: string, scope: any, args: any[]): IActionRunInfo;
function _endAction(runInfo: IActionRunInfo): void;Actions that only run under certain conditions.
import { observable, action, configure } from "mobx";
// Configure strict mode
configure({ enforceActions: "always" });
const state = observable({ count: 0 });
const conditionalUpdate = action((newValue) => {
if (newValue > state.count) {
state.count = newValue;
}
});
// This would throw in strict mode without action wrapper
// state.count = 5; // Error!
conditionalUpdate(5); // OKDifferent patterns for handling async operations with actions.
import { observable, action, runInAction, flow } from "mobx";
const store = observable({
data: null,
loading: false,
error: null
});
// Pattern 1: Multiple runInAction calls
const fetchData1 = async () => {
runInAction(() => {
store.loading = true;
store.error = null;
});
try {
const data = await api.fetchData();
runInAction(() => {
store.data = data;
store.loading = false;
});
} catch (error) {
runInAction(() => {
store.error = error;
store.loading = false;
});
}
};
// Pattern 2: Using flow (recommended)
const fetchData2 = flow(function* () {
store.loading = true;
store.error = null;
try {
const data = yield api.fetchData();
store.data = data;
store.loading = false;
} catch (error) {
store.error = error;
store.loading = false;
}
});