DEV Community

David K. 🎹
David K. 🎹

Posted on

Release Notes: XState 4.22.0 & @xstate/react 1.5.0

XState 4.22.0

See release

In past releases, createModel(...) was introduced to make typing context and events easier in machines. Now, you can create a machine directly from the .createMachine(...) method on a created model:

const userModel = createModel({
  name: 'David'
});

const userMachine = userModel.createMachine({
  // no need to specify initial `context`!

  entry: (context, event) => {
    // context and event are fully typed from model
  }
});
Enter fullscreen mode Exit fullscreen mode

This makes it even easier to create typed machines without having to specify type parameters. Keep in mind that the use of createModel(...) is completely optional, and meant as a helper. In the future, models will include ways to create and provide types for action objects, guards, and more.


In this version, you can also now spawn/invoke reducers, which are functions that return the next state given the current state and event. The source of a spawned/invoked reducer is created using fromReducer(reducer, initialState):

import { fromReducer } from 'xstate/lib/behaviors';

type CountEvent = { type: 'INC' } | { type: 'DEC' };

const countReducer = (count: number, event: CountEvent): number => {
  if (event.type === 'INC') {
    return count + 1;
  } else if (event.type === 'DEC') {
    return count - 1;
  }

  return count;
};

const countMachine = createMachine({
  invoke: {
    id: 'count',
    src: () => fromReducer(countReducer, 0)
  },
  on: {
    INC: {
      actions: forwardTo('count')
    },
    DEC: {
      actions: forwardTo('count')
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

If you've used Redux, Vuex, ngrx, or similar state management libraries that use reducers, this should feel familiar to you. It is now possible to reuse reducers directly inside of XState.

@xstate/react 1.5.0

See release

Just like useInterpret(...) takes in a machine and returns a service actor, there is a new useSpawn(...) hook that takes in any behavior and returns an actor for that behavior.

What is a behavior?
A behavior is a function that determines what happens next when an actor receives an event, depending on its current state. With state machines & statecharts, the behavior is formally and visually defined.

You can try this out with the previously mentioned new fromReducer(...) creator, which creates a "reducer behavior" from a reducer:

import { fromReducer } from 'xstate/lib/behaviors';
import { useActor, useSpawn } from '@xstate/react';

type CountEvent = { type: 'INC' } | { type: 'DEC' };

const countBehavior = fromReducer(
  (count: number, event: CountEvent): number => {
    if (event.type === 'INC') {
      return count + 1;
    } else if (event.type === 'DEC') {
      return count - 1;
    }

    return count;
  },
  0 // initial state
);

const Component = () => {
  const countActorRef = useSpawn(countBehavior);
  const [count, send] = useActor(countActorRef);

  return (
    <div>
      Count: {count}
      <button onClick={() => send({ type: 'INC' })}>Increment</button>
      <button onClick={() => send({ type: 'DEC' })}>Decrement</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

For brevity, you can also combine the hooks:

const [count, send] = useActor(useSpawn(countBehavior));
Enter fullscreen mode Exit fullscreen mode

On a more general note, this demonstrates that the most basic units in XState are behaviors and actors. Actors are live instances of behaviors. State machines and statecharts are specialized, powerful behaviors, and services are actors that have their behaviors described as those state machines/statecharts.

Top comments (0)