Giter Club home page Giter Club logo

xstate-tree's People

Contributors

kris-ad avatar mr-winter avatar redbar0n avatar semantic-release-bot avatar ubermouse avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

xstate-tree's Issues

Support tags

xstate supports tags on states. Currently you can't access that information in the selectors or views.

Passing the hasTags function to the selector and view would be a simple solution. Tags are not typesafe currently, not sure if typegen will be updated to support them at some point, may make more sense to wait until then to not require a rework to add the type information. We don't use them internally at Koordinates.

xstate-tree machine building 2.0

The build* factory function pattern that is currently in use, primarily to aid in typing, is.... not the best. I have long wanted to replace it with a better system but I have not been able to come up with one. The important functionality of the current system is

  1. Easily expand from a single file xstate-tree machine to a multi-file one, primarily splitting the view out
  2. Easily access the individual components of an xstate-tree machine, selectors/actions/view, for testing/storybook usage
  3. Provides typings without having to specify any generics or type complexity

User provided shared meta additions

There is a type, SharedMeta, that is used by routing events to populate the meta object. Currently this contains a bit of Koordinates specific typing and has no way for consumers of the library to provide their own exta meta information.

A system to enable consumers to add their own shared meta so we can remove the Koordinates specific property on SharedMeta solves both problems

Sync external data into machines

Syncing external data into xstate-tree machines is something we do a lot of at Koordinates. Most commonly Observables representing selectors from redux or watched GraphQL queries, to bring global state into the machines. But we have some areas where we need to sync data from React land back into the machine at the top of the xstate-tree hierarchy. That has been implented fairly poorly with a combination of useMemo and withContext building a new root machine anytime the useMemo dependencies change.

These are a very similar class of problems however, getting data from the outside world into the xstate machines context, with different implementations but the same mental model from inside the machine. Both ways can work with some sort of sync event sent to the machine whenever the external data changes allowing you to write a single sync event action to update context. They would need different events however as the view syncing and the observable syncing wouldn't know about each other.

Syncing from view

Handwavey thought to add a new type declaration declaring viewInputs or some such. That could then be tied into the event for syncing from the view, props on the component build by buildRootComponent, and as an argument to the slot creator functions to require passing props to a slot in the view.

From there it's a useEffect away from sending an event to the machine every time the props change to allow syncing the data into context.

import { UsesFoo } from "./SomeOtherChildMachine";

type ViewEvent<T> = { type: "xstate-tree.viewSync"; data: T };
type ViewProps = { foo: string };

type Events = ViewEvent<ViewProps>;

// specify props view generic of the slot factories
const slots = [singleSlot<ExtractViewProp<typeof UsesFoo>("UsesFoo")];

const machine = createMachine({
  id: "example1",
  schema: {
    events: {} as Events,
    viewProps: {} as ViewProps.
  },
  on: {
   "xstate-tree.viewSync": {
      actions: immerAssign((ctx, e) => {
          ctx.foo = e.data.foo;
      })
    }
  }
})

// snip

// foo prop in view passed in from above root component or machine
const view = buildView(..., ({ slots, foo }) => {
  // And then this slot was typed as also taking a foo so it gets passed here
  return <slots.UsesFoo foo={foo} />;
});

and with a root component

const Root = buildRootComponent(machine):

<Root foo="foo" />

Syncing from obervables

This seems like it would require some sort of wrapper around a group of observables to emit a new value any time any of the observables changes, including the last emit from any other observables to ease writing the sync action handler (or would that be bad because you have no way of telling if an observable had emitted a new value ๐Ÿค”).

You would then need to invoke that wrapped observable in your machine and attach a sync event action to update context when it emits.

As much as I would like automatically inject the invocation of the observable to reduce boilerplate I don't think it's advisable as it won't show up in the xstate visualizer. The reason we have been rolling back any machine config generation helpers we have at Koordinates, breaks the visualizer as the source of truth.

// Basically just combineLatest from RxJS and mapping them to an event
const wrappedObservables$ = wrapThemUp({
  foo: foo$,
  bar: bar$
});

type ObservableSyncEvent<T> = { type: "xstate-tree.sync"; data: T };

type Events = ObservableSyncEvent<ATypeExtractor<typeof wrappedObservables$>>;

const machine = createMachine({
  id: "example1",
  schema: {
   events: {} as Events,
  },
  invoke: {
    src: wrappedObservables$
  },
  on: {
   "xstate-tree.sync": {
      actions: immerAssign((ctx, e) => {
          ctx.foo = e.data.foo;
          ctx.bar = e.data.bar;
      })
    }
  }
}))

Upgrade History to v5

History v4 is pretty old, should be updated to v5 when we can. This is blocked on legacy code in the Koordinates codebase that requires us to use history v4

broadcast 2.0

In Thomas Weber's master theisis that inspired xstate-tree, all events were global and sent to all actors. I felt that was overkill for xstate-tree so only specific opt-in events are sent globally, via the broadcast method.

However now that we have been using that for a few years even just sending all events to all machines feels problematic, mostly because you need to start prefixing events since the names are global across all parts of the application. It feels like a better solution is to have mailboxes that have specific events attached to them that can be used in specific sections to better split up responsibilities. Then you can access this mailbox to send an event to all listeners of it, instead of funnelling all events through the same mailbox. It would also allow removing the somewhat strange GlobalEvent interface merging system for typing global events.

