I have a react-final-form form, which has 2 inputs. Let's call them from and to.
What I want to do is that whenever input from changes, I set a value for input to based on input from's value.
I do that in validate function because i don't know where else i can do that. And it causes re-rendering the component in a loop.
Since I change the value of to in validate, it causes validate function to run again and again and again. How can I avoid that?
The actual code is much more complex than this but this is where i run into problems.
Thanks
const validate = (v) => {
const calculateFrom = calculate(v.from);
window.setTo(calculateFrom);
};
<Form
onSubmit={onSubmit}
validate={validate}
mutators={{
setTo: (a, s, u) => {
u.changeValue(s, 'to', () => a[0]);
},
setMax: (a, s, u) => {
u.changeValue(s, 'from', () => getMaxBalance(selectedAsset1));
},
}}
subscription={{ submitting: true, pristine: true }}
render={({
form,
pristine,
invalid,
handleSubmit,
}) => {
if (!window.setTo) {
window.setTo = form.mutators.setTo;
}
return (
<form onSubmit={handleSubmit}>
<Field name="from">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
<Field name="to">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
/>
First of all you could use an existing decorator
https://codesandbox.io/s/oq52p6v96y
I'm not really sure why it is re-rendering infinitely. Might have something to do with reusing the function setTo that you put on the window.
If you don't want to add that mutator library I'd try the following solutions.
use parse prop on the the Field where you can get the value and compare it with the other value that you need and return exactly what is should be check attached example parse prop
Final form allows to nest Fields inside custom components so you could just handle everything there by using useForm hook
Related
I have a component that implements a simle Formik form. The component also has listeners for an event generated by clicking on a LeafletJS map. I would like to be able to update the lat and lng fields whenever the event handler is called so that they can optionally be filled by clicking on the map instead of entering values directly.
import React, { useEffect } from 'react';
import { Formik, Form, Field } from 'formik';
const FormComponent = () => {
const onMapClick = e => {
console.log(e.detail.lat, e.detail.lng);
};
useEffect(() => {
document.addEventListener('map:click', onMapClick);
return () => {
document.removeEventListener('map:click', onMapClick);
};
}, []);
return (
<Formik
initialValues={{ username: 'guest', lat: 50.447243, lng: 30.524933 }}
onSubmit={values => { console.log(values); }}
>
{() => (
<Form id="test">
<Field type="text" name="username" />
<Field type="text" name="lat" />
<Field type="text" name="lng" />
<button type="submit">
Submit
</button>
</Form>
)}
</Formik>
);
};
export default FormComponent;
I have come across the Formik setFieldValue function but this only seems to be accessible from within the form. Likewise I cannot use UseEffect within the form function.
I have had some success by changing the intial values and using enableReinitialize but this resets other fields unless I keep track of all fields and update the initial values on each change.
What would be the recommended way of acheiving this? I should add that the code is a trimmed down example. The real form has way more fields.
This is how I would make this.
Imagine that you have component with form, component with map and some parent component that contain both of them.
I would use some useState in parent component that will handle change of selected lat and lng and provide this data to my form.
Then in form I'll use useEffect, so whenever those data is changed it will setFieldValue in form.
All this stuff will look like that (abstract example):
Parent component
const [coords, setCoords] = useState(null)
return (
<>
<Form coords={coords} />
<Map onChange={setCoords} />
</>
)
Form
useEffect(() => {
if(coords !== null) {
setFieldValue("lat", coords.lat)
setFieldValue("lng", coords.lng)
}
}, [coords])
This should work because setFieldValue can set values for fields in every moment of time, even after initialization. I'm not sure what problems have you had with useEffect and setFieldValue, so if my answer didn't helped you, provide some more code examples
I have nested form, one is on the parent and one is inside of a table component (using PrimeReact), and i have problem getting the value from the input inside of the table.
I got the data for the table from API, pass to every columns, put it inside on an <input> and it shows the value correctly, but when i try submit it, the value is undefined (for every <input> that has value from data) and empty string (for every input that i filled independently), but the data structure is correct, like this:
{
"parent1": "i got the value",
"parent2": "i got the value",
"data": [{
"tableChild1": [undefined, undefined...],
"tableChild2": ['','',..],
}]
}
and my code look like this:
Parent.js
const methods = useForm({
mode: 'onChange',
})
const {
control,
register,
handleSubmit,
setError,
formState: { isSubmitting },
} = methods
<FormProvider {...methods}>
<form className="" onSubmit={onSubmit}>
<div>
// input parent
</div>
<div>
<ChildTable
control={control}
loading={loading}
data={formPreload}
{...createFormTableOptions()}
/>
</div>
<div className="full-grid ">
// submit button
</div>
</form>
</FormProvider>
ChildTable.js
const { register } = useFormContext()
const { fields, append, remove } = useFieldArray({
control,
name: 'data',
})
const elColumns = columns.map((col, i) => {
return (
<Column
key={i}
field={col.field}
body={(row, props) => {
return (
<>
<InputText
{...register(
`data.${col.name}[${props.rowIndex}]` ?? ''
)}
disabled={col.isReadOnly}
type="text"
id={col.name}
name={`data[${i}][${col.name}]`}
value={row[col.field ?? '']}
/>
{}
</>
)
}}
className={col.className}
header={col.header}
style={col.style}
/>
)
})
I have followed the instruction on the documention using useFormContext, useFieldArray, and useForm but i stil have no clue what's wrong. Thank you for your help.
React Hook Forms detect that I change the value of text input when I type something (onChange). But can it also detect the change if I enter data to the input by value={value}?
const validator = register(name);
I need something like
onValueSet={(e) => {
validator.onChange(e);
}}
I mean if I just set data by value={value} I get an error that this input is required. I don't want this error, because data was set by value={value}.
Here is my input:
<StyledInput
name={name}
maxLength={100}
value={value}
onChange={(e) => {
validator.onChange(e);
}}
/>
and my validation schema:
const validationSchema = Yup.object().shape({
first_name: Yup.string()
.required("required"),
});
You have to use reset here and call it when you received your initial form data. I assume you're doing an api call and want to set the result to the form. You can do so with useEffect, watching when you're data has resolved and then reset the form with the actual values. This way you don't have to set the value via the value prop and let RHF manage the state of your inputs. You also don't need to set name prop, as RHF's register call returns this prop as well.
const Component = (props) => {
const { result } = useApiCall();
const { register, reset } = useForm();
useEffect(() => {
reset(result)
}, [result]);
return (
...
<StyledInput
{...register('first_name')}
maxLength={100}
/>
...
)
}
Here is a little CodeSandbox demonstrating your use case:
You can define an onfocus or onkeyup event and call the validation function there instead of onChange event.
<StyledInput
name={name}
maxLength={100}
value={value}
onfocus={(e) => {
validator.onChange(e);
}}
/>
Instead of triggering the validator when input changes, you can instead call your validator through the onBlur event. And I am not sure how you are using react-hook-form .. but the useForm hook has a config mode (onChange | onBlur | onSubmit | onTouched | all = 'onSubmit') on when to trigger the validation:
I have this validation schema for a form made using withFormik() used in my React application, Here validateJql() is my custom validation function for yup
validationSchema: Yup.object().shape({
rework: Yup.string().required("Rework query is required").validateJql(),
originalEstimate: Yup.string().required("Original Estimate query is required").validateJql()
})
and my form Component is like this:
const addSomeForm = (props) => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleSubmit,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<div>
<label htmlFor="name" className="col-form-label"><b>Rework Query:</b></label>
<textarea id="query.rework" rows="5" type="text" className="form-control" placeholder="Enter JQL with aggregate Function" value={values.query.rework} onChange={handleChange} required />
{errors.query && errors.query.rework && touched.query && <span className="alert label"> <strong>{errors.query.rework}</strong></span>}
</div>
</div>
<div className="form-group">
<div>
<label htmlFor="name" className="col-form-label"><b>Original Estimate:</b></label>
<textarea id="query.originalEstimate" rows="5" type="text" className="form-control" placeholder="Enter JQL with aggregate Function" value={values.query.originalEstimate} onChange={handleChange} required />
{errors.query && errors.query.originalEstimate && touched.query && <span className="alert label"> <strong>{errors.query.originalEstimate}</strong></span>}
</div>
</div>
</form>
)
Now, what I want to do is not to run validation on form submit if the field rework and originalEstimate is not touched and also not empty. How can I achieve this with withFormik HOC or Yup? I have partially been through Yup docs and Formik docs but could not find something to fit with my problem.
This is the case after submitting the form once and editing after that for minor tweaks in some of those multiple fields. if there are multiple fields and only some are edited, I don't want to run validation for all the fields existed.
Thank you in advance.
This is the default desired behavior as stated in formik docs but i think you can do the following:
Instead of using validationSchema, use validate function.
Validate function will work the same way your validationSchema works. You just need to use Yup programmatically from a function with mixed.validate
So you can have the full control of all the props in your form. You could also use the getFieldMeta to get the touched and value of the field and use that in your validation. Or get those props from touched object in form with getIn
Something like:
// Some util functions
function mapYupErrorsToFormikErrors(err: { inner: any[] }) {
return err.inner
.filter((i: { path: any }) => !!i.path)
.reduce(
(curr: any, next: { path: any; errors: any[] }) => ({
...curr,
[next.path]: next.errors[0],
}),
{},
)
}
function validateSchema(values: object, schema: Schema<object>) {
return schema
.validate(values, {
abortEarly: false,
strict: false,
})
.then(() => {
return {}
})
.catch(mapYupErrorsToFormikErrors)
}
// Your validation function, as you are using `withFormik` you will have the props present
function validateFoo(values, props) {
const { touched, value } = props.getFieldMeta('fooFieldName') // (or props.form.getFieldmeta, not sure)
const errors = validateSchema(values, yourYupSchema)
if (!touched && !value && errors.fooFieldName) {
delete errors.fooFieldName
}
return errors
}
Well, touched might not work for your use case because formik probably would set it to true on submission, but there you have all the props and you can use something different, like the empty value or some other state prop you manually set. You got all the control there.
I had a similar issue, I ended up creating another field where I set the value when showing the edit screen. Then i compare inside a test function like this :
originalField: yup.string().default(''),
field: yup.string().default('').required('Field is required.').test('is-test',
'This is my test.',
async (value, $field) => {
if($field.parent.originalField !== '' && value === $field.parent.originalField) return true
return await complexAsyncValidation(value)
}
Not perfect, but definitely working
Sorry for the general title but I don't know what else to call it at this point. I have a form that uses widgets. Im using the react-jsonschema-form package. Id use the github for the project but I don't think this is bug so I want to check here first. I think it's just how to use this React thing question. Possibly Redux as well.
So...
I have these widgets for some of the form elements in my component.
dropdownWidget = (props, type, id) => {
return (
<div>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
multiFileWidget = (props) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={fileWidgetPopup}>
<input type="file" id="multiFileName" required={props.required} onChange={(event) => { props.onChange(event.target.value); this.getFileNames(); this.hideResultTables(event.target.id) }} multiple />
</OverlayTrigger>
<textarea id="multiFileNameList" className="form-control" rows="4" style={{ marginTop: "2%" }} readOnly />
</div>
)
}
dropdownTooltipWidget = (props, type, id, tooltip) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={tooltip} hidden>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
</OverlayTrigger>
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
They are stuffed in a object like so:
multiUISchema = {
file: {
'ui:widget': this.multiFileWidget,
classNames: "uiSchema",
},
convertTo: {
'ui:widget': (props) => this.dropdownWidget(props, "ConvertToTypes", "multiConvertTo"),
classNames: "uiSchema"
},
notification: {
'ui:widget': (props) => this.dropdownTooltipWidget(props, "NotificationType", "notification", websNotificationPopup),
classNames: "uiSchema"
}
}
Where things go bad!! This function is injected in the widgets onChange handler.
hideResultTables(targetId) {
debugger;
//disable Retrieve button
if (targetId === 'multiFileName' || targetId === 'multiConvertTo') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
this.setState({ disabled: true });
}
//leave Retrieve Button enabled
else if (targetId !== 'appIDs') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
}
}
Basically if I hit the first if statement which has the setState for disabled to True, my onChange doesn't seem to retain the selected dropdown data to the Form Data object. It's like setState for Disabled true, which is for a button unrelated to the field does something to in the reconciliation tree possibly and/or my form data never retains the selected field in the formdata object at all?? idk. The field just stays undefined in all console logs if it's a field that hits that first if statement with the setState in it. But that setState has nothing to do with the fields being selected. It's disabling a button further down the page is all. Im sure im missing some React lifecycle, reconciliation async something or other. Im thinking of just reduxing this all but this simple bool value seems like it should stay as local state to the component. Any thoughts on things I should be checking before i redux this simple bool for my form?
To Further clarify my question, why does setState in the injected function in onChange seem to disrupt the onChange value update for the field on the form in my Widget when I select a field. That's really what this is all about. We cannot figure out why this is happening.
footnote** The dropdown data in the form is redux store data. So im selecting store data in my onchange in the widgets. Not sure if that's relevant.