xstate icon indicating copy to clipboard operation
xstate copied to clipboard

[v5] implement action groups

Open with-heart opened this issue 2 years ago • 5 comments

This PR implements #3996, enabling actions to be implemented as an action group. (replaces #3561)

An action group is a named array of actions. When the action group is executed, each action in the group is executed in order:

import { createMachine, interpret, log } from 'xstate';

const machine = createMachine(
  {
    // reference the action group by name (`someGroup`)
    entry: 'someGroup'
  },
  {
    actions: {
      // executes `action1`, then `action2`
      someGroup: ['action1', 'action2'],
      action1: log('action1'),
      action2: log('action2')
    }
  }
);

interpret(machine).start(); // logs "action1", then "action2"

Action groups allow us to avoid error-prone repetition of actions, instead defining the group once and reusing it anywhere—like a single source of truth for the algorithm the group represents:

 const machine = createMachine(
   {
     context: { count: 0 },
     on: {
-      // increment count, then print it
-      incrementClick: { actions: ['incrementCount', 'printCount'] },
+       incrementClick: { actions: 'increment' },
-      // increment count, then print it
-      // oops! we accidentally put the actions in the wrong order
-      tick: { actions: ['printCount', 'incrementCount'] },
+      tick: { actions: 'increment' }
     }
   },
   {
     actions: {
+      // increment count, then print it. single source of truth for the `increment` algorithm
+      increment: ['incrementCount', 'printCount'],
       incrementCount: assign({
         count: ({ context }) => context.count + 1
       }),
       printCount: log(({ context }) => `Count: ${context.count}`)
     }
   }
 );

Action groups can reference other action groups by name. The referenced group's actions will be executed in order from the point of reference—like spreading the referenced group's actions in the group:

const machine = createMachine(
  {
    entry: 'initialize'
  },
  {
    actions: {
      // executes `load` group actions, then `listen` group actions
      initialize: ['load', 'listen'],
      load: ['loadConfig', 'loadData'],
      listen: ['startApp', 'listenOnPort']
    }
  }
);

interpret(machine).start();
// actions: (load) loadConfig -> loadData -> (listen) startApp -> listenOnPort

With a mix of actions, action groups, and action group references, we can compose our algorithms in flexible and reusable ways:

import { assign, createMachine, log } from 'xstate';

const machine = createMachine(
  {
    entry: 'initialize',
    exit: 'terminate',
    on: {
      timeout: { actions: 'reconnect' },
      reload: { actions: 'reload' }
    }
  },
  {
    actions: {
      initialize: ['load', 'connect'],
      terminate: ['disconnect', 'save', 'exitProgram'],
      reconnect: ['disconnect', 'connect'],
      reload: ['disconnect', 'save', 'initialize']
    }
  }
);

I feel like there's something powerful to thinking about action groups as composable algorithms, though I'm not entirely sure what that is yet, so I'm excited for this to land so more people can play with it!

with-heart avatar May 03 '23 16:05 with-heart

🦋 Changeset detected

Latest commit: 8a38c23d1cd324826fe6b847208a7210b561c275

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
xstate Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar May 03 '23 16:05 changeset-bot[bot]

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 8a38c23d1cd324826fe6b847208a7210b561c275:

Sandbox Source
XState Example Template Configuration
XState React Template Configuration

codesandbox-ci[bot] avatar May 03 '23 16:05 codesandbox-ci[bot]

👇 Click on the image for a new way to code review

Review these changes using an interactive CodeSee Map

Legend

CodeSee Map legend

ghost avatar May 03 '23 16:05 ghost

I didn't take a thorough look through the implementation but here are my thoughts:

  1. we'd like to support this
  2. i will be refactoring how we handle actions soon, so I likely would prefer for this work to land after that and thus this PR will require significant~ changes. OTOH, it's mostly about resolving the actions recursively so it shouldn't take a lot of time to rebase this
  3. I think that before landing this we might need to add support for this in the typegen (I know there is a PR for that already) and in the Studio. We'd need some designs or at least some basic support for this (this might also require adding support in the bidi editing code)

Andarist avatar May 17 '23 15:05 Andarist

  1. i will be refactoring how we handle actions soon, so I likely would prefer for this work to land after that and thus this PR will require significant~ changes. OTOH, it's mostly about resolving the actions recursively so it shouldn't take a lot of time to rebase this

That's fine with me!

  1. I think that before landing this we might need to add support for this in the typegen (I know there is a PR for that already) and in the Studio. We'd need some designs or at least some basic support for this (this might also require adding support in the bidi editing code)

That makes sense to me. I'll work on getting the typegen PR updated so it can land before this.

with-heart avatar May 17 '23 16:05 with-heart

🧹 I think that with enqueueActions(…) and v6 ideas for single-function actions (basically enqueueActions(…) but by default), action groups will be made redundant, since it would be easier to use functions as "groups" instead:

// TENTATIVE API
const machine = setup({
  actions: {
    actionGroup: (_, x) => {
      x.action({ type: 'action1' });
      x.action({ type: 'action2' });
    },
    action1: () => { ... },
    action2: () => { ... },
  }
}).createMachine({
  entry: 'someGroup'
});

davidkpiano avatar May 19 '24 13:05 davidkpiano

@davidkpiano that sounds lovely! Do you have v6 ideas documented anywhere publicly? I'd love to see what you're thinking

with-heart avatar May 20 '24 23:05 with-heart

@davidkpiano that sounds lovely! Do you have v6 ideas documented anywhere publicly? I'd love to see what you're thinking

Not yet, but once I set up a branch/draft PR for it I'll let you know.

davidkpiano avatar May 21 '24 11:05 davidkpiano

Thank you! ❤️

with-heart avatar May 21 '24 16:05 with-heart