Could make sense to have a routing mailbox too ๐Ÿค”

The xstate receptionist RFC seems relevant to this statelyai/rfcs#5

Allow passing callbacks to slot views/root components to allow communication with React from xstate machines

This is a not uncommon scenario, where you have a function that only exists in React land that you want to call from an event in an xstate machine. The current solution to that is to push the callback function into the machines context so it may call it.

With xstate 5.9.0s addition of event emitters it should be possible to come up with a solution that allows using event emitters to call supplied React functions passed as props to the slots view.

ie something like this

// In the child machine to be invoked into the slot
export type EmittedEvents = { type: "someEvent", bar: string}
const machine = setup({
  types: {
    emitted: {} as EmittedEvents
  },
}).createMachine({
  entry: emit({type: "some-event", bar: "foo})
})

// In some parent machine that uses the slot
const someSlot = singleSlot("SomeSlot").withEmittedEvents<EmittedEvents>();
const slots = [someSlot]

const machine = createMachine({
  // .....
})

const XstateTreeMachine = createXStateTreeMachine(machine, {slots, View({slots}) {
  return <slots.SomeSlot onSomeEvent={({bar}) => console.log(bar)} />
}})

The same sort of functionality should also be exposed on root components

Deprecate `inState` prop of view components

This prop is the only prop passed to views that is tricky to type correctly when using the view directly (ex in Storybook stories or tests) as it depends on typegen information from the machine. It's also better practice to use the same inState function that is in the selectors to instead add something to the selectors to determine your conditional with in the view instead of using inState directly in the view.

In light of that, inState should be deprecated and when #14 is done the new system should not provide inState to the view.

View to machine utility

Not uncommon to have scenarios where you have a React view you want to be able to invoke into a slot in an xstate-tree machine, so you wrap it into a dummy machine that just renders the view.

Should have a helper utility that accepts the view and returns a machine so this requires no boilerplate

Improve debug logging

Currently, the debug logging is totally unconfigurable. At the very least you should be able to turn it off somehow. But there are probably more things to improve

ESM support

Currently only CJS style code is shipped. Update package to ship ESM and CJS code and update package.json with ESM functionality

Release issues

xstate-tree started as an internal package in Koordinates in 2020. Since it was originally designed for our internal use only there will inevitably be some issues that crop up or subpar decisions when operating it in another environment. Things like dependency versions, documentation, packaging style etc

This issue is for more "meta" bugs like that, bugs in the library code should go on their own issues.

Prep for public release

Before making the repo public there are a number of things I want to do/must be done

Needs

  • Commit linting
  • Clean up deps
  • Drop lodash
  • Document pubilc methods and relevant public types
  • CONTRIBUTING.md
  • Rewrite README

Wants

  • Rewrite test-app as quality example app, including tests that double as xstate-tree E2E tests and testing examples
  • Add more guide style documentation
  • Add various issues around future improvement ideas

Improve action handling

Currently the result of the buildActions builder is cached until one of the following happens

  1. The reference to the send function changes, I don't think this ever happens in practice
  2. The selectors result changes, since the action functions need the most up to date selectors output

And then the selectors can change for the following reasons

  1. Context ref changes
  2. Current state changes

Which means that the actions will change reference whenever the machines context is updated or the state is changed. This is not great for re-renders and makes the DX of using an action inside of a useEffect pretty awful. Can't refer to the action directly and need to use a ref that gets synchronized with the actions current reference.

It seems like a solution to this would be to instead return a stable Proxy reference for actions and have that proxy forward the call to the current actions reference.

Add example app and use it to improve test suite

The test-app used by the current tests is... lacking. It was a simple attempt at todo-mvc from way back when this library was first created back in 2020 and has grown and changed randomly as the test suite needed it over the years. It does not provide enough testing flexibility either so it doesn't even fulfill it's only purpose very well

Need to create a new xstate-tree example app, with a good test suite, both to provide a good piece of documentation on what an xstate-tree app looks like and how to test one, as well as increase the testing confidence.

Readme example leaves out most important part: slots

Readme says slots is the defining feature of xstate-tree:

Actors views are composed together via "slots", which can be rendered in the view to provide child actors a place to render their views in the parent's view.

But it leaves it out of the example, so it's hard to see how slots would look and feel when using xstate-tree to its full extent:

// If this tree had more than a single machine the slots to render child machines into would be defined here
const slots = [];

API docs

There is an overview of the exported API in xstate-tree.api.md but that is rather lacking as far as actual API docs go. Need to investigate setting up an api-docs static site

Routing machine

Helper utility to remove boilerplate for machines of simple event -> state -> machine configurations as is common with routing machines. Something like

export const AppRouter = buildRoutingMachine([...routingEvents], {
  GO_TO_FOO: FooMachine,
  GO_TO_BAR: BarMachine
});

Which is analogous to

export const AppRouter = createMachine(
  on: {
    GO_TO_FOO: "foo",
    GO_TO_BAR: "bar",
  },
  initial: "waitingForRoute",
  states: {
   waitingForRoute: {},
   foo: {
      invoke: { src: FooMachine }
   },
   bar: {
     invoke: { src: BarMachine }
   }
  }
);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.