Documentation/Advanced/Native Async Actions

Native Async Actions

FractoState v2 introduces Native Async Actions to handle business logic and side effects directly within your flow definitions. This replaces the need for external middleware like Thunks or Sagas, keeping your logic co-located with your state.

Key Concept

Actions are functions attached to a flow that have direct access to the ops (operations) proxy. This allows you to perform "surgical" updates to the state at any point during an asynchronous operation (start, success, failure).

Defining Actions

Actions are defined in the actions property of the FlowOptions object passed to defineFlow.

import { defineFlow } from "fractostate";
import { api } from "./api"; // hypothetical api

const UserFlow = defineFlow(
  "user",
  {
    profile: null,
    isLoading: false,
    error: null,
  },
  {
    actions: {
      // The outer function receives your arguments.
      // It returns an inner function that receives the 'ops' proxy.
      login: (username, password) => async (ops) => {
        // 1. Set loading state
        ops.self.isLoading._set(true);
        ops.self.error._set(null);

        try {
          // 2. Perform async operation
          const user = await api.login({ username, password });

          // 3. Update state with result
          ops.self.profile._set(user);
          return true;
        } catch (err) {
          // 4. Handle error
          ops.self.error._set(err.message);
          return false;
        } finally {
          // 5. Reset loading state
          ops.self.isLoading._set(false);
        }
      },

      logout: () => (ops) => {
        ops.self._set({ profile: null, isLoading: false, error: null });
      },
    },
  },
);

Consuming Actions

When you use the flow in a component, the actions are available in the toolbox (the second return value). They are automatically bound, so you do not pass ops when calling them.

import { useFlow } from "fractostate";

const LoginButton = () => {
  const [userState, { actions }] = useFlow(UserFlow);

  const handleLogin = async () => {
    // Call the action directly with your arguments
    const success = await actions.login("john_doe", "secret123");

    if (success) {
      console.log("Welcome back!");
    }
  };

  return (
    <button onClick={handleLogin} disabled={userState.isLoading}>
      {userState.isLoading ? "Logging in..." : "Login"}
    </button>
  );
};

Type Safety

FractoState infers the types of your actions. When you define actions in defineFlow, useFlow will correctly type the actions object in the toolbox, including argument types and return values.