How to get the length of material UI input filed in React JS? - reactjs

I am trying to enable the button when the material input text length is more than 0.
This is a material input field component.
const MaterialInput = ({
name,
id = name,
select,
options = [],
type,
label,
shrink,
formik,
disabled,
handleClick,
}) => {
return (
<TextField
type={type}
name={name}
label={label}
select={select}
autoComplete='off'
variant='outlined'
disabled={disabled}
className='material-input'
value={get(formik.values, name, '')}
onBlur={formik.handleBlur}
onChange={formik.handleChange}
helperText={get(formik.touched, name) && get(formik.errors, name)}
error={get(formik.touched, name) && Boolean(get(formik.errors, name))}
InputLabelProps={{ shrink }}
>
{options.map((item) => (
<MenuItem
onClick={handleClick}
className='text-capitalize'
value={item.value}
key={item.value}
disabled={item.disabled || false}
>
{item.label}
</MenuItem>
))}
</TextField>
);
};
This is how I am using it in other components.
aComponent.js
<MaterialInput name={inputName} label={label} formik={typeofForm} />
How to Enable button when the length of text field is more than 0.

There are many other ways to achieve it.
For suppose you material input name is mobileNumber and formik submit logic in component.js you can write below snippet in ur aComponent.js
useEffect(() => {
if (formik.values.mobileNumber.length === 0) {
// do your disable logic here
} else {
// do your logic
}
}, [formik.values.mobileNumber]);

You must check your validation on textField onchange, in this case, that you use formik for your form, Formik main component has a a callback function validate, you can use this function to handle your validation,
example
<Formik
validate={(values) => { // here check your validations and you have access to the values }}
validateOnChange
validateOnBlur
/>

you can use what you pass to value prop in TextField and get its length
const getInputLength = (formik, name) => {
const value = get(formik.values, name, '');
return value.length;
}

Related

React Hook Form with MUI Autocomplete, FreeSolo and dependent fields

I'm trying to create ZipCode / City / State (in Italian CAP / Città / Provincia) dependent fields from this JSON file (here's the repo as well). I'm using React Hook Form v7 and MUI v5.4.4. I'd like to implement this 3 fields using MUI Autocomplete component with FreeSolo props in order to let the user to insert a custom input value if it's not present in the JSON list.
I tried to make it works but it doesn't. How can I implement that? Furthermore, the validation for the Autocomplete component doesn't work.
Here's the codesandbox that I wrote
There were several problems in your code:
you forget to pass the rules prop to your <Controller />
the current selected value will be passed as the second argument to <Autocomplete />'s onChange handler
you need to use RHF's watch method to react to changes of those 3 dependent fields and filter the options of the other selects accordingly
you need to use flatMap instead of map for the mapping of the options for postal codes, as option.cap is an array
export default function PersonalDetails() {
const { watch } = useFormContext();
const { postalCode, city, state } = watch("personalDetails");
return (
<Card variant="outlined" sx={{ width: 1 }}>
<CardContent>
<Grid container item spacing={2}>
<Grid item xs={12} lg={3}>
<SelectFree
name="personalDetails.postalCode"
label="ZIP (CAP)"
options={options
.filter((option) =>
city || state
? option.nome === city || option.sigla === state
: option
)
.flatMap((option) => option.cap)}
rules={{ required: "Richiesto" }}
/>
</Grid>
<Grid item xs={12} lg={10}>
<SelectFree
name="personalDetails.city"
label="City (Città)"
options={options
.filter((option) =>
postalCode || state
? option.cap.includes(postalCode) || option.sigla === state
: option
)
.map((option) => option.nome)}
rules={{ required: "Richiesto" }}
/>
</Grid>
<Grid item xs={12} lg={2}>
<SelectFree
name="personalDetails.state"
label="State (Sigla)"
options={options
.filter((option) =>
city || postalCode
? option.nome === city || option.cap.includes(postalCode)
: option
)
.map((option) => option.sigla)}
rules={{ required: "Richiesto" }}
/>
</Grid>
</Grid>
</CardContent>
</Card>
);
}
export default function SelectFree({
name,
rules,
options,
getOptionLabel,
...rest
}) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
rules={rules}
defaultValue={null}
render={({
field: { ref, ...field },
fieldState: { error, invalid }
}) => {
return (
<Autocomplete
{...field}
freeSolo
handleHomeEndKeys
options={options}
getOptionLabel={getOptionLabel}
renderInput={(params) => (
<TextField
{...params}
{...rest}
inputRef={ref}
error={invalid}
helperText={error?.message}
/>
)}
onChange={(e, value) => field.onChange(value)}
onInputChange={(_, data) => {
if (data) field.onChange(data);
}}
/>
);
}}
/>
);
}
UPDATE
As you have a very large json file you have two options where you can optimise performance:
limit the amount of options via the filterOptions prop of <Autocomplete /> -> the createFilterOptions function can be configured to set a limit
add a useMemo hook for the filtering and mapping of your options before passing them to the <Autocomplete />, e.g. right now on every input change for the other fields (firstName, lastName, address) the options will be recomputed

