Posted on • Edited on • Originally published atblog.robertbroersma.com
Stateful Styles With XState and Styled System
You've probably seen a button like this one before:
<Button>CoolButton</Button>
One that has options:
<Buttonsecondary>SecondaryCoolButton</Button>
Maybe even more options:
<Buttontertiary>TertiaryCoolButton</Button>
But what if I did this?
<Buttonsecondarytertiary>Secondary?CoolButton</Button>
That's probably not allowed. I guess we'll change the API to avoid that:
<Buttonvariant="secondary">SecondaryCoolButton</Button>
This is kind of a state machine! YourButton
can only be in onevariant
(state) at a time.
Here's what a parallel state machine (basically multiple independent state machines) would look like:
<Buttonvariant="secondary"mode="dark">DarkSecondaryCoolButton</Button>
I've found that these kind of style props work very well with logical state machines. Check out the following example of a... thing:
It's a parallel state machine with 3 sub machines:
- One machine that let's you change the shape:
- From Circle to Square
- From Square to Diamond
- From Square to Circle
- From Diamond to Square
- One machine that let's you change the color:
- From Red to Blue
- From Blue to Green
- From Green to Red
- One machine that let's you change the size:
- From Small to Big
- From Big to Small
If we want to craft some stateful styles for this thing, we'd need a component with an API like this:
<Thingshape="circle|square|diamond"color="red|blue|green"size="small|big"/>
You can implement it however you like, but what I like to do is usestyled-system
'svariant
API, because it maps nicely to the state machines we defined:
importstyledfrom'styled-components'import{variant}from'styled-system'constThing=styled(variant({prop:'shape',variants:{square:{/** Make it square */},circle:{/** Make it circular */},diamond:{/** Make it a diamond */},},}),variant({prop:'color',// ...}),variant({prop:'size',// ...}))
(You can use it with either Emotion or Styled Components)
Now to wire it up to our state machine usingxstate
and@xstate/react
functionApp(){const[state,send]=useMachine(shapeMachine);return<Shape{...state.value}/>}
Ta-da! A little explanation:
In case of a hierarchical or parallel state machine, ours being the latter,state.value
contains an object representation of our current state (checkthe docs for more info). Our state could look something like this:
//state.value{shape:"circle",color:"red",size:"small"}
Which happens to look exactly like our component's prop interface! Of course you can also dothis if you want your code to be a bit more explicit and readable:
functionApp(){const[state,send]=useMachine(shapeMachine);const{shape,size,color}=state.valuereturn<Shapeshape={shape}size={size}color={color}/>}
Here's aCodeSandbox with a fully working example.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse