Yup dynamic object validation - reactjs

I am sending a value to yupa via formik. As a result of this value, what can I do to make the necessary checks and return an error message in this style? Since the object has unlimited length, I decided to keep it validated as an object in order not to cause any slowness. I'm solving it with array but the yup part is getting slow.
The body I send:
{
user_id_1: {
name: 'test name', // required
surname: 'test surname', // required
age: 10, // optional
},
user_id_2: {
name: 'test name 2',
surname: 'test surname 2',
},
user_id_3: {
name: 'test name 2',
surname: '',
},
}
The expected error result:
user_id_3: {
surname: 'surname is required',
},

I wrote and tested a code suitable for my question. I want to share the solution with you. The critical point here was that I produced my own method and a custom error message with addMethod. You can check how I did it in the related code example. The inside of the test could be solved with the yup scheme, but I solved it at a simple level with if else. The else part in the test is also important, if I do not delete the object value, it remains in the cache.
code example: codesandbox

Related

Graphql React Form not mutating

I'm not sure if I'm wording this correctly, please bear with me.
How to relate a form field inside a type to a parent type?
I have a form with fields to fill-in namely:
name: '',
description: '',
subtotal: '',
tax: ''
and these inputs in the form will be published in a list/table.
In my schema, I have this for the form
export type CartItemInput = {
description?: string
name: string
tax?: number
subtotal?: number
}
export type CartItem = {
id: string
name: string
price: number
tax: number
subTotal: number
description: string
}
export type Cart = {
id: string
customerId: string
total: number
subTotal: number
items: CartItem[]
}
I receive an error saying. "The variables input contain a field name 'subtotal' is not defined for input object type 'CartItemInput'"
Is there anyway to relate 'subtotal' field to the the 'subTotal' in CartItem?
I have tried all of these in my CartItemInput but to no avail.
'subtotal?: Cart'
'subtotal?: CartItem'
'subtotal?: number'
Would really appreciate your inputs here. Thanks

Object or String type conditional with yup validation

I am using yup in combination with Formik in my React application to validate a TypeScript model/interface.
We previously had a yup.object for the follwing schema:
export const InputSchema = yup.object({
id: yup.string(),
name: yup.string().required().min(MIN_DESC_LENGTH).max(_MAX_NAME_LENGTH),
description: yup.string().required().matches(/(?!^\d+$)^[\s\S]+$/, 'Please enter a valid description').min(MIN_DESC_LENGTH).max(_MAX_DESC_LENGTH),
contact: yup.string().required()
});
As we changed our interface now, name and description fields can either be a string or an object.
I still want to validate my form with yup, so I tried to use name: yup.mixed() and description: yup.mixed() but now I get obviously problems with the min.() and .matches() functions.
Is it possible to have a string OR object condition? So if it is a yup.string() then, min/max will be used, otherwise just yup.object().
I looked for a way to do this in a simple way but couldn't find any, but the solution they give is to use yup.lazy.
For your case it would be
Yup.object({
...
name: Yup.lazy(value => {
switch (typeof value) {
case 'object':
return Yup.object(); // schema for object
case 'string':
return Yup.string().min(MIN_DESC_LENGTH).max(_MAX_NAME_LENGTH); // schema for string
default:
return Yup.mixed(); // here you can decide what is the default
}
}),
...
})
Another way you can do is like this gist.
It creates a custom method with .addMethod that receives schemas and validates all. Pretty good approach

Formik & Yup - How to make a schema for an array and string

I am using Formik to create a form of a book library where each item in the list would look like:
author: {
name: 'string',
titles: ['string']
}
I am having trouble trying to make a schema with Yup to validate those fields. I have:
schema = Yup.object().shape({
author: Yup.object().shape({
name: Yup.string().min().max().required('...')
}),
author: Yup.array().of(
Yup.object().shape({
titles: Yup.string().min().max().required('...')
})
)
});
The initial values are also:
{
author: {name: ''},
author: [{ titles: '' }]
}
My validation works for the array but not the name. I am assuming the issue is that I cannot have the same name for both of the elements in the object, however I do not know how to combine the two fields in both the initial values as well as the schema. I looked through Yup API and I noticed they had mixed() but I didn't understand how to implement with what I need. Is this way possible or would having validation for both of these possible?
Author is not an array, it's an object which contains an array and a string. Also, you're defining it twice:
schema = Yup.object().shape({
author: Yup.object().shape({
name: Yup.string().min().max().required('...'),
titles: Yup.arrayOf(Yup.string())
}),
});
Confused about why you have 2 author keys. The schema above corresponds to:
{
author: {
name: "hehyryg",
titles: ["title", "title2"]
}
}

Enzyme+React expect component string - failed to parse selector

So I have this test case which I need to solve. Background is updating dev environment to newer version and after that a lot of our tests broke.
Here I have a weird case which results in:
"Failed to parse selector: Label price detail 1"
This is how the test snippet looks like, I hope I've added all that is necessary.
it('Should render with price and one addon', () => {
data.addonHeaderName = 'addonHeaderName';
data.addons.push(
{
price: {
label: 'Addon text 1',
value: 50.33,
unit: 'dollars',
vat: 'excl'
},
discount: {
label: 'Addon text 2',
value: 11.43,
unit: 'dollars',
vat: 'excl'
},
future: false,
addonIcon: 'icon',
ecoText: 'Addon eco text',
linkUrl: 'http://testaddonlink.com'
}
);
data.contract.prices.push(
{
id: 'price',
label: 'Label price detail 1',
unit: 'dollars',
value: 4.03
},
{
id: 'Label price detail 2',
label: 'Discount',
unit: 'dollars',
value: -3.00
}
);
const component = shallow(
<MyContract
data={data}
andSomeOtherStuff={otherStuff}
/>
);
expect(component).toMatchSnapshot();
expect(component.find('Label price detail 1')).toBeTruthy();
expect(component.find('Label price detail 2')).toBeTruthy();
expect(component.find('Addon text 1')).toBeTruthy();
expect(component.find('Addon text 2')).toBeTruthy();
expect(component.find('Addon eco text')).toBeTruthy();
If I comment out the first expectation, it hits the next one, and then the other one etc etc.
Earlier we ran Enzyme 2.9.1 together with enzyme-adapter-react-15 (and of course React 15) but since we've upgraded to React 16 we also need to update a few other dependencies such as this one. And then shit hit the fan.
Now we're on Enzyme 3.8.0, enzyme-adapter-react-16.3 and React 16.3.x.
I've been fiddling around with trying to get it as a string instead but no bueno. Any ideas on what I'm missing here?
Enzyme find works with css selectors so if you would like to search on the label there you should probably use something like:
component.find('[label="Label price detail 1"]')
Additionally I believe this will always be truthy no matter if it's found or not. (Not sure on that one though).
I usually use .toHaveLength(1) to check if it gets rendered!
I had this before, this looks like a typing error. Confirm the strings.

How do I preselect a vue-multiselect option when options is an array of objects?

I want to pre-select a particular value in a select drop-down generated by vue-multiselect.
I can get this to work fine if I have a simple array of strings like the following:
['Test 1', 'Test 2', 'Test 3']
However, when I use an array of objects, I can't get this to work. For example, if I have the following:
<v-multiselect :options="[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]"
label="name"
track-by="id"
v-model="test">
</v-multiselect>
No matter what I set the test data property that v-model is connected to, it won't preselect the value. I've tried 1, 2, 3, '1', '2' and '3' for test when track-by is id and 'Test 1', etc. when track-by is name but nothing seems to work.
What am I doing wrong here? I looked at the docs at https://vue-multiselect.js.org/#sub-single-select-object, but they don't seem to provide an example when you want to preset a value for an array of objects for the options. Googling has also not returned what I'm looking for.
On a related topic, once I get this working, what would I have to change to select multiple values for when I set the component to multiple? Thank you.
track-by usage
The docs indicate that track-by is "Used to compare objects. Only use if options are objects."
That is, it specifies the object key to use when comparing the object values in options. The docs should actually state that track-by is required when the options are objects because <vue-multiselect> uses track-by to determine which options in the dropdown are selected and to properly remove a selected option from a multiselect.
Without track-by, you'd see two buggy behaviors for object-options: (1) the user would be able to re-select already selected options, and (2) attempting to remove selected options would instead cause all options to be re-inserted.
Setting initial values
<vue-multiselect> doesn't support automatically translating a value array, but you could easily do that from the parent component.
Create a local data property to specify track-by and initial multiselect values (e.g., named trackBy and initialValues, respectively):
export default {
data() {
return {
//...
trackBy: 'id',
initialValues: [2, 5],
}
}
}
Bind <vue-multiselect>.track-by to this.trackBy and <vue-multiselect>.v-model to this.value:
<vue-multiselect :track-by="trackBy" v-model="value">
Create a watcher on this.initialValues that maps those values into an object array based on this.trackBy, setting this.value to the result:
export default {
watch: {
initialValues: {
immediate: true,
handler(values) {
this.value = this.options.filter(x => values.includes(x[this.trackBy]));
}
}
}
}
Vue.component('v-multiselect', window.VueMultiselect.default);
new Vue({
el: '#app',
data () {
return {
trackBy: 'id',
initialValues: [5,2],
value: null,
options: [
{ id: 1, name: 'Vue.js', language: 'JavaScript' },
{ id: 2, name: 'Rails', language: 'Ruby' },
{ id: 3, name: 'Sinatra', language: 'Ruby' },
{ id: 4, name: 'Laravel', language: 'PHP' },
{ id: 5, name: 'Phoenix', language: 'Elixir' }
]
}
},
watch: {
initialValues: {
immediate: true,
handler(values) {
this.value = this.options.filter(x => values.includes(x[this.trackBy]));
}
}
}
})
<script src="https://unpkg.com/vue#2.6.6/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<v-multiselect :track-by="trackBy"
:options="options"
v-model="value"
label="name"
multiple>
</v-multiselect>
<pre>{{ value }}</pre>
</div>
Looks like a bug. The workaround is to use an actual reference to the object
Vue.component('v-multiselect', window.VueMultiselect.default);
let testOptions=[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]
new Vue({
el: '#app',
data: function () {
return {
test: testOptions[1], // <- use an object ref here!
testOptions
};
}
});
The easiest way I found out is sending the whole object from BE, so it gets pre-selected. If you send the same object from BE will get pre-selected. But I don't know if your options are hard coded on FE or they are coming from a database or something. I had the same issue but my values were coming from my database, so it was easy to reproduce the object
In your question just :object="true" is missing actually they didn't know that it is of type string or object when we pass this it knows yes it is object and i need label="name" from v-model="test" and picks it and shows it as a preselected

Resources