DEV Community

Mikey Stengel
Mikey Stengel

Posted on

18 6

State machine advent: Accessing private actor state in components (23/24)

Yesterday, we saw how to use the actor model to create hierarchy in applications and how to treat every actor as a computational unit that encapsulates behavior. Further, we established how actors have private state that can only be accessed from other actors using explicit communication (events). To build user interfaces, however, we do oftentimes want to access the private state of actors and render them to our UI. Today, we want to build a React component that renders the context of the Player actor.

Oftentimes, we can just mimic our actor architecture with React components. As a result, we can have a Game component that invokes the gameMachine and renders a Player component to display the action (Rock, Paper, Scissors) the player performed. Meanwhile, the gameMachine is a parent itself because it invokes the player actor. Essentially, recreating the same hierarchy and relationships between components that we first defined within our machines.

We can iterate through the array that holds the references to the player actor and pass them to the child component as props which can then deal with them in two different ways as we'll see in a minute.

import { useMachine } from '@xstate/react';
import React, { Fragment } from 'react';
import { Player } from './Player';

const Game = () => {
  const [state, send] = useMachine(gameMachine)

  return (
    <div>
      {state.context.playerRefs.map((playerRef, index) => (
        <Fragment key={index}>
          <Player playerRef={playerRef} />
        </Fragment>
      ))}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Once we define the Player component, we have a decision to make. Do we only want to access the actor so that we can receive and send events to it or do we want to access its private state? Although not the goal for today, for the former option, we should go with the useActor hook from the @xstate/react package.

When using this hook, state does not hold the context property since the actor state is private. Nonetheless, we could use the actor to send events from within our component.

import { useActor } from '@xstate/react';
import { PlayerActor } from './actorMachine'

const Player = ({playerRef}: {playerRef: PlayerActor }) => {
  const [state, send] = useActor(playerRef);
  // state.context === undefined 
  return null;
}
Enter fullscreen mode Exit fullscreen mode

On the other hand, if we do want to access the context, we could make use of the running service which is another word for an invoked machine by using the useService hook of the same package.

import { useService } from '@xstate/react';
import { PlayerService } from './actorMachine'

const Player = ({playerRef}: {playerRef: PlayerService }) => {
  const [state, send] = useService(playerRef);

  return (
    <p>{state.context.identity} decided on: {state.context.playedAction}</p>
  );
}
Enter fullscreen mode Exit fullscreen mode

Passing the reference to the actor into the useService subscribes the component to all the state changes of the actor. As a result, when the context or finite-state of the player actor changes, the component is rerendered as well. Needless to say, the reactive nature of state machines and React work harmoniously well together.

For a complete example, check the codesandbox for today's lecture and pay special attention to the type differences of the two respective hooks as pointed above (PlayerActor vs PlayerService).

About this series

Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.

The first couple of days will be spent on the fundamentals before we'll progress to more advanced concepts.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

πŸ‘‹ Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay