You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I've been trying to model a generic UI as Xstate machine. This is the structure that I use for an Xstate 'system' in my react projects. To keep all my logic separate, I usually have a 'systems' folder where I keep collection of Xstate components. Each Xstate component is a folder with a bunch of files in it. The example files of an Xstate component that I have described below, make this component:
states.yaml
I like keeping all my state machines specified as a YAML file, and then later passing actions, activities, etc as options. So first, I have description of my machine in a states.yaml which looks like this:
If you convert it to JSON and then visualize it on Xstate visualizer, you get this diagram:
index.js
Since it's a starter Xstate machine template for a simple page, it only has a display machine. This then gets imported into an index.js file, which contains all exports you need to share this machine between components via React contexts and hooks or individually:
importReactfrom"react"import*asXfrom"xstate"importstatesfrom"./states.yaml"importoptionsfrom"./options.js"import{useMachine}from"@xstate/react"/* ====================================== This file simply imports the YAML state representation and the options (actions, activities, services and guards) which are to be used inside the `states` YAML file. You can then use the YAML file to visualize the statechart properly and simply import machine (which can be interpreted as you wish) or as a service (which can be started and stopped as you wish). ======================================*/exportconstmachine=X.createMachine(states,options)exportconstservice=X.interpret(machine)/* ====================================== Here, we're also exporting the state machine with a wrapper for react. (Basically creating a Context & Provider). This is useful in case you want to share a state machine service across components in React. ======================================*/exportconstContext=React.createContext()exportconstConsumer=Context.ConsumerexportconstProvider=({ children })=>{const[state,send,service]=useMachine(machine)constvalue={ state, send, service }return<Context.Providervalue={value}>{children}</Context.Provider>}exportdefault{ machine, service, Context, Provider, Consumer }
options.js
Note, that options.js is being imported in this file. options.js is a sibling file that simply contains actions, activities, services and guards:
That makes my Xstate component complete. That's how I do it (I usually generate all this boilerplate code for every single 'state machine' module that I want to create using a custom global bash script, and then delete or add or modify some things that I need to). I would also like to know if there are better or cleaner ways of arranging your machines, or how you arrange it. I use YAML so that I can comment in my description of machine and it's a matter of preference that I keep it serializable instead of an object (because it lets me break and make things faster). This is how I would use this 'home' Xstate component in my React component (after wrapping up the root component in the provider that's exported from index.js of Xstate component):
pages/index.js
importReactfrom"react"importSystemfrom"@/systems/pages/home"importLinkfrom"next/link"import{motion}from"framer-motion"import{Box,Heading}from"@chakra-ui/core"constHome=()=>{/* ====================================== System variable contains the current state object along with 'event' that you can use to change the state of the system. Some states have metadata associated in them. They could be styles, and contents of the page. ====================================== */constsystem=React.useContext(System.Context)const{ display }=system.state.valueconstdata=p=>system.state.meta?.[`${p}.${display[p]}`]consttheme=data("theme")constlayout=data("layout")constcontent=data("content")return(<><Box{...layout.container}>{content.navigation?.map((item,i)=>{/* ==================================== If the index of item is divisible by 2 apply some styles corresponding to even object, and if not then apply styles corresponding to odd object specified in the display machine. ==================================== */constcards={layout: i%2 ? layout.items.even : layout.items.odd,theme: i%2 ? theme.items.even : theme.items.odd}return(<React.Fragmentkey={i}><motion.div{...layout.items.transitions}><Linkhref={item.link}><Box{...cards.layout.container}{...cards.theme.container}><Heading{...cards.theme.heading}>{item.title}</Heading></Box></Link></motion.div></React.Fragment>)})}</Box></>)}exportdefaultHome
That finishes part 1 of my discussion.
Queries
I like the way that I have arranged this, it's clean and my React components are very short. But there are some Xstate specific things that I can't figure out yet that I'd like to ask:
If I were to make a randomized theme switcher or a theme selector - how would I go about it? Solution to a theme switcher and a theme selector will work for layout switcher and content switcher as well (like if I want to support my content in 3 languages with custom translations and be able to switch between them). I'm guessing that I will have to find some way to determine next state of the theme compound state alone based on event.payload (if it's a select component that lets you pick theme, like maybe darkula or monokai).
On spectrum a gentlemen suggested me to have a random number generated, kept in context and then coupled with guards to determine the next state - and I get it, I had the same idea before but I feel it's a little bit of hard coding and this is one small problem but if I want a randomizer in a compound state which has much more states than I can write guards for (random number lies between X and Y, switch to this) - this solution won't scale very well.
Another query I have is associated with the solution suggested to me on spectrum. Like let's say if I opt for keeping a random number in context - then how do I keep the context of one of the parallel states separate from its root machine. Like can I have a separate context for theme and a separate one for layout (I don't want to have separate keys in context for them, but rather separate context) and then be able to modify their own contexts with actions that I've kept separately in another file?
Thanks in advance. Do share how you would go about modelling a generic UI template.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi,
I've been trying to model a generic UI as Xstate machine. This is the structure that I use for an Xstate 'system' in my react projects. To keep all my logic separate, I usually have a 'systems' folder where I keep collection of Xstate components. Each Xstate component is a folder with a bunch of files in it. The example files of an Xstate component that I have described below, make this component:
states.yaml
I like keeping all my state machines specified as a YAML file, and then later passing actions, activities, etc as options. So first, I have description of my machine in a
states.yaml
which looks like this:If you convert it to JSON and then visualize it on Xstate visualizer, you get this diagram:
index.js
Since it's a starter Xstate machine template for a simple page, it only has a display machine. This then gets imported into an
index.js
file, which contains all exports you need to share this machine between components via React contexts and hooks or individually:options.js
Note, that
options.js
is being imported in this file.options.js
is a sibling file that simply contains actions, activities, services and guards:That makes my Xstate component complete. That's how I do it (I usually generate all this boilerplate code for every single 'state machine' module that I want to create using a custom global bash script, and then delete or add or modify some things that I need to). I would also like to know if there are better or cleaner ways of arranging your machines, or how you arrange it. I use YAML so that I can comment in my description of machine and it's a matter of preference that I keep it serializable instead of an object (because it lets me break and make things faster). This is how I would use this 'home' Xstate component in my React component (after wrapping up the root component in the provider that's exported from
index.js
of Xstate component):pages/index.js
That finishes part 1 of my discussion.
Queries
I like the way that I have arranged this, it's clean and my React components are very short. But there are some Xstate specific things that I can't figure out yet that I'd like to ask:
On spectrum a gentlemen suggested me to have a random number generated, kept in context and then coupled with guards to determine the next state - and I get it, I had the same idea before but I feel it's a little bit of hard coding and this is one small problem but if I want a randomizer in a compound state which has much more states than I can write guards for (random number lies between X and Y, switch to this) - this solution won't scale very well.
Another query I have is associated with the solution suggested to me on spectrum. Like let's say if I opt for keeping a random number in context - then how do I keep the context of one of the parallel states separate from its root machine. Like can I have a separate
context
fortheme
and a separate one forlayout
(I don't want to have separate keys in context for them, but rather separate context) and then be able to modify their own contexts with actions that I've kept separately in another file?Thanks in advance. Do share how you would go about modelling a generic UI template.
Beta Was this translation helpful? Give feedback.
All reactions