Dynamically set form field values in React + Redux - forms

My app's store has a store.authState subtree. In this subtree, there are three things, an authToken, an isFetching boolean and, most importantly a fields object. My form contains two fields : username and password.
I have created an action called SET_FORM_FIELD_VALUE which should generate and update each field's state as they are changed by the user.
I would like my SET_FORM_FIELD_VALUE to update the fields object. If a user normally fills in both username and password fields, my store.authState should look like this:
{
authToken: undefined,
isFetching: false,
fields: {
username: "Brachamul",
password: "azerty123"
}
}
However, it seems that my current code actually just overwrites everything and I end up with :
{
field: {
username: "Brachamul"
}
}
The data in the field changes based on what field I last edited. It's either username or password.
Here is my code :
switch (action.type) {
case 'SET_FORM_FIELD_VALUE':
let field = {} // create a "field" object that will represent the current field
field[action.fieldName] = action.fieldValue // give it the name and value of the current field
return { ...state.fields, field }
How can I change it to fix my issue ?

Your return is wrong, it should be something like this
switch (action.type) {
case 'SET_FORM_FIELD_VALUE':
return {
...state,
fields: {
...state.fields,
[action.fieldName] : action.fieldValue
}
}
}
Hope it helps.

i used change() from 'redux-form'
which only re rendered that specific form input, and isued it pretty often.
everytime the user clicked a dropdown menu it suggested values in 2 input fields
i abstracted away the html from the anwser and some other stuff.
import { FieldArray, Field, change, reduxForm } from 'redux-form';
class WizardFormThirdPage extends react.component{
runInject(target,value){
target.value= value; // sets the client html to the value
// change (formName, Fieldname, Value) in the state
this.props.dispatch(change('spray-record', target.name, value))
}
injectFormvalues(){
var tr = div.querySelector(".applicator-equipment");
var name = div.querySelector("select").value;
if(!name.includes("Select Employee")){
// var inputs = tr.querySelectorAll("input");
var employeeDoc= findApplicatorByName(name); // synchronous call to get info
tractor = tr.querySelector("input")
sprayer = tr.querySelectorAll("input")[1];
// here i send off the change attribute
this.runInject(tractor,Number(employeeDoc.default_t))
this.runInject(sprayer,Number(employeeDoc.default_s));
}
}
// you have to connect it get the dispatch event.
export default connect(state => ({
enableReinitialize: true,
}))(reduxForm({
form: "myFormName", // <------ same form name
destroyOnUnmount: false, // <------ preserve form dataLabel
forceUnregisterOnUnmount: true, // <------ unregister fields on unmount
validate,
})(WizardFormThirdPage));

Related

Wagtail - how to get tags to work with `telepath` (tags in streamfield)?

I can use tags in regular page fields without any issue. When using tags within blocks (within a streamfield), the UI works and the tags are saved BUT the current page tags do not show up when loading the page in the admin. That's because the current value is not in the template anymore, it's in a JSON loaded via telepath.
I can confirm that the tags are saved and present in the data passed to initBlockWidget in the page source but these are ignored. Also, if I used a regular text field instead of the tag-widget, I can see the saved-values in the admin.
This is the code I have (which used to be enough before the refactor with telepath).
from wagtail.admin.widgets import AdminTagWidget
class TagBlock(TextBlock):
#cached_property
def field(self):
field_kwargs = {"widget": AdminTagWidget()}
field_kwargs.update(self.field_options)
return forms.CharField(**field_kwargs)
I think the following link is what I need to complete somehow to get it to work: https://docs.wagtail.io/en/stable/reference/streamfield/widget_api.html#form-widget-client-side-api
I've tried with this:
class AdminTagWidgetAdapter(WidgetAdapter):
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
register(AdminTagWidgetAdapter(), AdminTagWidget)
And under js/admin/admin-tag-widget-adapter.js:
console.log("adapter"); // this shows up in the console
class BoundWidget { // copied from wagtail source code
constructor(element, name, idForLabel, initialState) {
var selector = ':input[name="' + name + '"]';
this.input = element.find(selector).addBack(selector); // find, including element itself
this.idForLabel = idForLabel;
this.setState(initialState);
}
getValue() {
return this.input.val();
}
getState() {
return this.input.val();
}
setState(state) {
this.input.val(state);
}
getTextLabel(opts) {
const val = this.getValue();
if (typeof val !== 'string') return null;
const maxLength = opts && opts.maxLength;
if (maxLength && val.length > maxLength) {
return val.substring(0, maxLength - 1) + '…';
}
return val;
}
focus() {
this.input.focus();
}
}
// my code here:
class AdminTagWidget {
constructor(html, idPattern) {
this.html = html;
this.idPattern = idPattern;
}
boundWidgetClass = BoundWidget;
render(placeholder, name, id, initialState) {
console.log("RENDER", placeholder, name, id, initialState); // this does not show
var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
var idForLabel = this.idPattern.replace(/__ID__/g, id);
var dom = $(html);
$(placeholder).replaceWith(dom);
// eslint-disable-next-line new-cap
return new this.boundWidgetClass(dom, name, idForLabel, initialState);
}
}
console.log("here") // does show in the console
// variants I've tried:
//window.telepath.register('wagtail.admin.widgets.tags.AdminTagWidget', AdminTagWidget);
//window.telepath.register('wagtail.widgets.AdminTagWidget', AdminTagWidget);
window.telepath.register('path.where.its.used.AdminTagWidget', AdminTagWidget)
The log from my custom render method does not show. It seems that I'm not calling the right path within window.telepath.register but I don't know how what the string is supposed to be...
I'm not even sure if this is the right way forward.
Notes:
it works in regular field, the question is about tags in blocks
I'm using Wagtail version 2.13.2 but I've also tried with 2.15 without any difference.
In the console, I can log window.telepath and see my custom widget. It's just not "applied" to anything
Your WidgetAdapter class needs a js_constructor attribute:
class AdminTagWidgetAdapter(WidgetAdapter):
js_constructor = 'myapp.widgets.AdminTagWidget'
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
Any string value will work here - it just needs to uniquely identify the class, so it's recommended to use a dotted module-like path to avoid colliding with others. This then matches the string you pass to window.telepath.register on the Javascript side:
window.telepath.register('myapp.widgets.AdminTagWidget', AdminTagWidget)

AG Grid: Better way for validation row - valueSetter?

Is there a better way to validate a row in ag-grid than with valueSetter?
I can achieve the validation with that but I am not sure, if there is a better way.
https://www.ag-grid.com/javascript-grid-value-setters/#properties-for-setters-and-parsers
I want to validate two fields in the row. DateFrom and DateUntil (they are not allow to be null and DateFrom must be lower than DateUntil).
There are two ways of possible validation handling:
First: via ValueSetter function
and
Second: via custom cellEditor component
I suggest that it would be better to split the logic between custom components, but as you said you need to validate two cell-values between themselves.
On this case from UI perspective you could try to combine them inside one cell and it would be easily to work with values via one component only.
You could override the valueSetter and call the grid api transaction update instead.
Here is pseudo-code that shows how you could implement this.
valueSetter: params => {
validate(params.newValue, onSuccess, onFail);
return false;
};
validate = (newvalue, success, fail) => {
if (isValid(newValue)) {
success();
} else {
fail();
}
};
onSuccess = () => {
// do transaction update to update the cell with the new value
};
onFail = () => {
// update some meta data property that highlights the cell signalling that the value has failed to validate
};
This way you can also do asynchronous validation.
Here is a real example of an async value setter that has success, failure, and while validating handlers that do transaction updates when validation is done.
const asyncValidator = (
newValue,
validateFn,
onWhileValidating,
onSuccess,
_onFail
) => {
onWhileValidating();
setTimeout(function() {
if (validateFn(newValue)) {
onSuccess();
} else {
_onFail();
}
}, 1000);
};
const _onWhileValidating = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: true
};
params.api.applyTransaction({ update: [data] });
};
const _onSuccess = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: false,
lastValidation: true,
value: params.newValue
};
params.api.applyTransaction({ update: [data] });
};
const _onFail = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: false,
lastValidation: params.newValue
};
params.api.applyTransaction({ update: [data] });
};
const asyncValidateValueSetter = validateFn => params => {
asyncValidator(
params.newValue,
validateFn,
_onWhileValidating(params),
_onSuccess(params),
_onFail(params)
);
return false;
};
Here is a code runner example showing this in action: https://stackblitz.com/edit/async-validation-ag-grid-final
Have a look at this two snippets, these come from our internal knowledge base (accessible to customers)
When editing a value in column 'A (Required)', you will see that it does not allow you to leave it empty. If you leave it empty and return the edit, it will be cancelled.
//Force Cell to require a value when finished editing
https://plnkr.co/edit/GFgb4v7P8YCW1PxJwGTx?p=preview
In this example, we are using a Custom Cell Editor that will also validate the values against a 6 character length rule. While editing, if the value is modified outside of 6 characters, it will appear in red, and when you stop editing the row, the value would be reset, so it only accepts a complete edit if the value is valid.
//Inline Validation while editing a cell
https://plnkr.co/edit/dAAU8yLMnR8dm4vNEa9T?p=preview

