How to set React Final Form values on request and validate after that? - react-final-form

I have kind of a convoluted use case here. I have an API that responds with a JSON that maps to React components, including form fields (their values, validations, etc). Every component in the page receives props from the backend, so if I make a request I have to update those props, that's why I save the props to state and call setState after every request that should re-render the components.
So I have a RFF with two fields. The form initial value comes from the backend as well as the fields' initial validations (the json maps to validation functions).
I am using this Auto Save with debounce example to send a request to the backend every time the user completes the form and it's valid. Now comes the tricky part for me.
The form must receive the new values from the backend (I do this by calling a function that builds the form values from the page state inside <Form initialValues={fn} />, that causes the form to re-render with the new values in sync with the state) but it also receives the new validations (they can change depending on the values, for example, if field 2 should be at least 20% of field 1), but the form only shows validations on blur or when an input changes.
Also, the fields are text+dropdowns, and when I select an option another request is sent with the new dropdown value (this is desired behavior) but then the response comes in and it updates the fields' values and then ANOTHER request is sent because the inputs changed (this is NOT a desired behavior, and it has to do with the Auto Save example, the diff part).
So, to try and explain it better:
API call is made, responds with something like:
field1: {
value: '500',
dropdown_value: 'A',
validation_value: '100',
validation_msg: 'value must be 100 or more', // this depends on validation_value
},
field2: {
value: '1000',
dropdown_value: 'B',
validation_value: '300',
validation_msg: 'value must be 300 or more', // this depends on validation_value
},
// other components props...
Page is rendered with those values as initial form values
User types 600 in the first field
<AutoRequest /> fires a debounced request with response:
field1: {
value: '600',
dropdown_value: 'A',
validation_value: '100',
validation_msg: 'value must be 100 or more',
},
field2: {
value: '1000',
dropdown_value: 'B',
validation_value: '9999', // changed
validation_msg: 'value must be 9999 or more', // changed, now the field is invalid
},
// other components props...
The form should be invalid but validation happens on change or blur so it doesn't show any errors nor disables the submit button (ERROR 1)
Now let's assume the form is valid. The user selects a new option from the first dropdown, this changes form values so <AutoRequest /> fires a new debounced request with response:
field1: {
value: '7777', // changed, value transformed in the backend
dropdown_value: 'A2', // changed
validation_value: '7777', // changed
validation_msg: 'value must be 7777 or more', // changed but valid
},
field2: {
value: '8888', // changed
dropdown_value: 'B2', // changed
validation_value: '8888', // changed
validation_msg: 'value must be 8888 or more', // changed but valid
},
// other components props...
Because the value from at least one input changed after the request, another request is fired up but the API returns the same, so this is undesired (ERROR 2)
I know it's a long post, I'm sorry, but this has been troubling me for a while and I've solved issues in isolation, but when all of these requirements come together I can't seem to find a solution that fits every need of my use case.

I ended up with a solution that I don't like too much but it gets the job done for my use case: https://codesandbox.io/s/ejemplo-react-final-form-nrpgbp
It simulates an auto request that changes the inputs, but it's also fired every time a form value is changed, so in this case is only for testing the change in a dropdown (if you change the inputs, it will infinitely send new requests. If you want to test that, you will have to modify the request response to match your inputs' current values). Also, the validations change on request and they are fired up as the form re-renders instead of on change or blur.

Related

Update type of trigger is not working in Zapier

I am trying to create a trigger (Poll based) in Zapier app/integration which should run the zap when an existing item will be updated.
API response contains an array like this:
[
{ id: 1, title: 'AWS', time: '2021-12-03T11:41:13.615Z', modTime: '2021-12-03T11:41:13.615Z' },
{ id: 2, title: 'GCP', time: '2021-12-03T11:41:13.615Z', modTime: '2021-12-03T11:46:13.615Z' },
]
Now, as per the doc, if an item will contain id and updated_at key then it should work if same record will be updated with last modified timestamp field.
To make an update trigger, use an API endpoint that lists all items, both new and updated, or alternately an endpoint that lists only updated items. Zapier needs a composite id field that changes whenever the item is updated (ideally z.hash('md5', item.id + item.updated_at)), so subsequent updates aren’t be filtered out by Zapier’s deduper.
For that, I created a new key updated_at and copied the value of modTime key. But this trigger is working only for new records and not for any update in existing record. Am I missing something ? I can make id every time new like this { id: rec.id + ':' + rec.modTime ... } but it will run for new records as well which I don't want.
Here is my code:
// import statements
const listData = async (z) => {
const records = await getTaskList(z);
return records.map((rec) => ({
...rec,
updated_at: rec.modTime
}));
};
export default {
display: {
description: 'Triggers when a task will be updated.',
label: 'Task Updated',
},
key: 'task_updated',
noun: 'Updated Task',
operation: {
outputFields: outFields,
perform: listData,
sample: getSampleData(),
},
};
Great question! The Zapier deduper only cares about the field named id. That's why your solution of only adding a key only works for new items- the deuper doesn't look at updated_at at all and is working as intended (new items only). From those docs:
Zapier needs a composite id field that changes whenever the item is updated
So your second approach is correct. The id field should be a combination of the original id and the updated_field. This will trigger for all new and updated objects.
Filtering out new objects depends on the API you're sourcing data from. You can do a .filter before your .map, but will need a way to identify new (not updated) objects. Shot in the dark, but if time is the created date and modTime is the updated date, you could filter to only return objects where time !== modTime. But, this may not be the case.
Ultimately, whatever you reeturn from listData will get send to the deduper and triggered on if that zap hasn't seen that id before.
Hope that clears it up!

Allowing only one answer in Google forms

Hi I want to create a Google form which i can collect student data,
And i want to make a way to allow my students to only enter one answer one time
in example I want to use this method in the question which asks for the admission number, admission number is a uniqe number which will not be repeated. So if a student enter an admission number that is already entered , I want them to see an error message with "The admission number has been already used"
Issue:
Avoid multiple form responses to be submitted with the same value for a specific form item.
Workflow:
In order to avoid certain responses to be submitted, you have to use some kind of data validation for the Admission number item, as shown in Set rules for your form.
This data validation should be updated every time a form response is submitted, since the newly submitted Admission number should be forbidden in future responses.
In order to update this validation every time the form is submitted, you could use an Apps Script onFormSubmit() trigger, which would then fire a function on every form submission. Let's call this function updateDataValidation.
The onFormSubmit trigger can be installed either manually, following these steps, or programmatically, via executing the function createTrigger from the code sample below once.
Every time updateDataValidation fires, it should do the following:
Find all the previous responses for your Admission number item. You could first retrieve all form responses via Form.getResponses() and then find the responses related to your item by checking its title (see Item.getTitle()).
Transform the array of admission numbers retrieved in previous step into a regex pattern that can be used for the data validation, which could be like this: ^{number1}$|^{number2}$|^{number3}$....
Use TextValidation in order to update the validation rules for your item.
Code sample:
function updateDataValidation(e) {
const itemTitle = "Admission number"; // Name of the form item. Change accordingly
const errorMessage = "The admission number has been already used.";
const form = e ? e.source : FormApp.getActiveForm();
const responses = form.getResponses();
const admissionNumbers = responses.map(response => {
return response.getItemResponses().find(itemResponse => {
return itemTitle === itemResponse.getItem().getTitle();
});
}).map(itemResponse => itemResponse.getResponse());
const numbersPattern = admissionNumbers.map(number => `^${number}\$`).join("|");
const item = form.getItems().find(item => item.getTitle() === itemTitle);
let validation = FormApp.createTextValidation()
.setHelpText(errorMessage)
.requireTextDoesNotMatchPattern(numbersPattern)
.build();
item.asTextItem().setValidation(validation);
}
function createTrigger() {
const form = FormApp.getActiveForm();
ScriptApp.newTrigger('updateDataValidation')
.forForm(form)
.onFormSubmit()
.create();
}
Notes:
In the code sample above, the item Admission number is assumed to be named Admission number. Change that from the code if that's not the case.
In the code sample above, the item Admission number is assumed to be a text item, like Short answer.

setting 'replace_original' to false while responding to Slack message action request doesn't work

Background:
I am using the python slack API (slackclient) to build an iterative sequence of data-gathering actions in ephemeral messages.
The core of this works fine. After processing the incoming request that contains the user's interaction with a set of message buttons (or menus), I respond immediately with a JSON body, as described in the "Responding right away" section of the official slack docs.
The problem:
Every response replaces the preceding message+attachments. In many cases, this is what I want, but there are situations where I want to add a response rather than replace the previous message.
Per the slack docs,setting replace_original to false should do just that. But the following code, snipped from my handling of a simple button click (for example), replaces the original button (and the text message to which it was attached):
r = {
'response_type': 'ephemeral',
'text': 'foo',
'replace_original': 'false'
}
log.debug("Returning: {}".format(json.dumps(r)))
resp = Response(response=json.dumps(r),
mimetype="application/json",
status=200)
return resp
I have tried this with and without the delete_original and response_type fields, with no change.
In short, it appears that in this case the replace_original field isn't doing anything at all; behavior is always as if it were set to 'true'.
I feel like I must be missing something here - any help is greatly appreciated.
Simple solution here: the slack API is expecting a boolean, not a string. So 'replace_original': 'false' in the above snippet ends up as {"response_type": "ephemeral", "text": "foo", "replace_original": "false"} after the json.dumps() call, which is invalid.
Instead, setting 'replace_original': False becomes {"response_type": "ephemeral", "text": "foo", "replace_original": false}, which then has the expected behavior

