#Proper way to handle class instances and context mutations in XState

1 messages · Page 1 of 1 (latest)

atomic jacinth
#

Hi, I’m new to XState and I have a question about working with class instances stored in a machine’s context.

According to the docs, it’s recommended to update context data via the assign(...) action.
But I’m building a card game using a class-based approach, and my context holds an instance of a Deck class.

Here’s a simplified example:

class Deck {
  cards: string[]
  constructor(cards: string[]) { this.cards = cards }
  drawOne(): string { return this.cards.shift()! } // removes and returns the top card
}
import { createMachine, assign, createActor } from 'xstate'
const gameMachine = createMachine({
  id: 'game',
  initial: 'playing',
  context: { deck: new Deck(['A♠','K♥','Q♦']), hand: [] as string[] },
  states: {
    playing: { on: { DRAW_CARD: { actions: 'drawCard' } } }
  }
},{
  actions: {
    drawCard: assign({
      hand: ({context}) => {
        const card = context.deck.drawOne()
        return [...context.hand, card]
      }
    })
  }
})
const actor = createActor(gameMachine)
actor.start()
actor.subscribe(s => console.log('Deck:', s.context.deck.cards, ', Hand:', s.context.hand))
actor.send({type:'DRAW_CARD'})
actor.send({type:'DRAW_CARD'})
actor.send({type:'DRAW_CARD'})
// Output:
// Deck: [ 'K♥', 'Q♦' ] , Hand: [ 'A♠' ]
// Deck: [ 'Q♦' ] , Hand: [ 'A♠','K♥' ]
// Deck: [] , Hand: [ 'A♠','K♥','Q♦' ]

In this example, the deck instance is mutated directly inside an assign action, while hand is updated immutably.

My questions are:

  1. Is it acceptable in XState v5 to store mutable class instances in the context and call their methods that change internal state?
  2. Could this lead to unexpected behavior?
  3. Would you recommend always creating and returning new instances of Deck (and hand) inside assign, instead of mutating existing objects?

<@&826821237123842048>

ocean jolt
#

Assignment action is expected to be pure and free of side effects. In your example, you can keep the game class and encapsulate all game stuff in it while spawning or invoking it as an actor in your gameMachine which probably handles state orchestration and rendering

#

And you can make the game class a subscribable entity so your machine can subscribe to it or send events to it through an event emitter pattern

#

Another option if you prefer to keep it as-is is to to drawOne in an action and update deck reference in context in a subsequent action

#

But that can become too verbose soon

atomic jacinth
alpine phoenix
#

Is this a React app or similar?

atomic jacinth
#

That’s why I answered, I want to keep common classes for base entities (Card, Deck, etc.) and then build card games (like poker or durak/fool) with XState v5.

ocean jolt
#

I'm thinking something like this