How to prevent individual form elements from updating using Meteor + React?

I have a Meteor + React single-page-application with a basic form in it. Data is collected from MongoDB using the createContainer method and passed to a form component. The problem I am facing is this. A user starts completing the form but, if the data that originally populated the form changes (by another user somewhere else in the world saving the form), the createContainer method will re-compute, which in turn pushes a new set of props to the form component and therefore overwrites what the user is typing in.
For many reasons, I cannot use the shouldComponentUpdate lifecycle method within the form component. One reason is that the form contains a select element, whose list of items should still accept reactive updates.
I need a way of halting certain reactive updates, but allowing others, whilst the user is completing the form. Any suggestions?
export default FormContainer = createContainer(( params ) => {
const dataFormHandle = Meteor.subscribe('FormsPub');
const dataFormIsReady = dataFormHandle.ready();
const dataListHandle = Meteor.subscribe('ListItemsPub');
const dataListIsReady = dataListHandle.ready();
let name = "";
let listItems = [];
let listSelectedValue = null;
if(dataListIsReady) {
listItems = collections.ListItemsColl.find({_id: ListId}).fetch();
}
if(dataFormIsReady) {
let formData = collections.FormsColl.find({_id: formId}).fetch();
name = formData[0].name;
listSelectedValue = formData[0].listSelectedValue;
}
return {
name,
listItems,
listSelectedValue
};
}, Form);
...
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
name: (this.props.name) ? this.props.name : "",
listSelectedValue: (this.props.listSelectedValue) ? this.props.listSelectedValue : null
};
}
componentWillReceiveProps(nextProps) {
this.setState({name: (nextProps.name) ? nextProps.name : ""});
this.setState({listSelectedValue: (nextProps.listSelectedValue) ? nextProps.listSelectedValue : null});
}
updateFormState(){
var name = e.target.name;
var val = e.target.value;
if(name == "name"){this.setState({name: val});}
if(name == "list"){
if( typeof e.target[e.target.selectedIndex] != "undefined" ) {
this.setState({listSelectedValue: val});
}
}
}
render(){
return (
<div>
<input type="text" name="name" value={this.state.name} onChange={this.updateFormState.bind(this)} />
<Select2
value={this.state.listSelectedValue}
name="list"
onChange={this.updateFormState.bind(this)}
options={{
minimumResultsForSearch: Infinity
}}
data={this.props.listItems}
/>
</div>
);
}
}
For the data in the form that you wish to be non-reactive simply specify reactive: false in your .find(), for example:
let formData = collections.FormsColl.find({ _id: formId },{ reactive: false }).fetch();
This will prevent the data from reactively updating while the form is open.

