-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't transition back in history #188
Comments
Hi Dylan. The last active state of the machine, before the |
@djkirby, an implementation of the machine you are trying to do would be: {
initial: "step1",
states: {
step1: { on: { A: "step2" } },
step2: { on: { B: "step3", BACK: "step1" } },
step3: { on: { BACK: "step2" } },
},
} |
@carloslfu yea that's what I'm trying to avoid, because you don't always know where to go back to. That was a simple example but say step1 or step2 both took me to step3 -- I just want to go back to where it was previously. Think of a dynamic wizard flow. Maybe this isn't expected to be possible with the library. |
Yeah, I get it. But, I think explicitness is better. Right now I working in a wizard and when I draw the flow it is very important to me to have a clear understanding of what is the next step for a given event. I guess your use case is something like global navigation, in this case, you're right this is not the right approach, so the UI can enter one of those states and you what to go back. Here there are two cases. First, if you have to associate those states with URLs then the best approach is to integrate Second, if you don't have to associate it with URLs, implement it externally. This way you do it with an action that does the work at the interpreter level. Take a look at this implementation: import { Machine } from "xstate";
import { interpret } from "xstate/lib/interpreter";
let interpreter;
const history = [];
const machine = Machine(
{
initial: "step1",
states: {
step1: { on: { A: "step2" } },
step2: { on: { B: "step3", PREV: "step1" } },
step3: { on: { PREV: "step2" } },
},
on: { BACK: { actions: "back" } }
},
{
actions: {
back: () => {
history.pop();
interpreter.init(history[history.length - 1]);
}
}
}
);
interpreter = interpret(machine);
interpreter.onTransition(state => {
history.push(state);
console.log(state.value);
});
interpreter.init();
interpreter.send("A");
interpreter.send("B");
interpreter.send("BACK");
console.log("actual state:", interpreter.state.value); Live demo: https://codesandbox.io/s/q77lypnx1w As you are implementing a wizard, use the |
Ok if it isn't expected functionality to step backward arbitrarily, I'll close this issue. |
I'll reopen it because I still want to verify that the behavior is correct for these cases:
|
Is the code above a complete example for handling undo in xstate? If so, it might be good to add that to the docs, I can see it being something people would use (not sure if there's a "recipes" for section it could maybe go in?) |
What if I'm not interested in doing the following: some step can skip a step, so when I'm in the target step and I want to go back, I'd like to go back to the original previous step, considering if it was skipped or not:
|
@diegopamio exactly, I am also wondering how to handle that case. |
I assumed history states where used to fix the use case @diegopamio described. Why would you want to transition into a history state to revert back to the place you came from? I can't get it to work that way though. |
@davidkpiano just wondering if you had a chance to look into this at all. I can try to dig into it if that helps. I'll close this issue if this functionality isn't expected from the library -- just unsure if this is a bug or a feature request. |
I have similar behavior, but I am not sure if it is possible to achieve it. I have a State Machine which can go from multiple states to another state (for example the settings page, which one can make some changes and then go back). So when going back from the settings page, I want to go back to the last accessed state. Is it possible to do? |
@vctt94 it sounds like a good use case for history states |
I'm trying to do something similar:
can go to a state suspended, ON_RESUME: I need to go back to the previous state that could be created, ready or in_progress. |
@gpietro That is possible if those created/ready/in_progress states are in a nested state: active: {
initial: 'in_progress',
states: {
created: {},
ready: {},
in_progress: {},
previous: { type: 'history' }
},
},
suspended: {
on: { RESUME: 'active.previous' }
} |
https://codesandbox.io/s/xstate-state-machine-with-history-stack-6iq32?file=/src/index.js Thanks for this awesome library @davidkpiano, it's a pleasure working with something this well considered. Hope this demo makes sense! |
I just ran into this scenario and solved it by using guarded transitions. const toggleMachine = Machine({
initial: "step1",
states: {
step1: { on: { next: "step2", skipStep2: "step3" } },
step2: { on: { next: "step3", BACK: "step1" } },
step3: {
on: {
BACK: [
{
target: "step1",
cond: (_context, event) => event.meta.previousStep === "step1",
},
{
target: "step2",
}
]
}
}
}
}); Then define the send({ type: "BACK", meta: { previousStep: current.history?.value } }); Hopefully this helps someone else! |
@sean-beard thanks for this. Unfortunately, this solution doesn't work going back into nested states. // state change sequence
state.value = { views: 'step0', status: 'idle' } // #0 state.history is empty
state.value = { views: 'step1', status: 'fetching' } // #1 state.history is { views: 'step0', status: 'idle' }
state.value = { views: 'step1', status: 'idle' } // #2 state.history is { views: 'step1', status: 'fetching' } Therefore I can't target |
@dbismut can u post a complete example of what you are trying to do? A codesandbox maybe? |
I'll try to work on this asap @Andarist. I have a working solution which is based on a comment from @davidkpiano I read in some other thread (which I can't find at the moment), where I think he suggested to have "wizard" steps inside a |
@Andarist here it is https://codesandbox.io/s/wizard-machine-6k6tbs It's a bit of a contrived example, and I'm a bit new to all this. But the general idea is:
I'd be sort of ok with my implementation, but I wish I would be able to have only one function to commit the state to the stack: // What I have
states: {
zipCode: { entry: "commitZipCode", always: { target: "idle" } },
userAddresses: {
invoke: {
// ...
onDone: { actions: "commitUserAddresses", target: "idle" }
}
},
},
actions: {
commitZipCode: assign({
stack: ({ stack }) => [...stack, "zipCode"]
}),
commitUserAddresses: assign({
stack: ({ stack }) => [...stack, "userAddresses"]
}),
}
// What I'd like => same function to commit the state
states: {
zipCode: { entry: "commit", always: { target: "idle" } },
userAddresses: {
invoke: {
// ...
onDone: { actions: "commit", target: "idle" }
}
},
},
actions: {
commit: assign({
stack: ({ stack }, _, { state }) => [...stack, state.value]
}),
} |
Bug or feature request?
Bug, I believe.
Description:
When transitioning to a
history
state, the machine remains in the same state. It seems like it might be resolving the state by going forward to thehistory
state and then back one in history to the current state.(Bug) Expected result:
The machine's state reverts back to the previous state in history.
(Bug) Actual result:
The machine's state remains in the same state.
Link to reproduction or proof-of-concept:
BACK
on the machine: https://codesandbox.io/s/j31v21xvyvBACK
in a step: https://codesandbox.io/s/1zqzv81l97In
xstate@next
, thevalue
ends up as{"history":{}}
:BACK
on the machine innext
: https://codesandbox.io/s/pwr4k2rmv7BACK
in a step innext
: https://codesandbox.io/s/88mx737n5lThe text was updated successfully, but these errors were encountered: