Generic way to revert to old state if asynchronous action failed? - react-native

I am keeping all state in the store, and handle all async actions using redux-sagas.
So if I for example have an integer field, the changing of which should fire an async action, I connect the TextInput value to the actual value in the store.
As soon as the user changes the value and leaves the field, I dispatch an action.
This will be picked up by a saga, which will call an API. But if that fails, I need the previous value restored. What is a good way to handle that?
Currently I pass the old and new value in the action, so that the saga can put a new action to restore it, but this feels like a kludge. Hasn't this been encountered before?

Well, if you want to restore the old value you will have to store it somewhere. However, rather than passing both old and new value to your action I would just remember the old value in your state.
Your state might look like this:
const state = {
value: 5,
oldValue: 4
};
Then when the value changes, you will update oldValue to value and value to new value. If the API call fails you can just set value to oldValue again.
Full example: https://codesandbox.io/s/0RORjNm8y

Related

SubmitForm then Patch results in "The data returned by the service was invalid"

I'm building a PowerApps app on Azure SQL
The requirement
I have a form which has "Save" and "Confirm" buttons.
Both buttons should save the form data. The Commit button should also set database column "Confirm" to 1
I've read at length about how I can programatically override the update value of a hidden control for this. But I'm not satisfied with the level of complexity (maintenance) required to get this working, i.e.
Populate a variable with the current db value
In the button code set the variable value
In the form field, set the update property to the variable
What I'm Trying
So I'm trying a different approach: SubmitForm then Patch. Even though this requires an extra database call, I'd like to understand if this will work. This is the code for OnSelect in the commit button:
// Save the record
SubmitForm(frmEdit);
// Update confirmed to 1
Patch('[dbo].[Comments]',cRecord,{Confirmed:1});
Some Complexities
Note that my record is a variable, cRecord. In short I want this app to be able to upsert based on URL parameters.
This is my App.OnStart which captures URL values, inserts a record if required. Regardless, the result of this event is that cRecord is set to the record to be edited.
// Cache employees and store lookups (as they are in a different db)
Concurrent(Collect(cEmployees, Filter('[dbo].[SalesPerson]', Status = "A")),Collect(cStores, '[dbo].[Store]'));
// Check for parameters in the URL. If found, set to Edit/Add mode
Set(bURLRecord,If((!IsBlank(Param("PersonId")) && !IsBlank(Param("Date"))),true,false));
// If URL Parameters were passed, create the record if it doesn't exist
If(bURLRecord,
Set(pPersonId,Value(Param("PersonId")));
Set(pDate,DateValue(Param("Date")));
// Try and find the record
Set(cRecord,LookUp('[dbo].[Comments]',SalesPersonId=pPersonId && TransactionDate = pDate));
If(IsBlank(cRecord),
// If the record doesn't exist, create it with Patch and capture the resulting record
Set(cRecord,Patch('[dbo].[Comments]',Defaults('[dbo].[Comments]'),{SalesPersonId:pPersonId,TransactionDate:pDate}))
);
// Navigate to the data entry screen. This screen uses cRecord as its item
Navigate(scrEdit);
)
frmEdit.Item is set to cRecord. As an aside I also have a gallery that sets this variable value when clicked so we can also navigate here from a gallery.
The navigating using new and existing URL parameters works. Navigating from the gallery works.
The problem
When I press the Commit button against a record which has Confirmed=0 I get this popup error:
The data returned by the service is invalid
When I run this code against a record which already has Confirmed=1 I don't get an error
If I run the PowerApps monitor it doesn't show any errors but it does show some counts being run after the update. I can paste it here if required.
I also tried wrapping the Path in a Set in case it's result was confusing the button event but it didn't make a difference.
What I want
So can anyone offer me any of the following info:
How can I get more info about "The data returned by the service is invalid"?
How can I get this to run without erroring?
Is there a simpler way to do the initial upsert? I was hoping a function called Patch could upsert in one call but it seems it can't
With regards to the setting field beforehand approach, I'm happy to try this again but I had some issues implementing it - understanding which control where to edit.
Any assistance appreciated.
Edit
As per recommendations in the answer, I moved the patch code into OnSuccess
If(Confirmed=1,Patch('[dbo].[CoachingComments]',cRecord,{Confirmed:1}));
But now I get the same error there. Worse I cleared out OnSucces and just put SubmitForm(frmEdit); into the OnSelect event and it is saving data but still saying
The data returned by the service was invalid
First things first,
Refactoring has multiple steps,
I can t type all out at once...
The submitform and patch issue:
Never use the submitforn with extra conplexity
Submitform is only the trigger to send the form out,
The form handler will work with your data...
If you hsven t filled out the form correctly, u don t want to trigger your patch action...
So:
On your form, you have an OnSucces property,
Place your patch code there...
Change your cRecord in your patch statement:
YourForm.LastSubmit