kendo-ui grid foreign key column and mvvm

I am struggling with kendo-ui grid foreign key column and mvvm
I would like to be able to combine the "Foreign Key Column" example with the "MVVM" example
My question is: "how do I data-bind the values property of a look-up field?"
So this is kind of an older post, but I just had to work around the same issue and found this while trying to solve. Figured I'd answer the question for posterity.
The "values" property doesn't seem to work 100% in the kendo grid in MVVM. I have worked around this in a two step process.
Tack "this.viewModel" (where "viewModel" is whatever you are calling your VM) in front of the "loggerSeverityValues". This will give you a dropdownlist when editing the field.
Utilize the template functionality to display the correct value in the grid. I use a little function to make this easier:
getText: function (matchValue, valuesArray, text, value)
{
if (text === undefined)
{
text = 'text';
}
if (value === undefined)
{
value = 'value';
}
var retText = "No Value Found";
finalArr = $.grep(valuesArray, function (val, integer)
{
return val[value] == matchValue;
});
if (finalArr.length > 0)
{
retText = finalArr[0].text;
}
return retText;
}
The final look of the field will be something along the lines of this:
{ field: 'severity', width: 270, values: this.viewModel.loggerSeverityValues, template: '#: getText(severity, this.viewModel.loggerSeverityValues) #' }
Note that with the getText() function you can override the text and value parameters if you need to.
Anyway this worked for me. Kind of a workaround, but as of release 2014.3.1411 anyway it doesn't appear that the kendo MVVM bindings will work properly with foreign keys.
EDIT:
For anyone now using the kendo ng2+ components, same pattern but with a pipe transform works.
Pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'getText' })
export class GetTextPipe implements PipeTransform {
transform(value: any, idPropertyName: string, valuePropertyName: string, valueArray: any[]): any {
if (value != null && valueArray != undefined) {
let retIndex = valueArray.map(v => v[idPropertyName]).indexOf(value);
return valueArray[retIndex][valuePropertyName];
}
else {
return '';
}
}
}
Usage:
<kendo-grid-column field="severity" title="Severity" width="150px">
<template kendoGridCellTemplate let-dataItem="dataItem">
{{dataItem.severity | getText:'severity':'severityName':loggerSeverityValues}}
</template>
</kendo-grid-column>