webix: Validating edits from datatable on the server

I have the following scenario:
An datatable with some editable columns which validate for input on the client with the webix rules. There are columns though, that cannot be validated on the client, but on the server only (ie for unique id/code).
An approach would be to create a rule and validate with webix.ajax in synchronous mode that I would prefer to avoid this at all means.
I thought I could validate on 'save'. The server can return a status response with error or success. I can catch this with onAfterUpdate event of the datatable (correct me if there is a better way, but it works this way).
At this point, I would like to display a validation error on the datatable if the server script returns an error status and mark the row (and possibly the corresponding column/cell) with error.
I thought I could use the callEvent method on the datatable and fire a onValidationError event but I didn't manage to make that work.
save: {
url: "save.php",
autoupdate: true,
on:{
onAfterUpdate:function(response, id, details) {
if (response.status == 'error')
myDataTable.callEvent('onValidationError');
}
}
}
The documentation states that I can pass some parameters to the event from callEvent but I could not find any specification on the docs. The code above does not work (the event is not fired).
So the question is: How can I fire a onValidationError event for the datatable using callEvent?
or what would be another approach to use webix to show the error on the datatable with validation on the server side?
Thank you.
Instead of calleing onValidationError event you can use
//mark cell, call after error response
myDataTable.addCellCss(id, columnId, "webix_invalid");
//remove mark, call after success response
myDataTable.removeRowCss(id, "webix_invalid");
which will mark the cell as non-valid.
On a side note, if you want to trigger some event with parameters, you can use code like next. Just beware that triggering an event is not a good way to change the component's state ( it can be used to trigger your own event handler though )
myDataTable.callEvent("event name", [param1, param2, param3])
just

Transforming data returned while using jQuery's Autocomplete function

I'm trying to use the Autocomplete plugin to produce a search box similar to the one at IMDB, I want it to:
Show more than just text in the options.
To act like a link when an option is selected (as each option will be a unique record).
That, if the user chooses, they press the button and a search is performed on their input.
The docs say that there are 3 types of datasource it will work with:
A data source can be:
an Array with local data
a String, specifying a URL
a Callback
I can get the autocomplete to work with the 2nd option, but then there isn't the ability to transform the data returned, it just takes the JSON and puts it straight in the dropdown. This means the source url can only return data in the format {label: "blah", value: "blurg"}.
If I could inject a transformation function, then I could have the url return whatever JSON I like, the function would change the data into the format the autocomplete expects but also formatted as I wish, and all without changing the response served by the url (I'm not going to return HTML from there, only JSON).
e.g. The url could return this:
{ label:"Grosse Point Blank", id: 3, img:"/imgs/gpb.png",...}
and a transform function could mung it into something like this:
{ label:"<a href='/films/3/grosse-point-blank'><img src='/imgs/gpb.png' />Grosse Point Blank</a>", value="grosse-point-blank"}
I've tried using option 3, a callback, with a getJSON call, but I can't get it to work. The nearest code I've found to what I may need is here, but it has options that aren't listed in the current docs for Autocomplete and I don't understand how to use the response object.
Is there an example of using the callback method with an AJAX request, or how I can inject a function that transforms the code?
You can use the _renderItem() private method of autocomplete to format the return data. It looks like this:
$("#element").autocomplete({...}).data( "autocomplete" )._renderItem = function( ul, item ) {
//...
};
You can see the method definition here https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.autocomplete.js#L520
An example of code that I have used:
$("#element").autocomplete({...})
.data("autocomplete")._renderItem = function( ul, item ) {
return $( "<li></li>" ) // create the list item
.data( "item.autocomplete", item ) // save the data to the DOM
.append( "<a>"+ item.label + "</a>" ) // add the HTML
.appendTo( ul ); // append to the UL
};
Here's a brief structure when using callback method.
source: function( request, response ) {
$.ajax({
...
success: function( data ) {
// do transformation of data here
response(data);
}
});
}
Quoted from your link, http://api.jqueryui.com/autocomplete/#option-source
The third variation, the callback, provides the most flexibility, and
can be used to connect any data source to Autocomplete. The callback
gets two arguments:
A request object, with a single property called "term", which refers
to the value currently in the text input. For example, when the user
entered "new yo" in a city field, the Autocomplete term will equal
"new yo".
A response callback, which expects a single argument to
contain the data to suggest to the user. This data should be filtered
based on the provided term, and can be in any of the formats described
above for simple local data (String-Array or Object-Array with
label/value/both properties). It's important when providing a custom
source callback to handle errors during the request. You must always
call the response callback even if you encounter an error. This
ensures that the widget always has the correct state.

Resources