How to access values inside validationSchema in formik - formik

values are accessible only if I use validation but not inside validationschema
<Formik
initialValues={initialValues}
validationSchema={validationSchema(values)}
onSubmit={actions.handleSubmit}
>
<Form>
if I use useFormikContext(); values are not accessible inside validationSchema because initialization happens after Formik.
how to solve this problem.

Use validate instead of validationSchema
Pass your Form data as 4th argument in validateYupSchema which represents context and can be accessed later in schema.
Pass your schema as 2nd argument in validateYupSchema.
<Formik
validate={(values) => {
try {
validateYupSchema(values, validationSchema, true, values);
} catch (err) {
return yupToFormErrors(err); //for rendering validation errors
}
return {};
}}
onSubmit={} />
Now we can access any form value using this.options.context inside test function in schema

If you need to validate one formik value conditionally based on another formik values,
you can achieve using when() method chaining inside of validationSchema like so:
const validationSchema = Yup.object().shape({
is_female: Yup.boolean('Select The Pricing'),
female_price: Yup.string().when('is_female', {
is: isFemale => isFemale === true,
then: Yup.string().required('Female Price is required')
}),
male_price: Yup.string().when('is_female', {
is: isFemale => isFemale === false,
then: Yup.string().required('Male Price is required')
})
});
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={actions.handleSubmit}
/>
Hope this helps.

Related

using Fomik hook `useField` and `react-data-table-component` causes infinite loop