Does Mongoose provide access to previous value of property in pre('save')?

I'd like to compare the new/incoming value of a property with the previous value of that property (what is currently saved in the db) within a pre('save') middleware.
Does Mongoose provide a facility for doing this?
The accepted answer works very nicely. An alternative syntax can also be used, with the setter inline with the Schema definition:
var Person = new mongoose.Schema({
name: {
type: String,
set: function(name) {
this._previousName = this.name;
return name;
}
});
Person.pre('save', function (next) {
var previousName = this._previousName;
if(someCondition) {
...
}
next();
});
Mongoose allows you to configure custom setters in which you do the comparison. pre('save') by itself won't give you what you need, but together:
schema.path('name').set(function (newVal) {
var originalVal = this.name;
if (someThing) {
this._customState = true;
}
});
schema.pre('save', function (next) {
if (this._customState) {
...
}
next();
})
I was looking for a solution to detect changes in any one of multiple fields. Since it looks like you can't create a setter for the full schema, I used a virtual property. I'm only updating records in a few places so this is a fairly efficient solution for that kind of situation:
Person.virtual('previousDoc').get(function() {
return this._previousDoc;
}).set(function(value) {
this._previousDoc = value;
});
Let's say your Person moves and you need to update his address:
const person = await Person.findOne({firstName: "John", lastName: "Doe"});
person.previousDoc = person.toObject(); // create a deep copy of the previous doc
person.address = "123 Stack Road";
person.city = "Overflow";
person.state = "CA";
person.save();
Then in your pre hooks, you would just need to reference properties of _previousDoc such as:
// fallback to empty object in case you don't always want to check the previous state
const previous = this._previousDoc || {};
if (this.address !== previous.address) {
// do something
}
// you could also assign custom properties to _previousDoc that are not in your schema to allow further customization
if (previous.userAddressChange) {
} else if (previous.adminAddressChange) {
}
Honestly, I tried the solutions posted here, but I had to create a function that would store the old values in an array, save the values, and then see the difference.
// Stores all of the old values of the instance into oldValues
const oldValues = {};
for (let key of Object.keys(input)) {
if (self[key] != undefined) {
oldValues[key] = self[key].toString();
}
// Saves the input values to the instance
self[key] = input[key];
}
yield self.save();
for (let key of Object.keys(newValues)) {
if (oldValues[key] != newValues[key]) {
// Do what you need to do
}
}
What I do is use this.constructor within the pre-save route to access the current value in the database.
const oldData = this.constructor.findById(this.id)
You can then grab the specific key you're looking for from the oldData to work with as you see fit :)
let name = oldData.name
Note that this works well for simple data such as strings, but I have found that it does not work well for subschema, as mongoose has built in functionality that runs first. Thus, sometimes your oldData will match your newData for a subschema. This can be resolved by giving it it's own pre-save route!