I'm using formik with material-ui to build custom autocomplete component.
my link to code
I add validation using yup for autocomplete to be required (user need to select one option):
validationSchema: Yup.object().shape({
skill: Yup.object().shape({
value: Yup.string().required(),
label: Yup.string().required()
})
}),
when user select any option I want it to save the option as {label,value} and not just the value because then I need to send it as object to the server.
I get some errors:
when I press submit I get error
Objects are not valid as a React child (found: object with keys {value, label}). If you meant to render a collection of children, use an array instead.
I get those warnings:
MUI: The value provided to Autocomplete is invalid. None of the options match with `{"label":"","value":""}`. You can use the `isOptionEqualToValue` prop to customize the equality test.
Warning: Failed prop type: Invalid prop `helperText` supplied to `ForwardRef(TextField)`, expected a ReactNode.
I'm trying to build reusable autocomplete component to be able to get the selected value as {label,value} without any error and working as expected.
I'm not sure if my validation is the right way. I want just the user to select an option.
in addition, I would like to ask, can I store object details into the value not as string? if no, what the best way to store value of object as option?
is my yup schema is the right way? should I store id of object as value and then filter it? how to pass value as string to autocomplete and show it selected in autocomplete?
How can I do it without skill schema as object, just as string like:
validationSchema: Yup.object().shape({
skill: Yup.string().required()
}),
This is because initial value as {label:'' , value :''} is not in skills list. so in isOptionEqualToValue it will return false
simply remove isOptionEqualToValue prop and in value check if there is option with that value and if not set null
const getValueFromOptions = (value: string) => {
return (
options.find((option: any) => option.value === value) ?? null
);
};
and in auto complete :
<Autocomplete
value={getValueFromOptions(field.value)}
...
or check if field.value has value otherwise null
<Autocomplete
value={Boolean(field.value?.value) ? field.value : null}
...
and another error was from show error message because meta.error is object that contains value and label error so you need to show one of them, I changed it to this :
renderInput={(params) => {
const errorMsg = meta.error as { value?:string }
return (
<TextField
{...params}
label={label}
name={name}
error={showError}
helperText={meta.touched && errorMsg?.value}
/>
);
}}
and another error was from show error message, it was displaying 'skill.value is a required field' so I simply add new message in yup validation
validationSchema: Yup.object().shape({
skill: Yup.object().shape({
value: Yup.string().required('skill is a required field'),
label: Yup.string()
})
}),
you see pure code here : https://codesandbox.io/s/blissful-nova-nqvxq2?file=/src/components/AutocompleteField.tsx
Related
I have a warning that says
Warning: You provided a `value` prop to a form field without an `onChange` handler.
This will render a read-only field. If the field should be mutable use `defaultValue`.
`Otherwise, set either `onChange` or `readOnly`.`
Of course, I have googled a solution for it. One way to fix it is to replace "value" attribute with "defaultValues". But, this is causing another issue because my component is a controlled.
I could add onchange attribute and method to the fields. But the problem is that it will cause redundancy as I have a single onChange method on the tag that manages all the values and states. For example,
const [allStates, setAllStates] = useState({value_one: "", value_two:""});
function handleOnChange(event){
.........
//update the allStates state here
}
<Form onChange={handleOnChange}>
<input value={allStates.value_one} />
<input value={allStates.value_two} />
</Form>
What are the alternatives I have? Should I just ignore it since I can only see it on dev environments, the end users cannot and won't see it on the prod environment anyway?
It is a Helpful Warning by React, you can probably refer to this thread which discusses this.
https://github.com/facebook/react/issues/1118
Although adding the defaultValue should work for you, since your state has the latest value for the inputs, and when your component would render for the first time as long as the defaultValue is assigned to your <input>, the fields would reflect your state.
const [state, setState] = useState({});
const handleChange = (ev) => {
setState(prev_state => {
return {
...prev_state,
[ev.target.name]: ev.target.value
}
})
}
<form onChange={handleChange}>
<input name={"foo"} defaultValue={state.foo}/>
<input name={"bar"} defaultValue={state.bar}/>
</form>
The warning it is giving you is that you have provided the input with a value argument, so when you go to type in the input, it won't work:
const [allStates, setAllStates] = useState({
value_one: 'I will be display in the input initially', // <-- This text will already be entered in the text input.
value_two: ''
});
<input value={allStates.value_one} />
But since you added the value prop, you need to give it an onChange, otherwise the value of the field will always be the same and you won't be able to change it.
Ofcourse you can add the onChange like this:
const [allStates, setAllStates] = useState({
value_one: '',
value_two: ''
});
const handleOnChange = (event) => {
setAllStates({
...allStates,
// Now you can target the name prop
[event.target.name]: event.target.value
});
}
return (
<>
{/* I will suggest adding a name prop also */}
<input name="value_one" value={allStates.value_one} onChange={handleOnChange} />
<input name="value_two" value={allStates.value_two} onChange={handleOnChange} />
</>
);
The name prop is really useful for these one function setups, as it allows you to target the state value straight from the name.
But remember that the name values should be the same as the values in the state.
I am using React with Formik v ^2.2.5"
Here is a link to a code snippet in sandbox: https://codesandbox.io/s/formik-example-forked-vqwnm3?file=/index.js
The important parts:
Note the default values. facebook is nested within socialMedia:
initialValues={{ email: "", socialMedia: { facebook: "" } }}
validationSchema={Yup.object().shape({
email: Yup.string().email().required("Required"),
socialMedia: Yup.object().shape({
facebook: Yup.string(),
gmail: Yup.string()
})
})}
For the actual input, note the id is socialMedia.facebook (using the nested value). and the onBlur calls validateField("socialMedia.facebook") :
<input
id="socialMedia.facebook"
name="facebook"
value={values.socialMedia.facebook}
onChange={handleChange}
onBlur={() => {
validateField("socialMedia.facebook");
setFieldTouched("socialMedia.facebook");
}}
className={
errors.socialMedia?.facebook && touched.socialMedia?.facebook
? "text-input error"
: "text-input"
}
/>
When you onblur from the facebook field, it throws an error
can't access property "validate", fieldRegistry.current[name] is undefined
But I would expect for there to be no error and the field is properly validated.
Note: setFieldTouched("socialMedia.facebook"); does indeed work and sets the proper touch field in the formik object.
You must define name props. Because formik recognizes the field values by name.
It would be nice to have functionality when the user submits a form that fails the validation, it will set focus on the first field with an error.
I have shouldFocusError : true set in the useForm configuration. When I click submit, the validation triggers, but the input is not focused.
Any suggestions on how I can achieve this?
For reference, I am using MUI and react-hook-form to create custom components in the form. For the components themselves, they are using the useController hook like so:
const { field, fieldState } = useController(props);
<TextField
{...field}
error={!!fieldState.error}
helperText={fieldState.error?.message || ''}
/>
Solution:
Spreading the field object returned from the useController hook contains the following:
{
value: ...
name: ...
onChange: ...
onBlur: ...
ref: ...
}
The problem I described above was solved when I removed ref property from the object and passed it separately to inputRef. Like so:
const { field, fieldState } = useController(props);
const { ref, ...fieldProps } = field;
<TextField
{...fieldProps}
inputRef={ref} //or you can use field.ref
id={field.name} //this solved other issues I was having
error={!!fieldState.error}
helperText={fieldState.error?.message || ''}
/>
After doing this, React Hook Form set focus on the first field with an error when the user submits a form that fails the validation.
I have two Autocomplete fields, my goal is to change the value of the second field by the value of the first field.
The problem I face is when my trying to send the new value to the "setValue" function nothing happens the state form changing, but the autocomplete field shows the old value.
In this sand box:
https://codesandbox.io/s/dazzling-carson-84dxp?file=/src/Form/Components/UserCountry.js
you can see my implementation.
If you look at console when you change user_name field, you can see materialUI warning in the console which says:
Material-UI: A component is changing the uncontrolled value state of Autocomplete to be controlled.
it's reason is that your user_country's value by default is undefined and material consider this field as uncontrolled field, it means material will take the control of user_country value. To solve the problem you have two solutions:
1- Defining your form by defaultValues options like this:
const methods = useForm({
defaultValues: {
user_name: null,
user_country: null
}
});
2- Sending null as value to AutoComplete when it doesn't have any value, like this:
<Controller
control={control}
name={`user_country`}
render={({ field: { onChange, value } }) => (
<Autocomplete
id="country-select-demo"
options={countries}
onChange={(event, item) => {
onChange(item);
}}
value={value || null}
...
/>
)}
/>
Here You can see your form which updates user_country whenever you change user_name field.
I am using two library with is Formik and Yup. And trying to validate by the value stored on the radio box(check box as well) but seems like I cant find anything on google where I can get the actual value to check. I can use the name value to validate but is there a way to check validation by looking at the value? I will provide the example below.
const initialValues = {
A: "",
B: "",
C: [],
};
const onSubmit = (values) => {
console.log(values);
};
const validationSchema = Yup.object({
A: Yup.string().required("Required"),
B: Yup.string().required("Required"),
C: Yup.array().min(1, "Must be selected"),
});
and here is the return for react
return (
<>
<Formik
enableReinitialize={true}
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
render={(props) => <TreatmentView {...props} data={data} />}
/>
</>
);
And this is the rendered view below
I want to store that logic on the validationSchema. So again, I want to add a validation where checkbox with name "C" with value "4" can only be checked when radio button of name "B" with value "3" is selected. I did looked at the doc for the YUP and tried using the .value but seems like its not working..
Thank You!