React - Mobx Validation input Field - mobx

I have been searching for a MOBX validation on a input field, but I havent be able to find anything, I found "MobX-input" which requires a form but I dont have any form. another one that I found was "mobx-react-form" with ValidatorJs which again uses form. any hint or example would be appreciated . I just wanna be able to use it on plain input field
<Input placeholder="FirstName" type="text"
defaultValue={Contact.FirstName} onChange={(e) => handler(e)} />

Simple validation is pretty easy to create on your own with MobX. For a single field like this, a simple method for validating the function could look like this:
in the component we have an error field that only shows if the input has been submitted (which could be triggered by a button push or whatever)
return <div>
<input placeholder="FirstName" type="text"
defaultValue={Contact.FirstName} onChange={(e) => handler(e)} />
{submitted && <span className="error-message">{Contact.FirstNameError}</span>}
</div>;
In the observable class (I used the non-decorator style), we define the field as an observable, and an error message class as a computed value.
class Contact {
constructor() {
extendObservable({
submitted: false,
FirstName: observable(),
FirstNameError: computed(() => {
if(this.FirstName.length < 10) {
return 'First name must be at least 10 characters long';
}
// further validation here
return undefined;
})
})
}
}
You could easily add an extra hasError computed value that just checks to see if FirstNameError has a value.
This method scales to a few inputs. If you start having a bunch of them, then you'd want to look into an abstraction like a 3rd party library or something you write yourself to manage your validations. You could write a function to generate the computed properties you need based on a little configuration.

Related

VueJS - reusing prop (field name) at definition of other bindigns

I don't know how to exactly name this, I have some sort of forms in Vue which consist of different input types or more complex wrapped elements or even custom components. They all have one thing in common though - their fieldName which is used for checking various stuff such as validation, class binding etc.
Example:
<div class="field">
<div class="label">Product name</div>
<input :value="productName" #input="valueChanged" :class="{ changed: isChanged('productName') }" data-field="productName">
</div>
As you can see, productName is repeated 3 times in just a single line. I use it in dataset so valueChanged method (a global mixin) knows that field name has changed, then in the class binding to check if value has changed to style it properly, and next for the value binding itself.
It grows bigger and bigger as I want to add for example another class binding like error: hasErrors('productName')
Is there any way to define the field name once and re-use it in other bindings? It would still require some repetition, but at least changing the field name in the future would be just one change instead of 4-5. Something like this:
<input :fName="productName" :value="fName" #input="valueChanged" :class="{changed: isChanged(fName), error: hasErrors(fName)" :data-field="fName">
I know that wrapping it in some custom component would probably be one way, but that would require a lot of different conditions to render things correctly as I'm using various field types with different structures. And I would need to re-write half of my app.
Here are two potential solutions:
Use v-bind and generate an object with all of the attributes you need
<input v-bind="getAttrs('productName')" #input="valueChanged" />
...
methods: {
getAttrs(fieldName) {
return {
value: this[fieldName],
class: {
changed: this.isChanged(fieldName),
error: this.hasErrors(fieldName)
},
'data-field': fieldName
}
}
}
Store all the fields in a variable and loop through them:
<div
v-for="field in fields"
class="field"
>
<div class="label">
Product name
// Or e.g. {{ field.label }} if you have an array of field objects
</div>
<input
:value="getValue(field)"
:class="{ changed: isChanged(field), error: hasErrors(field) }"
// Still need to use v-bind to generate the data attribute on the fly
v-bind="getDataAttr(field)"
#input="valueChanged"
/>
</div>
...
data() {
return {
fields: [
'fieldName1',
'fieldName2',
'fieldName3'
],
}
},
methods: {
getValue(fieldName) {
return this[fieldName];
},
getDataAttr(fieldName) {
return {
'data-field': fieldName
}
}
}
I believe that having custom component handling input fields is the smartest way. I don't think, there would be that much conditions and even if there is some workaround, you will still need to rewrite half of your app. You may use input type as prop, so you can use component for different field types and if there are much different structures, you can use slots, to add some custom structure.

Validation form with optional fields react-hook-forms

I'm trying to handle a form composed by two parts, one fixed and the other one that gets displayed by a switch.
To handle the forms I'm using react-hook-form.
I defined a validation scheme in the file validation.ts inside the constants folder.
About the optional part I defined a sub-object but it doesn't work and it gives a compile time error.
Because of this I opted for the solution you'll find in the link at the bottom of the page
Although I defined the optional input fields inside the validation file, they don't get recognized when I press the submit button.
How can I fix this problem?
At this link you can find a working example of the problem.
The main problem is with your components/Form component, it has this line
// components/Form.tsx
return child.props.name
? .... : child;
What you have done here is ignoring all child components without the name prop,
where as when rendering the component what you did was rendered them inside <></>, use below alternative instead.
// in App.tsx
{isEnabled ? <Input name="trailerPlate" placeholder="Targa rimorchio" /> : <></>}
{isEnabled ? <Input name="trailerEnrolment" placeholder="Immatricolazione rimorchio" /> : <></>}
Still the validation won't because you need to register the components and your current useEffect code doesn't account for change in number of input fields.
Use below code instead
React.useEffect(() => {
....
....
}, [
register,
Array.isArray(children) ?
children.filter(child => child.props.name).length : 0
]
);
We are using the count of child components with name prop as a trigger for useEffect.
And finally you also have to unregister the fields when you toggle the switch,
below is a sample code, feel free to change it according to your preference.
const { handleSubmit, register, setValue, errors, unregister, clearErrors } = useForm<VehicleForm>();
const toggleSwitch = () => {
if (isEnabled) {
unregister('trailerEnrolment');
unregister('trailerPlate');
}
clearErrors();
setIsEnabled(prev => !prev)
};
Feel free to upvote if I was helpful.

Get value of input controls which were created dynamically

In my React TypeScript project, I am dynamically creating multiple input controls like TextField, DatePicker, Checkbox, ComboBox etc... in a form like UI. On click on Submit, I want to get the value of each of the input controls. What's an effective way to do this? I can have an onChange event for each of the controls, but I was wondering if there is a better way to do this.
That's not a bad way to do it. You should probably be storing the values in the component's local state (i.e. setState) or a Redux/Mobx/whatever store if you have that.
That being said, if you have many controls and don't want to adds an individual handler for each one, Fabric components have mostly similar APIs for their onChange handlers, so you can have a single handler, and add an identifier to the element so that it's easier to store.
Something like:
class MyForm extends React.Component<MyFormProps, MyFormState> {
constructor(props: MyFormProps) {
super(props);
this.state = {
formFields: {...defaultFormValues} // you can imagine having an object of default values
};
}
private _onChangeHandler(ev: React.FormEvent<HTMLElement | HTMLInputElement>, newValue: any) => {
const formFields = {...this.state.formFields};
formFields[ev.target.dataset.id] = newValue;
this.setState({formFields});
}
public render() {
return (
<form>
<TextField data-id="formelem1" onChange={this._onChangeHandler} />
<TextField data-id="formelem2" onChange={this._onChangeHandler} />
<CheckBox data-id="checkbox1" onChange={this._onChangeHandler} />
</form>
);
}
}
Note that the one exception to this is the DatePicker because its onChange handler (onSelectDate) receives the new selected date as a single parameter, but you can easily add an if clause to handle this case.

VueJS - Vee-Validate: Custom rule to call method/function to evaluate a result?

Sorry, I'm still very new to VueJS and it's framework.
I'm trying to use vee-validate and a custom rule to check the value of an input field against an Axios GET response to an API backend. Essentially, if you input an invalid ID, it will throw up an error until you get it right (ie: assigning a ticket to a valid employee, you must enter a valid employee ID).
My template code looks like this right now:
<b-col cols="4">
<b-input-group>
<b-input-group-text style="width:150px;" slot="prepend">Key Owner</b-input-group-text>
<input
class="form-control"
v-model="selected_owner_id"
type="text"
id="ownerId"
name="ownerId"
data-vv-delay="800"
#change="validateCorpId"
v-validate="'ownerId|required'"
v-bind:class="{'form-control': true, 'error': errors.has('ownerId') }"
>
</b-input-group>
<span v-show="errors.has('ownerId')" class="text-danger">{{ errors.first('ownerId') }}</span>
</b-col>
(I am messing around with how to do this, hence the #change to the function that actually does the Axios API call)
Here is the Validate extend rule I have made in the same *.vue file as above:
<script>
import { Validator } from "vee-validate";
import VeeValidate from 'vee-validate'
Validator.extend('ownerId', {
// Custom validation message
getMessage: (field) => `The ${field} is not a valid ID.`,
// Custom validation rule
validate: (value) => new Promise(resolve => {
resolve({
valid: value && (this.validateCorpId(value))
});
})
});
...etc...
The validateCorpId(value) function is later on in the methods: {} block
I have been trying to go through the Vee-Validate docs on how to properly create a custom validation rule but as a newbie, a lot of things still seem missing like, where should that Validate.extend actually sit? Where I put it up top in the 'script' area or inside either the 'create' or 'mount' functions?
I did try that and it gets called right away which isnt what I want -- i only want it to happen when you the user enter or change the data in the field that it calls the function which returns a true|false value.
I also added a field called is_selected_owner_id_valid into the 'data' return block and had my Axios call set that true|false depending on the result but, as it's a boolean, the Validate rule read it immediately and it evaluated incorrectly.
I'm happy to keep working it our for myself, but does anyone have a fully working Validate custom rule I could see and reverse-engineer ? Really hard to find an example of what I am trying to do, involving an API call as part of the rule.
I managed to get my issue resolved by initially copying this similar posted answer here:
Vee-validate (VueJS) - evaluating a condition asynchronously
While it works, I'm not convinced about having the actual APi call in the custom rule. However, thanks to the suggestion by Walter Cejas, I'm going to retro fit my solution into that example provided : https://baianat.github.io/vee-validate/examples/async-backend-validation.html
(I thought I had gone through all the vee-validate examples... i was wrong!)

In vue.js is it possible to notify "observers" to refetch the value from the observed data, without changing the value of the observed data

Suppose that I have an input element bound like this:
<input :value="somedata">
The user types something in the input, and since I am not using v-model or altering somedata through a handler, the value of the element is now different from somedata. This is what I want, but I would also like to have the following capability:
Without changing the value of somedata I would like to be able to notify the element so that it sets its value equal to somedata again. Something like knockout's notifySubscribers() or valueHasMutated()
Is that possible in vue.js?
UPDATE: A clear illustration of the issue here: https://jsfiddle.net/gtezer5c/3/
It's a little difficult interpreting what exactly the requirements and acceptance criteria might be to suit your needs, and I thought Bill's solution was what you were after, but after all the updates and clarifications, I think I understand a little more what you're trying to accomplish: in short, I think you need a generic way to have an input that can hold a value but that can be independently reverted to some other value.
Please have a look at this CodePen. I believe it's providing what you're trying to do. It allows you to create an input element as a revertable component, which can optionally be passed a default value. Any changes to that input are maintained by the component with its value data property. It will not be observing/pulling in any lastKnownGood type of value because any reversion will be pushed into the component from outside.
Externally to the revertable component, you can $emit a revert event with a new value that will cause either all revertable components or a single revertable component (with a matching ID) to be reverted.
I feel like it's mostly a clean solution (assuming I'm understanding the requirements correctly), except that in VueJS 2 we have to use a standalone, shared Vue object to pass the events when there is no parent-child relationship. So you'll see:
const revertBus = new Vue()
defined in global scope in the demo. And the revertable component will use it to receive incoming messages like so:
revertBus.$on('revert', (value, id) => { ... }
and the controlling Vue object that is triggering the messages will use it like this:
revertBus.$emit('revert', this.reversionValue, targetId)
You can also emit the event with a null value to cause the revertable component to reset its value to its initial default value:
revertBus.$emit('revert', null, targetId)
Again, it's a mostly clean solution, and though it might not fit perfectly inline with what you're trying to accomplish, I'm hoping it might at least help you get closer.
I'm not sure I'm following properly but I'll give it a shot.
What I think you want is to only update some values when their "temporary" values meet some type of condition. Here's how I was thinking of it.
<div id="app">
<input v-model="tempValues.one">
<input v-model="tempValues.two">
<input v-model="tempValues.three">
<pre>{{ values }}</pre>
<pre>{{ tempValues }}</pre>
</div>
Then, in my component, I watch tempValues and only update values when a condition is met.
new Vue({
el: '#app',
data: {
values: {
one: '',
two: '',
three: '',
},
tempValues: {},
},
created () {
// Create the tempValues based on the real values...
this.tempValues = Object.assign({}, this.values)
},
methods: {
updateValues (tempValues) {
// Only updating the values if all the tempValues are longer than 3 characters...
var noneEmpty = Object.values(tempValues).every((value) => value.length > 3)
if (noneEmpty) {
this.values = Object.assign({}, tempValues)
}
},
},
watch: {
// Watch tempValues deeply...
tempValues: {
handler (tempValues) {
this.updateValues(tempValues)
},
deep: true,
},
},
})
Here's a quick demo: https://jsfiddle.net/crswll/ja50tenf/
yourvar.__ob__.dep.notify()
works on objects and arrays
Yes, You should be able to do this with help of v-on:input. You can call a function on input and put your logic of checking and updating in this function:
<input :value="somedata" v-on:input="yourMethod">
In fact if you look at the documentation, <input v-model="something"> is syntactic sugar on:
<input v-bind:value="something" v-on:input="something = $event.target.value">
so instead of assigning variable something to value inputted, you can put your logic in that place.