-
-
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
Dealing with history #481
Comments
Thank you! History might be a little confusing in this scenario because its use-cases are more statechart-specific; i.e. to mean "return to the previous child state node(s) of this complex parent state node" rather than "return to the previous state". It's for states to recall its last active configuration. What you can do for now is specify guards on each state that use the previous states: {
D1: { on: { NEXT: 'D' } },
D2: { on: { NEXT: 'D' } },
D: {
on: {
PREV: [
{ target: 'D1', cond: (ctx, e, { state }) => state.history.matches('D1') },
{ target: 'D2', cond: (ctx, e, { state }) => state.history.matches('D2') }
]
},
// ...
} If you want to completely swap the current state with the previous state, there's the nuclear option: let currentState;
service.onTransition(state => currentState = state);
// ...
service.stop();
// restart
service.start(currentState.history); That will only go one state back in history though (you currently can't go more than one state back in time). Things get more complicated if you want to allow an arbitrary amount of backtracking, or going forward, or context-specific backtracking. I'll be investigating this area more. |
Thanks for your quick reply. I investigated the use of conditions, but it won't work if for instance you took multiple paths and saved some informations, because at some points both guards can be true but it will only go in the first one. I'm not sure about how to deal with those edge cases too, what first came in my mind was to have access to the current state inside the assign function, not just the context, and store the StateValue in an array. Let me know if you have any idea, I might be able to work on this a bit and set up a PR if needed :) |
IMO the best way to do this in XState is using the undo/redo pattern: https://redux.js.org/recipes/implementing-undo-history const questionsMachine = Machine({
id: "questions",
initial: "welcome",
context: {
questions, // { one: { ... }, two: { ... } }
question: "one",
// history stack
stack: []
},
states: {
welcome: {
on: {
NEXT: "question"
}
},
question: {
on: {
NEXT: {
actions: assign({
question: (_, e) => e.question,
stack: ctx => ctx.stack.concat(ctx.question)
})
},
BACK: {
actions: assign(ctx => {
const { stack } = ctx;
const newStack = stack.slice(0, stack.length - 1);
const prev = stack[stack.length - 1];
return {
question: prev,
stack: newStack
};
}),
cond: ctx => ctx.stack.length > 0
},
// ...
}
},
// ...
}
}); Instead of treating each question as an individual state, you treat it as a single state of "asking questions" and update the question state through Then, whenever a question is answered, you push that question to a stack. Here's an example: https://codesandbox.io/s/xstate-react-back-example-4q2vh |
In case anyone stumbles upon this in the future, I've built a simple wrapper around xState which adds undoable behaviour: https://github.com/marcelkalveram/xstate-undoable. It's not perfect yet and doesn't support redo or interpreted machines, but I thought it might be worth sharing since it's such a common scenario that people run into. |
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! |
Is there a way to integrate immer patches into this? I'm looking for a way to treat context as "history" (not state nodes), that may be an anti pattern though 🤔 |
Bug or feature request?
Feature request / guidance
Description:
First of all, thanks for the awesome library. It helped our organization manage complex state machines in a React app without redux 💪.
We are having one issue though, and it is about managing history.
Our state machine is a serie of questions, each question might lead to different paths and it has 4 final states. Let's call these 4 states A, B, C, D. There is a total of 5 paths, let's call them PA, PB, PC, PD1, PD2, where every path leads to the letter in its name.
At every state of a path, the user has the possibility to go back to the previous question.
Our problem is, when you are in D, you cannot know for sure which path was the previous. It might be PD1 and you'll be asking a question about the age of the user, or PD2 and the question is totally different.
We don't currently see any way to do this inside the app. Do you have any hint or guidance you can give us on how to manage this kind of situation ?
Thanks 🙂
The text was updated successfully, but these errors were encountered: