Skip to content

Releases: statelyai/xstate

@xstate/[email protected]

24 Feb 14:52
80db725
Compare
Choose a tag to compare

Minor Changes

  • #5200 0332a16a42fb372eb614df74ff4cb7f003c31fc8 Thanks @{! - Added selectors to @xstate/store that enable efficient state selection and subscription:

    • store.select(selector) function to create a "selector" entity where you can:
      • Get current value with .get()
      • Subscribe to changes with .subscribe(callback)
      • Only notify subscribers when selected value actually changes
      • Support custom equality functions for fine-grained control over updates via store.select(selector, equalityFn)
    const store = createStore({
      context: {
        position: { x: 0, y: 0 },
     name: 'John', age: 30 }
      },
      on: {
        positionUpdated: (
          context,
          event: { position: { x: number; y: number } }
        ) => ({
          ...context,
          position: event.position
        })
      }
    });
    
    const position = store.select((state) => state.context.position);
    
    position.get(); // { x: 0, y: 0 }
    
    position.subscribe((position) => {
      console.log(position);
    });
    
    store.trigger.positionUpdated({ x: 100, y: 200 });
    // Logs: { x: 100, y: 200 }

@xstate/[email protected]

21 Feb 19:28
10e0d6d
Compare
Choose a tag to compare

Minor Changes

  • #5205 65784aef746b6249a9c3d71d9e4a7c9b454698c8 Thanks @davidkpiano! - Added createStoreConfig to create a store config from an object. This is an identity function that returns the config unchanged, but is useful for type inference.

    const storeConfig = createStoreConfig({
      context: { count: 0 },
      on: { inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }) }
    });
    
    // Reusable store config:
    
    const store = createStore(storeConfig);
    
    // ...
    function Comp1() {
      const store = useStore(storeConfig);
    
      // ...
    }
    
    function Comp2() {
      const store = useStore(storeConfig);
    
      // ...
    }
  • #5205 65784aef746b6249a9c3d71d9e4a7c9b454698c8 Thanks @davidkpiano! - There is now a useStore() hook that allows you to create a local component store from a config object.

    import { useStore, useSelector } from '@xstate/store/react';
    
    function Counter() {
      const store = useStore({
        context: {
          name: 'David',
          count: 0
        },
        on: {
          inc: (ctx, { by }: { by: number }) => ({
            ...ctx,
            count: ctx.count + by
          })
        }
      });
      const count = useSelector(store, (state) => state.count);
    
      return (
        <div>
          <div>Count: {count}</div>
          <button onClick={() => store.trigger.inc({ by: 1 })}>
            Increment by 1
          </button>
          <button onClick={() => store.trigger.inc({ by: 5 })}>
            Increment by 5
          </button>
        </div>
      );
    }

Patch Changes

@xstate/[email protected]

14 Feb 12:26
08ade1a
Compare
Choose a tag to compare

Patch Changes

@xstate/[email protected]

10 Feb 14:34
a4f9ca3
Compare
Choose a tag to compare

Major Changes

  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - The createStore function now only accepts a single configuration object argument. This is a breaking change that simplifies the API and aligns with the configuration pattern used throughout XState.

    // Before
    // createStore(
    //   {
    //     count: 0
    //   },
    //   {
    //     increment: (context) => ({ count: context.count + 1 })
    //   }
    // );
    
    // After
    createStore({
      context: {
        count: 0
      },
      on: {
        increment: (context) => ({ count: context.count + 1 })
      }
    });
  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - You can now enqueue effects in state transitions.

    const store = createStore({
      context: {
        count: 0
      },
      on: {
        incrementDelayed: (context, event, enq) => {
          enq.effect(async () => {
            await new Promise((resolve) => setTimeout(resolve, 1000));
            store.send({ type: 'increment' });
          });
    
          return context;
        },
        increment: (context) => ({ count: context.count + 1 })
      }
    });
  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - The fromStore(config) function now only supports a single config object argument.

    const storeLogic = fromStore({
      context: (input: { initialCount: number }) => ({
        count: input.initialCount
      }),
      on: {
        inc: (ctx, ev: { by: number }) => ({
          ...ctx,
          count: ctx.count + ev.by
        })
      }
    });
  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - The createStoreWithProducer(…) function now only accepts two arguments: a producer and a config ({ context, on }) object.

    // Before
    // createStoreWithProducer(
    //   producer,
    //   {
    //     count: 0
    //   },
    //   {
    //     increment: (context) => {
    //       context.count++;
    //     }
    //   }
    // );
    
    // After
    createStoreWithProducer(producer, {
      context: {
        count: 0
      },
      on: {
        increment: (context) => {
          context.count++;
        }
      }
    });
  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - Only complete assigner functions that replace the context fully are supported. This is a breaking change that simplifies the API and provides more type safety.

    const store = createStore({
      context: {
        items: [],
        count: 0
      },
      on: {
    -   increment: { count: (context) => context.count + 1 }
    -   increment: (context) => ({ count: context.count + 1 })
    +   increment: (context) => ({ ...context, count: context.count + 1 })
      }
    })
  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - Emitted event types are now specified in functions on the emits property of the store definition:

    const store = createStore({
      // …
      emits: {
        increased: (payload: { upBy: number }) => {
          // You can execute a side-effect here
          // or leave it empty
        }
      },
      on: {
        inc: (ctx, ev: { by: number }, enq) => {
          enq.emit.increased({ upBy: ev.by });
    
          // …
        }
      }
    });

Minor Changes

  • #5175 38aa9f518ee2f9a5f481306a1dc68c0ad47d28d5 Thanks @davidkpiano! - Added store.trigger API for sending events with a fluent interface:

    const store = createStore({
      context: { count: 0 },
      on: {
        increment: (ctx, event: { by: number }) => ({
          count: ctx.count + event.by
        })
      }
    });
    
    // Instead of manually constructing event objects:
    store.send({ type: 'increment', by: 5 });
    
    // You can now use the fluent trigger API:
    store.trigger.increment({ by: 5 });

    The trigger API provides full type safety for event names and payloads, making it easier and safer to send events to the store.

[email protected]

12 Jan 18:45
3b43d84
Compare
Choose a tag to compare

Patch Changes

@xstate/[email protected]

12 Jan 18:45
3b43d84
Compare
Choose a tag to compare

@xstate/[email protected]

12 Jan 18:45
3b43d84
Compare
Choose a tag to compare

@xstate/[email protected]

12 Jan 18:45
3b43d84
Compare
Choose a tag to compare

@xstate/[email protected]

12 Jan 18:45
3b43d84
Compare
Choose a tag to compare

@xstate/[email protected]

12 Jan 18:45
3b43d84
Compare
Choose a tag to compare