React Formik Mui TextField with custom input loosing focus

I have a Formik form which contains a few Material UI TextField components. All of them works fine, except for one who looses focus when onChange() is executed. It has a PhoneInput (react-phone-number-input) component as a custom input.
Everything works fine (data is being saved, etc.), but I have to keep clicking on the input to continue writing on it.
I have a feeling it has something to do with the ref. When logging inputRef (PhoneInputRef, provided by TextField) it doesn't look like a normal ref.
Formik component (Parent)
{...}
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleClickRegister}>
{(props) => <RegisterForm {...props} />}
</Formik>
{...}
Form (Children)
{...}
const {
values: {
firstName,
lastName,
email,
confirmEmail,
password,
confirmPassword,
phoneNumber,
},
//Formik methods passed as props
errors,
touched,
handleChange,
handleSubmit,
setFieldValue,
setFieldTouched,
} = props;
{...}
//Formik onChange handler (used by every TextField besides the one containing PhoneInput)
const change = (name, e) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
//Custom onChange handler for Formik (used by PhoneInput)
const handleOnChange = (name, value) => {
setFieldValue(name, value);
setFieldTouched(name, true, false);
};
{...}
{/*NORMAL TEXTFIELD*/}
<TextField
className={classes.inputField}
id="confirmPassword"
name="confirmPassword"
label={t("header.registerDialog.form.labelConfirmPassword")}
value={confirmPassword}
onChange={change.bind(null, "confirmPassword")}
error={touched.confirmPassword && Boolean(errors.confirmPassword)}
helperText={
touched.confirmPassword && errors.confirmPassword ? (
errors.confirmPassword
) : (
<>
<br />
</>
)
}
/>
{/*CUSTOM INPUT TEXTFIELD*/}
<TextField
className={classes.textFieldPhoneInput}
key="textFieldPhone"
ref={textFieldRef}
id="phoneNumber"
name="phoneNumber"
label={t("header.registerDialog.form.labelPhone")}
InputLabelProps={{
className: phoneNumber
? classes.inputFieldPhoneLabel
: classes.inputFieldPhoneLabelEmpty,
}}
InputProps={{
inputComponent: ({ inputRef, ...rest }) => (
<PhoneInput
{...rest}
key="phoneInput"
ref={inputRef}
international
countryCallingCodeEditable={false}
name="phoneNumber"
defaultCountry="AR"
onChange={(phone) =>
handleOnChange("phoneNumber", phone)
}
labels={phoneLanguage}
value={phoneNumber}
/>
),
}}
error={touched.phoneNumber && Boolean(errors.phoneNumber)}
helperText={
touched.phoneNumber && errors.phoneNumber ? (
errors.phoneNumber
) : (
<>
<br />
</>
)
}
/>
{...}

How to display initialValues for material-ui autocomplete field?

I use the autocomplete field of material-ui (v5) and formik to generate my forms.
On this form, I have some lists defined as constants.
I use an api to get the default value of this list.
This api returns only the "code" of the option but not its label.
<Formik
enableReinitialize
initialValues={initialFormValues}
validationSchema={Yup.object().shape({
[...]
<Autocomplete
error={Boolean(touched.civility && errors.civility)}
helperText={touched.civility && errors.civility}
label="Civility"
margin="normal"
name="civility"
onBlur={handleBlur}
onChange={(e, value) => setFieldValue('civility', value)}
options={civilities}
value={values.civility}
getOptionLabel={(option) =>
option.name ? option.name : ''
}
isOptionEqualToValue={(option, value) => option.code === value}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label={<Trans>Civility</Trans>}
/>
)}
/>
My parameter isOptionEqualToValue is good because even if the value is not displayed in the input, it is well selected in the list.
You can see that the input text field is empty:
But if I unroll the list, I can see that my "ms" value has been selected:
What should I do to make the input text contain the default value?
After cloned your snippet code above, the problem was in getOptionLabel, the option argument is a string value, so it hasn't a name property and appears empty string. Here is an online example codesandbox.
import { useState } from "react";
import { Formik, Form } from "formik";
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
export default function App() {
const civilities = ["Mr", "Ms", "Other"];
const [values, setValues] = useState({
civility: "Ms"
});
const handleBlur = (e) => {
console.log("Blur:", e.target.value);
};
const setFieldValue = (type, value) => {
setValues((oldValues) => ({ ...oldValues, [type]: value }));
};
return (
<Formik err>
{({ errors, touched }) => (
<Form>
<Autocomplete
error={Boolean(touched.civility && errors.civility)}
helperText={touched.civility && errors.civility}
label="Civility"
margin="normal"
name="civility"
onBlur={handleBlur}
onChange={(e, value) => setFieldValue("civility", value)}
options={civilities}
value={values.civility}
isOptionEqualToValue={(option, value) => option.code === value}
renderInput={(params) => (
<TextField {...params} variant="outlined" label="Civility" />
)}
/>
</Form>
)}
</Formik>
);
}

Set defaultValues to Controllers in useFieldArray

Misunderstanding react-hook-forms.
I have a form for editing some stuff. Form contains fieldArray.
I set initial formData in useForm hook using default values
const methods = useForm({ defaultValues: defaultValues });
where defaultValues is
const defaultValues = {
test: [
{
name: "useFieldArray1"
},
{
name: "useFieldArray2"
}
]
};
And fieldArray. Here I'm using Controller (it's simplified case - in fact Custom input Controller more complex)
<ul>
{fields.map((item, index) => {
return (
<li key={item.id}>
<Controller
name={`test[${index}].name`}
control={control}
render={({value, onChange}) =>
<input onChange={onChange} defaultValue={value} />}
/>
<button type="button" onClick={() => remove(index)}>
Delete
</button>
</li>
);
})}
</ul>
When form is rendered everything is fine. Default values are displayed in input fields. But when I delete all fields and click append - new fields are not empty ... Default values are displayed
again. And it happens only with Controller. Why it happens ? And how I can avoid it?
Please, here is CodeSandBox link. Delete inputs and press append to reproduce what I am saying.
https://codesandbox.io/s/react-hook-form-usefieldarray-nested-arrays-forked-7mzyw?file=/src/fieldArray.js
Thanks
<Controller
name={name}
rules={rules}
defaultValue={defaultValue ? defaultValue : ''}
render={({ field, fieldState }) => {
return (
<TextField
inputRef={field.ref}
{...props}
{...field}
label={label}
value={field.value ? field.value : ''}
onChange={(event) => {
field.onChange(event.target.value);
props.onChange && props.onChange(event);
}}
style={props.style || { width: '100%' }}
helperText={fieldState?.error && fieldState?.error?.message}
error={Boolean(fieldState?.error)}
size={props.size || 'small'}
variant={props.variant || 'outlined'}
fullWidth={props.fullWidth || true}
/>
);
}}
/>

Downshift autocomplete onBlur resetting value with Formik

I have a form with a field that needs to show suggestions via an api call. The user should be allowed to select one of those options or not and that value that they type in gets used to submit with the form, but this field is required. I am using Formik to handle the form, Yup for form validation to check if this field is required, downshift for the autocomplete, and Material-UI for the field.
The issue comes in when a user decides not to use one of the suggested options and the onBlur triggers. The onBlur always resets the field and I believe this is Downshift causing this behavior but the solutions to this problem suggest controlling the state of Downshift and when I try that it doesn't work well with Formik and Yup and there are some issues that I can't really understand since these components control the inputValue of this field.
Heres what I have so far:
const AddBuildingForm = props => {
const [suggestions, setSuggestions] = useState([]);
const { values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
modalLoading,
setFieldValue,
setFieldTouched,
classes } = props;
const loadOptions = (inputValue) => {
if(inputValue && inputValue.length >= 3 && inputValue.trim() !== "")
{
console.log('send api request', inputValue)
LocationsAPI.autoComplete(inputValue).then(response => {
let options = response.data.map(erlTO => {
return {
label: erlTO.address.normalizedAddress,
value: erlTO.address.normalizedAddress
}
});
setSuggestions(options);
});
}
setFieldValue('address', inputValue); // update formik address value
}
const handleSelectChange = (selectedItem) => {
setFieldValue('address', selectedItem.value); // update formik address value
}
const handleOnBlur = (e) => {
e.preventDefault();
setFieldValue('address', e.target.value);
setFieldTouched('address', true, true);
}
const handleStateChange = changes => {
if (changes.hasOwnProperty('selectedItem')) {
setFieldValue('address', changes.selectedItem)
} else if (changes.hasOwnProperty('inputValue')) {
setFieldValue('address', changes.inputValue);
}
}
return (
<form onSubmit={handleSubmit} autoComplete="off">
{modalLoading && <LinearProgress/>}
<TextField
id="name"
label="*Name"
margin="normal"
name="name"
type="name"
onChange={handleChange}
value={values.name}
onBlur={handleBlur}
disabled={modalLoading}
fullWidth={true}
error={touched.name && Boolean(errors.name)}
helperText={touched.name ? errors.name : ""}/>
<br/>
<Downshift id="address-autocomplete"
onInputValueChange={loadOptions}
onChange={handleSelectChange}
itemToString={item => item ? item.value : '' }
onStateChange={handleStateChange}
>
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
}) => (
<div>
<TextField
id="address"
label="*Address"
name="address"
type="address"
className={classes.autoCompleteOptions}
{...getInputProps( {onBlur: handleOnBlur})}
disabled={modalLoading}
error={touched.address && Boolean(errors.address)}
helperText={touched.address ? errors.address : ""}/>
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{suggestions.map((suggestion, index) =>
<MenuItem {...getItemProps({item:suggestion, index, key:suggestion.label})} component="div" >
{suggestion.value}
</MenuItem>
)}
</Paper>
) : null}
</div>
</div>
)}
</Downshift>
<Grid container direction="column" justify="center" alignItems="center">
<Button id="saveBtn"
type="submit"
disabled={modalLoading}
className = {classes.btn}
color="primary"
variant="contained">Save</Button>
</Grid>
</form>
);
}
const AddBuildingModal = props => {
const { modalLoading, classes, autoComplete, autoCompleteOptions } = props;
return(
<Formik
initialValues={{
name: '',
address: '',
}}
validationSchema={validationSchema}
onSubmit = {
(values) => {
values.parentId = props.companyId;
props.submitAddBuildingForm(values);
}
}
render={formikProps => <AddBuildingForm
autoCompleteOptions={autoCompleteOptions}
autoComplete={autoComplete}
classes={classes}
modalLoading={modalLoading}
{...formikProps} />}
/>
);
}
Got it to work. Needed to use handleOuterClick and set the Downshift state with the Formik value:
const handleOuterClick = (state) => {
// Set downshift state to the updated formik value from handleOnBlur
state.setState({
inputValue: values.address
})
}
Now the value stays in the input field whenever I click out.

Resources