HandsOnTable (Vue Wrapper) - Associate row with database id?

I'm trialling HandsOnTable via the Vue wrapper to make a simple database editor. I can populate the table easily, however I now need to save changes back to the database.
If I use the afterChange() method hot will give me the changes in the cells that have changed, however I need to be able to associate those changes with a database id to send them back to the server. Any idea of how to do this, and also is it possible to get the changes associated with a row? Would it also be possible to do this without displaying the database id to the user in the table?
To answer your main question, you can associate metadatas to cells. So you can put your technical id in your first column for example or a hidden column (or whereever you want).
hot.setCellMeta(0, 0, 'myIdName', 'myIdValue');
where "hot" is your Handsontable instance. (documentation reference)
You can then access getCellMeta(0, 0).
Second question :
and also is it possible to get the changes associated with a row ?
You already are able to get the changes of specific rows by filter the changes in the afterChange hook. Taking the example from Handsontable documentation this is how you get the row :
new Handsontable(element, {
afterChange: (changes) => {
changes.forEach(([row, prop, oldValue, newValue]) => {
// Some logic...
});
}
})
As you can see changes is an array of change that contain row and col (prop).
Hope this helps.

Stop operation in change()

Is there a way to stop an remove operation in model.document.on('change') ?
I listen to change with this code:
model.document.on('change',(eventInfo,batch) => {
// My code here.
}
And it works fine, in so far as I do get and can inspect all changes. But there does not appear to be any way to reject the change.
I tried to call eventInfo.stop() and reset() on the differ. Both of these methods does stop the change, but always later results in a model-nodelist-offset-out-of-bounds:
exception if I try to stop a remove operation.
What I am trying to do is to change how text delete works, so when the user delete text, instead of really deleting the text from the editor, I create a marker which marks which text have been "deleted" by the user. (For optional change control).
Instead of stopping the change, maybe you could save the data value after a change and "reset" to the previous value when a delete happens:
var oldData = '';
var editor = ClassicEditor
.create(document.querySelector('#editor'))
.then(editor => {
editor.model.document.on('change',(eventInfo, batch) => {
if(oldData.length > editor.getData().length) {
// or use eventInfo.source.differ.getChanges() and check for type: "remove"
editor.setData(oldData);
}
oldData = editor.getData();
});
})
.catch( error => {
console.error( error );
});
Note instead of checking the data length, you could loop through the changes happened using eventInfo.source.differ.getChanges() and checking if there were changes of type "remove".
An operation that was already applied (and the change event is fired after operations are applied) cannot be silently removed. They need to be either reverted (by applying new operations which will revert the state of the model to the previous one) or prevented before they actually get applied.
Reverting operations
The most obvious but extremely poorly performing solution is to use setData(). That will, however, reset the selection and may cause other issues.
A far more optimal solution is to apply reversed operations for each applied operation that you want to revert. Think like in git – you cannot remove a commit (ok, you can, but you'd have to do a force push, so you don't). Instead, you apply a reversed commit.
CKEditor 5's operations allow getting their reversed conterparts. You can then apply those. However, you need to make sure that the state of the model is correct after you do that – this is tricky because when working on such a low level, you lose the normalization logic which is implemented in the model writer.
Another disadvantage of this solution is that you will end up with empty undo steps. Undo steps are defined by batches. If you'll add to a batch operations which revert those which are already in it, that batch will be an empty undo step. Currently, I don't know of a mechanism which would allow removing it from the history.
Therefore, while reverting operations is doable, it's tricky and may not work flawlessly at the moment.
Preventing applying operations
This is a solution that I'd recommend. Instead of fixing an already changed model, make sure that it doesn't change at all.
CKEditor 5 features are implemented in the form of commands. Commands have their isEnabled state. If a command is disabled, it cannot be executed.
In CKEditor 5 even features such as support for typing are implemented in the form of commands (input, delete and forwardDelete). Hence, preventing typing can be achieved by disabling these commands.

Accessing Vue Store values in Router

I need to access a variable from my store in my router. This variable is called 'isAdmin'. Now, when I'm using the following code, I'm getting the initial state (which has the value of 'false'):
console.log(store.state.isAdmin)
Although I've updated the 'isAdmin' value to 'true' committing an action, the state of 'isAdmin' continues to be as the initial state.
Now, if I console.log the following object:
console.log(store.state)
I see this:
My question is: How do I get to the value of isAdmin after I've amended it in my store?
Thank you!!!

Sails.js: How to access and modify req.params in middleware

I want to implement a mechanism to obfuscate the id fields in my application . Right now all the id fields are integers. I want to use some sort of reversible hashing to create random strings corresponding to the id fields. Also, I am trying to accomplish this with minimal changes to the overall project.
One thing that came to my mind was to write a middleware to intercept every request and response object and check for the presence of id field. If the request contains id field and it is an obfuscated version, decode the string and replace the request parameter with the integer id.
If the response contains the integer id, run the encode function on it to send the obfuscated id to the client.
The problem I am facing is with modifying the req object. The id field can be present in req.body or req.params or res.query. However, in the middleware, I cannot see the id field when it is present in req.params.
I tried using policies. But the problem I am facing there is even after changing the req.params, the changes are lost when the control reaches the controller. What would be the recommended way of solving this problem ?
Here is the sample code:
In the policy:
module.exports = function (req, res, next) {
req.params.id = '12345';
req.query.pageSize = 30;
req.body = {};
sails.log.info('req.params', req.params);
sails.log.info('req.query', req.query);
sails.log.info('req.body', req.body);
return next();
};
I am just modifying values of req.params, req.query and req.body.
When I try to access these values in the controller, the values of req.query and req.body are the modified values as changed in the policy. However, req.params changes back to what was sent by the client and the changes made in the policy are lost
I think you are confusing policy and middleware? Is your code above in api/policies? If so, you still need to define which controller(s) this policy is applied to in config/policies.
So config/policies.js should look like:
modue.exports.policies = {
// If you really want this policy for every controller and action
'*': 'policyName.js',
// If you want it for a specific controller. '*' denotes every action in controller
specificController: {
'*': 'policyName.js'
},
// If you want it for a specific action on a specific controller
specificController: {
'specificAction': 'policyName.js'
}
};
Also I'd like to add. Policies are generally meant for authorization of controllers but this seems like a decent use case. Since every single request is not going to have these fields this should be a policy. Policies are great when applying something to a handful of controllers/actions. Middleware is great when you need to apply to every single action that comes into your app.
http://sailsjs.org/documentation/concepts/policies
http://sailsjs.org/documentation/concepts/middleware
Gitter response:
sgress454 #sgress454 10:45
#mandeepm91
In the policy, if I change req.body or req.query, the changes persist in the next policy or controller. However, changes made to req.params are lost.
This is one of the main use cases for req.options. This object is intended to be used to store request data that can be mutated by one handler before being passed on to the next. The req.params object is meant to provide each handler with a copy of the original request parameters.
How you approach this really depends on what your goal is. If you really need each request handler (that is, policies and controller actions) to see an encoded version of the ID, then a good approach would be to encode the ID in a policy as #S-Stephen suggested, store that value in req.options.id and refer to that in your other request handlers. If, on the other hand, you're really only concerned with the response having the encoded ID, the suggested practice is to use the res.ok() response in your handlers (rather than res.send() or res.json), and adjust the code for that response in api/responses/ok.js to encode the ID before sending. You can also use a custom response if this is only required for certain requests. See custom responses for more info.
Hi #sadlerw, you should be able to modify the code for res.ok() in your api/responses/ok.js file to have it always return JSON if that's what you want for every response. If it's something you only want for certain actions, you could create a custom response instead and use that where appropriate.