I am using react-data-table-component inside formik form to update new values. But the problem is whenever the MyTable component is re-rendered, the selectableRowSelected() callback is called, which triggers onSelectedRowsChange event in which I use helpers.setValue() to set value, which then makes MyTable component renders again. This whole process causes infinite loop, and I still don't have a solution for this.
function MyTable() {
const [data, setData] = useState([]);
const [field, meta, helpers] = useField({ name: "use" });
useEffect(() => {
fetch("https://reqres.in/api/users?page=1&per_page=3")
.then((res) => res.json())
.then((res) => setData(res.data));
}, []);
const handleChange = React.useCallback(({ selectedRows }) => {
let selectedIds = selectedRows.map(function (row) {
return parseInt(row.id);
});
selectedIds.sort();
console.log("🚀 ~ selectedIds", selectedIds);
// helpers.setValue(selectedIds, true); --> uncomment this will cause infinite loop.
}, []);
return (
<DataTable
title="User List"
columns={columns}
data={data}
selectableRows
selectableRowsHighlight
onSelectedRowsChange={handleChange}
selectableRowSelected={(row) => {
return meta.value.includes(parseInt(row.id));
}}
/>
);
}
CodeSandbox: https://codesandbox.io/s/goofy-tu-l1pxvb?file=/src/MyTable.jsx:375-1249
I think I've figured it out myself. But I will post it here in case anyone may encounter the same.
There is no way to stop this problem as the way RDT works is whenever selectableRowSelected is called, it will dispatch an action with type: SELECT_MULTIPLE_ROWS:
dispatch({
type: 'SELECT_MULTIPLE_ROWS',...
});
then in tableReducer, it returns toggleOnSelectedRowsChange as boolean value:
case 'SELECT_MULTIPLE_ROWS': {
...
return {
...state,
...,
toggleOnSelectedRowsChange,
};
which controls the trigger of onSelectedRowsChange event (as mentioned at comment in the source code):
useDidUpdateEffect(() => {
onSelectedRowsChange({ allSelected, selectedCount, selectedRows: selectedRows.slice(0) });
// onSelectedRowsChange trigger is controlled by toggleOnSelectedRowsChange state
}, [toggleOnSelectedRowsChange]);
Overall, solution for this problem is don't use formik with RDT for row selection, use another datatable lib.

How do I access a Formik context's validationSchema?

tl;dr The result of useFormikContext() doesn't include its validationSchema. Based on its TypeScript types, it exists on the object as an optional property, but for some reason, I don't know how to make it appear.
I have a generic component that takes in a fieldName prop. I think use useField(fieldName) to retrieve the data. Now I want to check if there's a validationSchema, and if there is, do something with it (e.g. determine if the field has a max() test).
const MyComp = ({ fieldName, maxLength, ...props }) => {
// for some reason, validationSchema is always undefined
const { validationSchema } = useFormikContext();
if (maxLength == null && validationSchema) {
// never gets here because validationSchema is always undefined
maxLength = Yup.reach(validationSchema, fieldName)
?.tests.find(t => t.OPTIONS.name === 'max')
?.OPTIONS.params.max;
}
return (
<div>
The maxLength of <code>{fieldName}</code> length is{' '}
<code>{maxLength ?? 'undefined'}</code>.
{'\n'}
Its <code>validationSchema</code> via{' '}
<code>useFormikContext()</code> is{' '}
<b>{
validationSchema === null ? 'null' :
validationSchema ? 'definied' :
'not defined'
}</b>.
</div>
);
};
const schema = Yup.object({
withoutMaxLength: Yup.string(),
withMaxLength: Yup.string().max(10),
withMaxLengthProp: Yup.string()
});
const initialValues = {
withoutMaxLength: "",
withMaxLength: "",
withMaxLengthProp: ""
};
const App = () => (
<Formik
initialValues={initialValues}
onSumbit={() => {}}
validationSchema={schema}
>
<Form>
<MyComp fieldName="withoutMaxLength" />
<MyComp fieldName="withMaxLength" />
<MyComp fieldName="withMaxLengthProp" maxLength={10} />
</Form>
</Formik>
);
Code Sandbox
Result
As you can see above, useFormikContext().validationSchema is always undefined even though it is set as a property of <Formik />. Any idea why or what I can do about it?
It turns out this is a known bug in formik, and has been fixed in v3. My best option appears to be yarn patch, or it would be if formik weren't an external for my library.

Conditional validation of multiple fields using Yup

I have 3 input fields (name, id, and postcode), and I use formik along with Yup for the validation.
What I want to achieve here is, I want to match each input field with a known combination of name, id and postcode (I have a predefined default value for id, name, postcode).
And if the values entered on all the 3 input fields exactly match with the default values of name, id, and postcode, then I have to show the formik error on each of the fields(*something like default not allowed). If one of these fields is different from the default values, do not show the error on any fields.
For eg, if my default values for each fields are name="testName", id="testID", postCode="testPostCode", show validation error on each field only if all 3 input values matches with the defaultValues.
This is what I've now:
const defaultValues = {
name: 'testName',
id: 'testID',
postCode: 'testPostCode'
}
const YUP_STRING = Yup.string().ensure().trim();
const validationSchema = yup.object().shape({
name: YUP_STRING.required('required'),
id: YUP_STRING.required('required'),
postcode: YUP_STRING.required('required'),
})
I've tried several variations, but nothing worked here. Can anyone help me find a solution for this?
You can do something like <Field validate={(value)=>validate(value,values)} name="name" type="text" />
In more detail..
<Formik
initialValues={defaultValues}
onSubmit={values => alert(JSON.stringify(values)}
>
{({ errors, touched, values }) => (
<Form>
<Field validate={(value) => validate(value, values)} name="name" type="text" />
{errors.name && touched.name ? <div>{errors.name}</div> : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
And define validate function as
const validate = (value, values) => {
if(values === defaultValues){
return "Default Values not allowed"
} else return undefined
}
Or if you want to do it with validationSchema you can do something as by adding test function on each of the field, I have only done for name:
const validationSchema = yup.object().shape({
name: YUP_STRING.required('required')
.test('name', "No default please", function (item) {
const currentValues = {
name: this.parent.name,
id: this.parent.id,
postCode: this.parent.postCode
}
return !(currentValues === defaultValues)
}
),
id: YUP_STRING.required('required'),
postcode: YUP_STRING.required('required'),
})
No arrow function to be able to use this

Cypress command that return values from DOM

In my DOM i have an input and a div, and i want to get the value of both in one command.
Here is an HTML exemple
<div id="myDiv">Content of the div</div>
<input id="myInput" value="2000" />
Here is what i tried for my command
Cypress.Commands.add("getDomValues", () => {
var divValue = cy.get('#myDiv').invoke('text')
var inputValue = cy.get('#myInput').invoke('val')
return cy.wrap({
divValue:divValue,
inputValue:inputValue
});
});
If there is no cy.wrap around my returned object i get this error
Unhandled rejection CypressError: Cypress detected that you invoked
one or more cy commands in a custom command but returned a different
value.
And then in my test for now i use it like that
cy.getDomValues().then((values)=>{
console.log(values)
})
In my console inside the returned object i have something like that for both values
$Chainer {userInvocationStack: " at Context.eval (http://localhost:8888/__cypress/tests?p=cypress/support/index.js:181:24)", specWindow: Window, chainerId: "chainer4419", firstCall: false, useInitialStack: false}
Do you have any idea how i could have a result like that ?
{
divValue:"Content of the div",
inputValue:"2000"
}
You need to access the values with .then()
Cypress.Commands.add("getDomValues", () => {
cy.get('#myDiv').invoke('text').then(divValue => {
cy.get('#myInput').invoke('val').then(inputValue => {
// no need to wrap, Cypress does it for you
return {
divValue, // short form if attribute name === variable name
inputValue
}
});
The error you received was because you were returning Chainers instead of values.
You can use .as() to assign an alias for later use.
cy.get('#myDiv').invoke('text').as('divValue')
cy.get('##myInput').invoke('val').as('inputValue')
Then use these values later separately like -
cy.get('#divValue').then(divValue => {
//Do something with div value
})
cy.get('#inputValue').then(inputValue => {
//Do something with input value
})
OR, use these values later together like -
cy.get('#divValue').then(divValue => {
cy.get('#inputValue').then(inputValue => {
//Do something with div value
//Do something with input value
})
})

Mongoose conditional required validation

I'm using mongoose and trying to set a custom validation that tells the property shall be required (ie. not empty) if another property value is set to something. I'm using the code below:
thing: {
type: String,
validate: [
function validator(val) {
return this.type === 'other' && val === '';
}, '{PATH} is required'
]}
If I save a model with {"type":"other", "thing":""} it fails correctly.
If I save a model with {"type":"other", "thing": undefined} or {"type":"other", "thing": null} or {"type":"other"} the validate function is never executed, and "invalid" data is written to the DB.
As of mongoose 3.9.1, you can pass a function to the required parameter in the schema definition. That resolves this problem.
See also the conversation at mongoose: https://github.com/Automattic/mongoose/issues/941
For whatever reason, the Mongoose designers decided that custom validations should not be considered if the value for a field is null, making conditional required validations inconvenient. The easiest way I found to get around this was to use a highly unique default value that I consider to be "like null".
var LIKE_NULL = '13d2aeca-54e8-4d37-9127-6459331ed76d';
var conditionalRequire = {
validator: function (value) {
return this.type === 'other' && val === LIKE_NULL;
},
msg: 'Some message',
};
var Model = mongoose.Schema({
type: { type: String },
someField: { type: String, default: LIKE_NULL, validate: conditionalRequire },
});
// Under no condition should the "like null" value actually get persisted
Model.pre("save", function (next) {
if (this.someField == LIKE_NULL) this.someField = null;
next()
});
A complete hack, but it has worked for me so far.
Try adding this validation to the type attribute, then adjust your validation accordingly. E.g.:
function validator(val) {
val === 'other' && this.thing === '';
}
thing: {
type: String,
required: function()[{
return this.type === 'other';
}, 'YOUR CUSTOM ERROR MSG HERE']
}

Resources