Changing Autocomplete value using react-hook-form (Material-UI) - reactjs

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.

Related

react MUI TextField inside react-hook-form Controller inside MUI Stepper-Dialog setValue not working

I have a Button that opens a MUI Dialog.
Inside the Dialog I have a MUI Stepper. My Form is split up into different parts. Some Inputs are required others are not.
//Example Input
<Controller
name="stateName"
control={control}
rules={{ required: true }}
render={({ field: { onChange, value } }) => (
<TextField
required
label="stateName"
variant="standard"
onChange={onChange}
value={value}
fullWidth
error={errors.stateName ? true : false}
helperText={errors.stateName ? "Pflichtfeld" : null}
/>
)}
/>
Full Example: https://codesandbox.io/s/gracious-tdd-dkzoqy
When I submit my form I add an entry to an existing list and display it alongside with an edit-Button.
If the edit-Button gets pressed I want to open the Dialog and have the Inputs filled with the values of the edited data.
I tried using react-hook-form setValue("field", value) but it is not working.
I also tried to pass the edit-object via Props to the nested form-steps and use setValue inside these components useEffect utilizing useFormContext() but it didn't work either.
How can I pass the values to the Inputs so they get correctly displayed in the Multi-Step-Form-Dialog?
Working CSB -> https://codesandbox.io/s/eloquent-chaum-znt71c?file=/src/App.tsx
In editHandler, state is a json string, so the simplest fix is to parse it into the object
const editHandler = (stateJSON: any) => {
const state = JSON.parse(stateJSON)
methods.reset(state);
But in submitHandler data is stringified, the submitHanlder should look smth like this:
const submitHandler = (data: any) => {
setContent(prevContent => [...prevContent,data] );
methods.reset();
setEditState(undefined);
setOpen(false);
};
Also checkout this out https://beta.reactjs.org/learn/updating-objects-in-state
and
how to avoid mutations https://www.educative.io/courses/simplifying-javascript-handy-guide/B6yY3r7vEDJ

Formik material ui with autocomplete not works as expected

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

Focus On Input With Error on Form Submission

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.

react-quill loses focus when onChange is called

I have a component wrapper in antd Form.create() HOC where I want to implement validation for my react-quill editor.
<Form.Item>
{getFieldDecorator('input', {
rules: [RequiredRule],
initialValue: text,
onChange: this.onChangeText
})(
<ReactQuill
className="question-form__rich-text"
modules={quillToolbar}
/>,
)}
</Form.Item>
If I start typing inside my quill editor text field it triggers the onChangeText function which in its turn changes the local state and initiates rerender of the component
onChangeText = (_, __, ___, editor) => {
this.setState({
textVal: editor.getText(),
});
};
this.state = {
textVal: '',
};
Every time the component rerenders my text field loses focus, so I have to click inside the field to type another letter.
I noticed that it happens only if the <ReactQuill> component is wrapped by antd <Form.Item>
It also shows some weird behaviour if I put console.log inside onChangeText function and try to check what's inside the text field:
Let's say my text field is initially empty. I type letter 'a' and it calls the function 3 times. First, it shows that the text field contains letter 'a', then it calls the function again showing that the field is empty and then the 3rd time letter 'a' appears again. This behaviour persists as I keep typing.
Also, there is an warning saying addRange(): The given range isn't in document. which I have no idea what it means.
I've been struggling with that issue for a few days now, any help would be greatly appreciated. Thank you
The reason it's loses focus when keypress is probably because the quill component is recreating when rerendering. See this same question about losing focus when typing.
I can't find a sample of implementing react quill on antd using getFieldDecorator, so I made a little workaround to make it work but same output. See this working link I made.
const { getFieldDecorator, validateFields, setFieldsValue } = props.form;
const onChangeText = (text) => {
console.log("called");
text = text !== "<p><br></p>" ? text : "";
setFieldsValue({ input: text });
setTextVal(text);
};
<Form.Item>
{getFieldDecorator("input", {
rules: [{ required: true }],
})(<Input.TextArea style={{ display: "none" }} />)}
<ReactQuill
className="question-form__rich-text"
modules={quillToolbar}
defaultValue={textVal}
onChange={onChangeText}
/>
</Form.Item>
as you can see in the above code, I place the editor outside getFieldDecorator and replaced it with hidden <Input> so your set of rules will still applied. The quill component is uncontrolled component and every time it change, it will also change the value of the hidden <Input> so when you submit or get the value of the form, the value of it is the value of quill component.
Also the warning you get addRange(): the given range isn't in the document was not visible on the codesandbox. You may look into this about that.

Can't change value of react-select field

I am using the React-Select library for my React app select fields. I have the same form for adding and editing data. If I don't provide any value, it works just fine, but if I want to have default values when the app renders, I can't change them. They appear as selected, but if I try to select another option it just doesn't update and the value I provided in value prop remains selected.
I tried using defaultValue instead of value, but then it doesn't select anything when the app renders.
<Select
placeholder="Deposit"
options={deposits}
getOptionValue={ option =>
option["id"]
}
getOptionLabel={ option => {
return option["name"];
}}
onChange={ value => {
this.setState({ deposit_id: value.id }}
value={{
value: deposit_id || "",
name: deposit_name || ""
}}
/>
deposits is an array of objects, and it renders the list of names, but when I click on one of them, the one that I provided in value remains selected, but it should select the one that I click on... How do I change it? Is there something wrong with my onChange function? Or what is it that I'm doing wrong?
Try the following code. The value props should be a string, i.e the value from the state.
<Select
placeholder="Deposit"
options={deposits}
getOptionValue={ option =>
option["id"]
}
getOptionLabel={ option => {
return option["name"];
}}
onChange={ value => {
this.setState({ deposit_id: value.id }}
value={deposit_id}
/>

Resources