statelyai / xstate

Actor-based state management & orchestration for complex app logic.
https://stately.ai/docs
MIT License
27.18k stars 1.26k forks source link

Question: how would this live side-by-side with a router? #51

Closed martaver closed 6 years ago

martaver commented 6 years ago

Thanks for an inspiring talk! I'm fascinated with the idea of expressing UIs as automata and I'm really keen to take xstate on a test run in a small project. I'm wondering, though, as to how I would integrate this state machine with a route hierarchy.

Each route, I imagine, would be an immediate entry point to a default state associated with it... perhaps the transitions would also transition the route state where appropriate... would each route contain its own state machine, orthogonal from each other route's state machine? How would this nest?

I would love to hear your opinions on the topic!

davidkpiano commented 6 years ago

For normal routing, check out the example here: https://state-machine-yzgnuxxwmg.now.sh/

For nested routes, that's a good question - and also a good reason to avoid tying business logic to the UI (which is hard, since React tends to want to colocate business logic with actual UI, though this can cause problems in the long run).

We've been discussing nested statechart encapsulation here: https://spectrum.chat/?t=de76a10c-32f5-4377-a51f-5ba61c451081 and to clarify, whenever you have a "black box" 3rd party component (or route, in this case) that has its own state machine, in development code, this should simply be treated as a provider of values to the parent statechart (think dispatch in redux) and handled as external state.

This probably requires more thought, and the reason it's difficult to find a suitable API for this (at least at the moment) is because whereas parts of the user interface might be completely encapsulated in terms of code (e.g., 3rd-party components or API calls), there is no such thing as encapsulation to the user using the user interface. Everything is within context; everything is on the same screen; all pixels are equal to the user, and 3rd-party components are very much a part of the same app, and don't "feel" like 3rd-party components.

With that insight, a testing statechart would need to be able to assume the behavior of 3rd-party components, but a development statechart might not (that is, an encapsulated statechart might just be a provider of values, like an event emitter/listener). Something to think about.

However, now I'm thinking - ideally, a component should be able to know, and enumerate, a finite representation of its possible rendered child components. Those child components might have static statecharts, that can be provided to the parent before the component is initiated:

import Foo from './components/foo';
import Bar from './components/bar';

const machine = Machine({
  initial: 'foo',
  states: {
    foo: {
      ...Foo.machine,
      on: {
        NEXT: 'bar',
        PREV: 'foo'
      }
    },
    bar: {
      ...Bar.machine,
      on: {
        NEXT: 'bar',
        PREV: 'foo'
      },
    }
  }
});

class App extends React.Component {
  static machine = machine;
  // ...
  render() {
    // ...
    // renders either <Foo /> or <Bar />
  }
}

I'm going to play around with the above idea - it seems the most idiomatic and the least "black-boxy". The goal of statecharts is to model application behavior and logic as comprehensively as possible, because it essentially needs to "think" like a user using the app would think.

Any thoughts around this, @michelebertoli?

martaver commented 6 years ago

I've had a lot of success decoupling routing logic from the UI in my react projects, using router5 instead of react-router.

In a perfect world, routing is simply another state and has nothing to do with UI. The other domains of application state include data (obtained from an API, for e.g.) and ui. I imagine the 'data' state as a normalized data set and the ui as, for example, an xstate machine. We reselect/combine from these three domains to make props for components.

Having the router separate from other concerns works beautifully, because it means we can attach behavior to route transitions freely before their signal reaches any components. I imagine a similar approach could work with xstate, but how would it cross over? E.g.

How would route params (parsed from url) be passed to our state machines? Would route transitions be treated as a first-class state transition when navigating? Would route transitions simply invoke necessary state transitions as side-effects?

davidkpiano commented 6 years ago

How would route params (parsed from url) be passed to our state machines?

Do you mean in the context of conditional transitions? You'd pass that data (the extended state) in as the third argument to machine.transition(...), see the docs: http://davidkpiano.github.io/xstate/docs/#/guides/guards

Would route transitions simply invoke necessary state transitions as side-effects?

That's one way of approaching it, yeah. URL routing is a perfect example of a finite state machine, though xstate isn't "specialized" for routing, but can be used for it. Consider the transition() function just as a way of telling you "what is the next state?" That's the only difference you'd make in your routing approach - instead of imperatively telling the router where to go, you first ask xstate what the next route will be given the current state and the action just taken. Everything else is solved just as you would solve it without any extra libraries.

MicheleBertoli commented 6 years ago

Hello @Martaver @davidkpiano, the static machine property is an interesting approach for nested (encapsulated) machines. One of the most powerful features of statecharts is the possibility to zoom in and zoom out. So, in this scenario, the top level states represent the routes and each router transition is also a xstate transition of the statechart which is controlling the entire application.

davidkpiano commented 6 years ago

If you have any more questions on this topic, feel free to open a new issue 😄

tv-ravi commented 4 years ago

i am a new in xState and also stuck on same point from last 2 days and didn't found any clue to navigate/map routes between screens in react native app.Like i have login screen and when i successfully login i need to navigate on home screen with same machine' state logic.Another thing i didn't understand that when i trying to create a instance of via useMachine() in home screen it will reset machine state to initial state.

davidkpiano commented 4 years ago

@tv-ravi Want to open this as a discussion instead?

There's nothing special about routing + XState, since XState is agnostic to routing, but I'd love to see your code to get a better idea of what you're trying to do.