The currently existing assign action cannot delete properties of the context object. It only assigns new properties or changes existing ones.
Is there a way in xstate to assign a completely new object to the context in action?
If not, I see this functionality as a new function e.g. replace with the API similar to the existing assign function. It takes the context "replacer", which represents how the current context should look.
The "replacer" can be an object or a function and the main difference is that the context is completely replaced with the new object.
EDIT: this might not be the case. Looking into this.
Yes, you can change the entire context (replace it) by using a function, like this:
// assume you have { one: 1, two: 2 }
actions: assign(ctx => {
return { one: ctx.one }; // delete two
});
See here: https://xstate.js.org/docs/guides/context.html#assign-action
Thank you for the immediate reply! Actually I tried the mentioned approach and it doesn't work as you described. Here is the reproduction: https://codesandbox.io/s/vigorous-volhard-f0en1
Am I doing something wrong?
Would it be sufficient to just set { unwantedProp: undefined }?
Actually not, because when I鈥檓 saving a context with undefined props in MongoDB, it replaces them with nulls. Yes, there is an option in mongo driver that prevents this behaviour, but MongoDB drivers must not be responsible for xstate鈥檚 unexpected behaviour.
With all respect to the effort spent on xstate and appreciation for the hard work of supporting and improving the code, the fact that xstate doesn鈥檛 work as you, the creator of xstate, described means that the issue requires a fix, not a workaround solution.
I agree that I would expect the same result as @nikogosovd suggests (and which @davidkpiano also thought that it would be the case), but I'm not sure if everyone would expect it the same way. Given the fact that this logic is already in place for quite some time already, I would consider changing it right now unsafe because it could break somebody badly.
That being said - I believe that this could be tackled in upcoming v5, we only would need to check how it relates to SCXML spec. From the top of my head, it seems that there is no way in it to get "rid off" some datamodel value beyond doing explicitly what @davidkpiano has suggested - assigning undefined to all other properties.
the fact that xstate doesn鈥檛 work as you, the creator of xstate, described means that the issue requires a fix, not a workaround solution.
This is not necessarily true. We are all humans, we have faulty memory etc. It really is no surprise that something is working differently than its creator thinks it does. Even given such situation, it doesnt immediately mean that the code should be changed in a way that the author has thought it works already. Each situation is different and has to be evaluated on per-case basis. The author might have forgotten some reasoning he has made in the past, but it doesnt invalidate that reasoning - it could have been sound. I'm not saying anything here about this particular case, it's just a general remark. I bet you have also written software which has behaved in a different way that you have though it does behave.
Actually not, because when I鈥檓 saving a context with undefined props in MongoDB, it replaces them with nulls. Yes, there is an option in mongo driver that prevents this behaviour, but MongoDB drivers must not be responsible for xstate鈥檚 unexpected behaviour.
This has a straightforward workaround: don't link XState's behavior directly to MongoDB, and instead introduce some sort of adapter layer:
function removeUndefinedProps(object) {
return Object.keys(object).reduce((acc, key) => {
if (object[key] === undefined) return acc;
acc[key] = object[key];
return acc;
}, {});
}
// anywhere you write to MongoDB, use the function
function writeToMongo(data) {
return someMongoClient.write(removeUndefinedProps(data));
}
Alternatively, you can have a property in context that represents the data to send to MongoDB, and assign({ thatProp: () => {...} }) will work as expected (regarding removed properties) since it doesn't deep-merge.
@Andarist You are right, this fix can break someone's code. That's why an introduction of the replace function as I described in the first message here will not break anything. And then, the upcoming v5 may merge this functionality into the assign((ctx, event) => ({/* ... */})) like calls and remove the replace function. What do you think?
@davidkpiano thank you for the detailed workaround description, I will definitely do something like that for now before some solution in xstate is available.
the fact that xstate doesn鈥檛 work as you, the creator of xstate, described means that the issue requires a fix, not a workaround solution.
It was a kind of overreacting from my side, I apologize. Of course, I've also made mistakes. Actually I'm very happy with xstate and until now all the problems that I've faced were already solved in closed issues or in the documentation. This is an indicator of a high-quality product that you @davidkpiano and @Andarist and other contributors made.
Greatly appreciated, @nikogosovd. I will add a test in V5 to ensure the assign replace behavior is exactly as you expect it to be here.
I have also thought that this change would be sound and good at first, but now I'm questioning it a little bit.
removeUndefinedProps is completely fine. It's perfectly understandable, that you might need to do some extra conversion when integrating with some other system (Mongo in this case)
Most helpful comment
I agree that I would expect the same result as @nikogosovd suggests (and which @davidkpiano also thought that it would be the case), but I'm not sure if everyone would expect it the same way. Given the fact that this logic is already in place for quite some time already, I would consider changing it right now unsafe because it could break somebody badly.
That being said - I believe that this could be tackled in upcoming v5, we only would need to check how it relates to SCXML spec. From the top of my head, it seems that there is no way in it to get "rid off" some datamodel value beyond doing explicitly what @davidkpiano has suggested - assigning undefined to all other properties.
This is not necessarily true. We are all humans, we have faulty memory etc. It really is no surprise that something is working differently than its creator thinks it does. Even given such situation, it doesnt immediately mean that the code should be changed in a way that the author has thought it works already. Each situation is different and has to be evaluated on per-case basis. The author might have forgotten some reasoning he has made in the past, but it doesnt invalidate that reasoning - it could have been sound. I'm not saying anything here about this particular case, it's just a general remark. I bet you have also written software which has behaved in a different way that you have though it does behave.