State Machine: current state's path

Hey, guys!

I implemented a state machine with Machinery to control the UI sequence I show to my user. But I have a problem.

When I am in the :ready state, I show a live_component A with Card. This card contains a question and possible answers. If the user selects the answer 1, I change the state to, let’s say “A1” and I show the next live_component B. If the user selects answer 2, I change to “A2” and show the next component C.

So far so good. This is a very basic transition and everything is working well. In my LiveView each live_component is wrapper within a condition like:

if state == “ready” do live_component A end
if state == “A1” do live_component B end
if state == “A2” do live_component C end

What’s the problem? Each live_component is shown one below the other.
So, when I transit to state “A1”, the live_component A is hidden.

So, I needed some way to construct a breadcrumb of the current state, from the initial one until the current state, like: path => [“ready”, “A1”, “B2”, “C1”]

This way, I could have:

if Enum.member?(path, “ready”) do live_component A end ← visible
if Enum.member?(path, “A1”) do live_component B end ← visible
if Enum.member?(path, “A2”) do live_component C end
if Enum.member?(path, “B1”) do live_component D end
if Enum.member?(path, “B2”) do live_component E end ← visible
if Enum.member?(path, “C1”) do live_component F end ← visible
if Enum.member?(path, “C2”) do live_component F end

I thought about using Machinery’s log_transition to manage by my own this new path property. Basically, it would be keeping and appending a new state at the end of the path list.

So, I’d like your opinion about this approach. Not sure if it is the nicest one. I’ve been researching also about the Zipper list/tree and some implementations seem to keep this path as we navigate the tree.

Maybe instead of having just one state, use multiple assigns like:

def mount(_params, _session, socket) do
  socket = assign(socket, choice_A: nil, choice_B: nil, choice_C: nil)
  {:ok, socket}
end

Then when a choice is made,

assign(socket, choice_A: 1)
# or
assign(socket, choice_A: 2)

and in the html.leex

<%= live_component @socket, LiveComponentA %>

<%= if @choice_A == 1 do %>
  <%= live_component @socket, LiveComponentB %>
<% end%>
<%= if @choice_A == 2 do %>
  <%= live_component @socket, LiveComponentC %>
<% end%>

<%= if @choice_B == 1 do %>
  <%= live_component @socket, LiveComponentD %>
<% end %>
<%= if @choice_B == 2 do %>
  <%= live_component @socket, LiveComponentE %>
<% end %>

etc…

Hey :wave:
Yes, that works but … I don’t know.

I created an issue anyways in the Machinery repo to discuss this: Does it worth thinking about tracking transitioned states? · Issue #77 · joaomdmoura/machinery · GitHub

Having a new field called path and accessible like state would be interesting. Please, join the thread in there as well :slight_smile: