Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: "An inline child actor cannot be persisted." error. #5057

Open
mkhsanov opened this issue Aug 28, 2024 · 10 comments
Open

Bug: "An inline child actor cannot be persisted." error. #5057

mkhsanov opened this issue Aug 28, 2024 · 10 comments
Labels

Comments

@mkhsanov
Copy link

mkhsanov commented Aug 28, 2024

XState version

XState version 5

Description

In case if I try to create persisted snapshot with getPersistedSnapshot for actor which contains child spawned actors xstate raises an error.

Expected result

Crate persisted snapshot with child spawned actors.

Actual result

image

xstate raises an error with text "An inline child actor cannot be persisted."

Reproduction

https://codesandbox.io/p/live/47d8d896-243b-40a5-9a76-dafd43bb4847

Additional context

No response

@mkhsanov mkhsanov added the bug label Aug 28, 2024
@mkhsanov
Copy link
Author

You may find the error in the console output.

@davidkpiano
Copy link
Member

Have you tried moving the actor to not be an inline actor, and instead be a referenced actor?

@remake99
Copy link

remake99 commented Oct 9, 2024

Have you tried moving the actor to not be an inline actor, and instead be a referenced actor?

I have the same problem, but after following your instructions, it is resolved now.

@reinoute
Copy link

reinoute commented Jan 20, 2025

Do you mind sharing the solution @remake99 ? Or @mkhsanov ?

I'm getting the same error, but calling spawn() inside setup() (instead of calling it inline) still gives me same error.

Here's my code sandbox.

@hckhanh
Copy link

hckhanh commented Feb 3, 2025

@davidkpiano how can it be a referenced actor?

@WonderPanda
Copy link

Is there a way to have a dynamic number of child actors based on the context that can actually be persisted?

I have the same question as @hckhanh I can't seem to figure out how to create a referenced actor.

I'm working on a game and I was hoping to have a parent actor that acts as authoritative server state and then individual child actors for each player. This is inside of a CloudFlare Durable Object so I'd like to be able to persist the state of the whole thing including parent and child machines into storage.

@hckhanh
Copy link

hckhanh commented Feb 5, 2025

@WonderPanda @reinoute I found out the answer. The referenced actor need to be declared in the setup function. Here is an example:

export const mainTabLayoutMachine = setup({
  types: {
    context: {} as {
      isTabDragging: boolean
      tabGroupIds: string[]
      tabGroups: ActorRefFrom<typeof mainTabGroupMachine>[][]
    },
    events: {} as
      | { groupId: string; type: 'group.empty' }
      | {
          groupId: string
          position: MainTabLayoutPosition
          scope: MainTabLayoutScope
          tabs: TabData[]
          type: 'tabs.added'
        }
      | { type: 'tab.dragging' }
      | { type: 'tab.dropped' },
  },
  actors: {
    group: mainTabGroupMachine, // <--- declare it here
  },
}).createMachine({
  /* ... */
  states: {
    empty: {
      on: {
        'tabs.added': {
          target: 'filled',
          actions: assign({
            tabGroups: ({ event, spawn }) => [
              [spawn('group', { input: { tabs: event.tabs } })], // <-- make a call to the declared actor
            ],
            tabGroupIds: [crypto.randomUUID()],
          }),
        },
      },
    },
  },  
  /* ... */
})

@WonderPanda
Copy link

@WonderPanda @reinoute I found out the answer. The referenced actor need to be declared in the setup function. Here is an example:

export const mainTabLayoutMachine = setup({
types: {
context: {} as {
isTabDragging: boolean
tabGroupIds: string[]
tabGroups: ActorRefFrom[][]
},
events: {} as
| { groupId: string; type: 'group.empty' }
| {
groupId: string
position: MainTabLayoutPosition
scope: MainTabLayoutScope
tabs: TabData[]
type: 'tabs.added'
}
| { type: 'tab.dragging' }
| { type: 'tab.dropped' },
},
actors: {
group: mainTabGroupMachine, // <--- declare it here
},
}).createMachine({
/* ... /
states: {
empty: {
on: {
'tabs.added': {
target: 'filled',
actions: assign({
tabGroups: ({ event, spawn }) => [
[spawn('group', { input: { tabs: event.tabs } })], // <-- make a call to the declared actor
],
tabGroupIds: [crypto.randomUUID()],
}),
},
},
},
},
/
... */
})

Ahhh so this requires that the child actor is already instantiated before the setup phase of the parent. I was really hoping to be able to spawn children based on parent context and still have them be persistable.

@hckhanh
Copy link

hckhanh commented Feb 6, 2025

I think you should list all of them to the setup Functions. You can pass name to spawn children

@WonderPanda
Copy link

@hckhanh Unfortunately these are created dynamically so I can't list them ahead of time in the setup functions in my case.

@davidkpiano I've just realized that this error only comes from the development build of XState. When I reference the main code instead, the error is not thrown and in all my tests the child actor state can be persisted and restored as expected. So this seems to be implemented.

It makes sense to my naive brain that we just need to serialize getPersistedSnapshot for each of the children which is what it looks like the code does when not in development mode.

Is there a reason we should avoid doing this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants