Yup (with formik and react) - Can't validate array length - reactjs

kinda new to Yup and I can't figure out how to validate that an array is not empty.
I'm using react + formik + yup + material-ui
here is an example I've created:
https://codesandbox.io/s/new-fire-29onf?file=/src/App.js
I tried in the validationSchema to just use the required method:
validationSchema={Yup.object({ permissions : Yup.array().required('permission cant be empty') })}
i tried to add my functionally using the test method like this:
validationSchema={Yup.object({ permission: Yup.array().test ('notEmptyArr', 'array is empty', (value) =>{ console.log(value); return value.length > 0; }) })}
i also tried to add method to the array like this:
Yup.addMethod(Yup.array, "notEmpty", function(message) { return this.test("notEmpty", message, function(arr) { return Boolean( arr.length > 0 ); }); });
But none of that worked for me : (
if I remove the validation I see the value.permission is indeed an array, with values (if selected)
what am I doing wrong?
thanks

You can use .min():
validationSchema={Yup.object({
permissions: Yup.array().min(1)
})}

UPDATE / WARNING:
Previously only array().required() was required to make sure there was at leas 1 item in the array. For example [] would not pass the test.
⚠️ As of version 0.31.0 (2020-11-23) the behavior of array().required() changed! Now to make sure you have at least 1 item in the array you need to sure: array().required().min(1)

use array().min(1) to handle an empty array error instead of required(); if you use Formik to handle a custom message, use array().min(1, 'your message')

Related

Required doesn't work after adding test Yup

This is what i've done
subject: Yup.string().test('checkForManualText', 'Please, add hand-written subject as well',
function(inputValue) {
let newMessage = inputValue.replace(/\{{(.+?)\}}/gmi, '').replace('/\s/gmi', '').trim()
return newMessage.length !== 0
}).required()
Now the test validation works fine, but the required stopped working. Before adding test, all was good.
You don't need to use required when using .test. Returning false will show the error message. Just add null/undefined check to the string.
subject: Yup.string().test('checkForManualText', 'Please, add hand-written subject as well',
function(inputValue) {
if(!inputValue) return false;
let newMessage = inputValue.replace(/\{{(.+?)\}}/gmi, '').replace('/\s/gmi', '').trim()
return newMessage.length !== 0
})
Working Example

Is there a better way to dynamically set Values in react-hook-form on a next.ts project?

I am working on a next.ts project in which I need to dynamically set the fields of a react-hook-form form. The best way I've found to type the "name" parameter in literal string is with the as operator, as it follows:
type val = "businessName";
setValue(name as val, "Some businessName");
but I presume that there is a more elegant way to do it that maybe escapes me.
Here's the documentation about the setValue function:
https://react-hook-form.com/api/useform/setvalue
and here's a basic example of what I mean, any advice is welcome.
https://codesandbox.io/s/twilight-thunder-5ecnfu
This is actually the correct way to change your form values. But since you are using typescript you can use type definition to set the form names.
Here is an example.
Let's say you have a form with name and email. But it might be user and email. Then you can define a type like the following.
type LoginFormValues {
name: string,
email: string
} | {
user: string,
email: string
}
Then define this type with the useForm function.
const {} = useForm<LoginFormValues>()
Now, you can have dynamic form values.
There are other ways to do what you want, but this is the most elegant way.
Hope this helps.
key as any takes the error away and since I'm using the validation schema as the source, I'm not worried about it.
const data = {
"id": "somedata"
}
// Zod validation schema
const validationSchema = z.object({
id: z.string()
});
useEffect(() => {
Object.keys(validationSchema.keyof().Values).map((key) => {
if(key == 'xyz') return;
data?.[key] && setValue(key as any, data[key]);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alerts]);

Antd: How to set nested field value using setFieldsValue

So I have a nested dynamic form, and I want to check its value using getFieldsValue. But whenever I do, it doesn't return me the values.
Using ant form hook, I create form list as <Form.List name="guests">
And assign the name to form input <FormInput name={[index, "firstName"]} label='FIRST NAME' />
I am trying to set its value using form.setFieldsValue({ [index, "firstName"]: [value]) but it doesnt works. Any suggestion regarding how to set path.
Strictly need to use setFieldsValue
First get the guests array using form.getFieldValue (You can also use getFieldsValue(['guests'])?.guests).
Then you can modify and set the value like this:
let guests = form.getFieldValue('guests');
// Check If `guests` exist & guest is an array & it's not empty
if (guests && Array.isArray(guests) && guests.length) {
// Check If firstName exists at that `index`
if (guests[index]?.firstName) {
guests[index].firstName = 'New Name';
form.setFieldsValue({ names: guests });
}
}

Using Jest and Enzyme to call a function

I am using Jest and Enzyme to test a React component. I am trying to test my form validation rules when submitting a form. The tests need to cover all possible cases of this function
const handleSubmit = event => {
event.preventDefault();
const { createPassword, confirmPassword } = event.target.elements;
if (createPassword.value !== confirmPassword.value) {
setPassValidationError("*Passwords must match!");
} else if (createPassword.value.length < 8) {
setPassValidationError("*Passwords must be at least 8 characters long!");
} else if (createPassword.value.search(/[A-Z]/) < 0) {
setPassValidationError(
"*Passwords must contain at least one uppercase letter!"
);
} else if (createPassword.value.search(/[!##$%^&*]/) < 0) {
setPassValidationError(
"*Passwords must contain at least one special character!"
);
} else {
props.updatePassword({
uid: props.uid,
token: props.token,
new_password: createPassword.value
});
event.target.reset();
}
};
This function is pretty straight forward createPassword and confirmPassword are the values for 2 different input fields. When the form is submitted and this function gets called I am testing the password on different criteria. If the password is not strong enough, the setPassValidationError hook is called and updates a state variable.
I am currently trying to test the function with a password shorter than 8 characters.
it("passwords must be 8 char long", () => {
const wrapper = mount(<NoAuthPasswordChange />);
const passInput = wrapper.find("#create-password");
const confirmPass = wrapper.find("#confirm-password");
passInput.simulate("change", { target: { value: "QQQQQQ" } });
confirmPass.simulate("change", { target: { value: "QQQQQQ" } });
const submitButton = wrapper.find("#submit-button");
submitButton.simulate("click");
expect(wrapper.find("#password-validation-error").text()).toContain(
"*Passwords must be at least 8 characters long!"
);
});
Jest is telling me that #password-validation-error cannot be found (expected 1 node found 0). Now this particular part of the code is only rendered if passValidationError has data.
{passValidationError ? (
<h2
className={styles.passwordError}
id="password-validation-error"
>
{passValidationError}
</h2>
) : null}
I'm not sure if I just have a simple bug in my test or if something more advanced needs to be done in order to use Jest and have a function call a hook update.
Edit: I am beginning to wonder if the event parameter required by the handleSubmit function is problematic due to the function being called by Jest.
This can be cause by not updating the component itself. Have you tried to force your wrapper to be re-rendered:
https://airbnb.io/enzyme/docs/api/ShallowWrapper/update.html
https://airbnb.io/enzyme/docs/api/ReactWrapper/update.html
I have found a solution to my issue. The test needs to call the form submission on the form element itself and not via a button click. So instead of submitButton.simulate("click") I need to simulate a submit on my form element. I am unsure why this solution works and the posted code does not.

I need to add predefined - '#domain' when user type on the form?

My form only allows user to type their email without #domainname. for e.g. tomdickharyy#email.com the bit from #email.com is predefined. My handle change function will concatenate "#email.com" at the end. Once the form is submitted on my state i set {email : ''} and the form is empty as expected.
The problem is when I type a new email the form will concatenate #email.com for every letter typed. I don't know how to fix this problem, any help would be great.
handleChange({ target: {name, value} }){
const domainEmail = '#email.com';
name === "email" ? this.setState({ [name]: value + domainEmail}) : this.setState({ [name]: value});
} ```
You have two options:
1) Remove domainEmail and append it again on each change. Some very rude code:
handleChange({ target: {name, value} }) {
const domainEmail = '#email.com';
name === 'email'
? this.setState({ [name]: value.replace(domainEmail, '') + domainEmail })
: this.setState({ [name]: value });
}
2) Move out domainEmail to be purely representational, next to the input field. This is how many UI frameworks do it, see for example Bootstrap: https://getbootstrap.com/docs/4.3/components/input-group/
Then when you submit, remember to append the domain. I recommend this approach, it makes it much less fragile, removing many edge cases. It isalso very clear for the user that they should not type their email.

Resources