useFieldArray with Material UI Select doesn't work - javascript

I am trying to use useFieldArray for dynamic Select field of Material UI and react-hook-form. It works fine with TextField, but when used with Select it doesn't work..
What doesn't work..
Set defaultValue doesn't show in Select, but shows in TextField
Value selected on Select and inputted value in TextField doesn't come when click in Submit.
Here is the code of Select component
import * as React from "react";
import {
FormControl,
FormHelperText,
InputLabel,
MenuItem,
OutlinedInput,
Select,
SelectChangeEvent
} from "#mui/material";
import { useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import Placeholder from "./Placeholder";
export interface IOptionTypes {
id: string;
label: string;
value: string;
}
interface IFormElementTypes {
name: string;
label: string;
required?: boolean;
defaultValue: string;
options: IOptionTypes[];
placeholder: string;
}
export default function MultiSelectField({
name,
label,
required,
defaultValue,
options,
placeholder
}: IFormElementTypes) {
const {
control,
register,
formState: { errors }
} = useFormContext();
// const defaultVal = control._defaultValues[name];
const [selectedVal, setSelectedVal] = useState<string[]>([]);
const handleChange = (event: SelectChangeEvent<typeof selectedVal>) => {
const {
target: { value }
} = event;
setSelectedVal(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value
);
};
const labelText = `${label}${required ? "*" : ""}`;
const labelId = `multi-select-${name}`;
return (
<Controller
name={name}
defaultValue={defaultValue}
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel
shrink
sx={{ backgroundColor: "white", padding: "0px 2px" }}
error={!!errors[name]}
id={labelId}
>
{labelText}
</InputLabel>
<Select
{...field}
label={labelText}
labelId={labelId}
value={selectedVal}
displayEmpty
multiple
{...register(`${name}` as const)}
variant="outlined"
fullWidth
error={!!errors[name]}
input={
<OutlinedInput
id={`multi-select-${label}`}
label={`${label}${required ? "*" : ""}`}
/>
}
renderValue={
selectedVal.length > 0
? undefined
: () => <Placeholder>{placeholder}</Placeholder>
}
onChange={handleChange}
>
{options.map((option) => (
<MenuItem key={option.id} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText>{String(errors[name]?.message ?? "")}</FormHelperText>
</FormControl>
)}
/>
);
}
and How it is been called finally
import * as React from "react";
import {
useForm,
useFieldArray,
FormProvider,
SubmitHandler
} from "react-hook-form";
import { Button, Grid, Stack, Box } from "#mui/material";
import MultiSelectField, { IOptionTypes } from "./MultiSelectField";
import TextFieldElement from "./TextFieldElement";
type FormValues = {
items: { name: string; age: string }[];
};
const defaultValues = {
items: [{ name: "john", age: "10" }]
};
const names: IOptionTypes[] = [
{ id: "1", label: "John", value: "john" },
{ id: "2", label: "Smith", value: "smith" },
{ id: "3", label: "Julia", value: "julia" },
{ id: "4", label: "David", value: "david" }
];
export default function App() {
const methods = useForm<FormValues>({
defaultValues,
mode: "all"
});
const { control, formState, setError } = methods;
const { fields, append, remove } = useFieldArray({ name: "items", control });
const formSubmitHandler: SubmitHandler<FormValues> = async (
data: FormValues
) => {
console.log("Form values ", data);
};
return (
<Grid container>
<Grid item xs={12}>
<Stack direction="row" spacing={2} my={2}>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
onClick={() => {
append({ name: "", age: "" });
}}
>
Add Field
</Button>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
onClick={() => {
remove(fields.length - 1);
}}
>
Remove Field
</Button>
</Stack>
</Grid>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(formSubmitHandler)}>
<Grid item xs={12}>
<Stack spacing={2}>
{fields.map((field, index) => (
<Stack direction="row" spacing={2} key={field.id}>
<MultiSelectField
name={`items.${index}.name`}
label="Choose Name"
placeholder="Name"
defaultValue=""
options={names}
/>
<TextFieldElement
name={`items.${index}.age`}
label="Input Age"
placeholder="Age"
/>
</Stack>
))}
</Stack>
</Grid>
<Grid container my={2}>
<Grid item>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
>
Save Changes
</Button>
</Grid>
</Grid>
</form>
</FormProvider>
</Grid>
);
}
As in the code above, I have set default values, which works for TextField but not for the SelectField
I wonder what is wrong in there..
Here is my Codesandbox, thanks for help.

You're going to want to make sure that you set the value prop on your Select inside of MultiSelectField to selectedVal:
<Select
{...field}
label={labelText}
labelId={labelId}
value={selectedVal}
displayEmpty
multiple
{...register(`${name}` as const)}
variant="outlined"
fullWidth
error={!!errors[name]}
input={
<OutlinedInput
id={`multi-select-${label}`}
label={`${label}${required ? "*" : ""}`}
/>
Also remove the defaultValue={defaultValue} from the Controller in MultiSelectfield so the Controller looks like this:
<Controller
name={name}
control={control}
render={({ field }) => (
Since you are using controlled components, we need to make sure we set the default value of selectedVal to the default value passed in as a prop:
const [selectedVal, setSelectedVal] = useState<string[]>(defaultValue);
selectedVal is supposed to be a string[] not a string, so we should change the type of defaultValue to a string[]:
interface IFormElementTypes {
name: string;
label: string;
// eslint-disable-next-line react/require-default-props
required?: boolean;
defaultValue: string[];
options: IOptionTypes[];
placeholder: string;
}
Finally, back in App.tsx we should pass an array to MultiSelectField as the defaultValue prop. Here's what it would look like if we wanted "John" to be selected by default:
<MultiSelectField
name={`items.${index}.name`}
label="Choose Name"
placeholder="Name"
defaultValue={["john"]}
options={names}
/>
Or, if we wanted to have the default be "John" and "Smith" then we could set the default like this:
defaultValue={["john", "smith"]}
Here's a full working example on CodeSandbox:

Related

Infer type from parent prop to children using React.cloneElement

I'm using formik to create a reusable component. This is how I've created a container to render a formik form.
I need to infer types from props; since, i've cloned a children, also passed on the props from the container, I need to infer types in the FormFields
import React from 'react';
import { Formik } from 'formik';
import { Container, Grid } from '#mui/material';
import MainCard from 'ui-component/cards/MainCard';
interface ContainerProps<T> {
title: string;
initialValues: T;
validationSchema: Object;
handleSubmit: (values: T, setSubmitting: (isSubmitting: boolean) => void) => void;
children: React.ReactElement;
others?: any;
}
const FormikContainer = <T,>({ title, initialValues, validationSchema, handleSubmit, children, ...others }: ContainerProps<T>) => (
<MainCard title={title}>
<Formik
enableReinitialize
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
handleSubmit(values, setSubmitting);
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<Grid container gap={3} maxWidth="500px">
{React.cloneElement(children, { ...props })}
</Grid>
</form>
)}
</Formik>
</MainCard>
);
export default FormikContainer;
I'm not sure on how to infer types in the FormFields for all the props associated with it. How can I define types for otherProps and since otherProps are props through formik;I need to infer formikProps types dynamically as well.
FormFields.tsx
/* eslint-disable consistent-return */
/* eslint jsx-a11y/label-has-associated-control: 0 */
import React, { useState } from 'react';
import {
Grid,
TextField,
FormHelperText,
FormControl,
FormLabel,
RadioGroup,
FormControlLabel,
Radio,
MenuItem,
Button,
IconButton,
Typography,
InputLabel
} from '#mui/material';
import { FieldArray, getIn } from 'formik';
import { v4 as uuid } from 'uuid';
import AddIcon from '#mui/icons-material/Add';
import DeleteOutlineIcon from '#mui/icons-material/DeleteOutline';
import { ImgWrapper } from './formik.styles';
import { Label } from 'components/form/Form';
type PropTypes = {
formFields: any;
btnLabel: string;
handleAdd?: any;
handleRemove?: any;
otherProps?: any;
};
const ErrorMessage = ({ errors, touched, name }) => {
// console.log(errors);
return <FormHelperText error>{getIn(touched, name) && getIn(errors, name) && getIn(errors, name)}</FormHelperText>;
};
const FormFields = ({ formFields, btnLabel, ...otherProps }): any => {
const { values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue, handleAdd, handleRemove } =
otherProps;
const [img, setImg] = useState<string>('');
return (
<>
{formFields.map((field, index) => {
switch (field.type) {
case 'fieldArray':
return (
<Grid key={index} item xs={12}>
<Typography
variant={field.mainHeading.variant || 'h4'}
component="h1"
textAlign={field.mainHeading.align}
sx={{ p: 2 }}
>
{field.mainHeading.title}
</Typography>
<FieldArray name={field.name}>
{({ push, remove }) => {
return (
<Grid container gap={3} maxWidth="100%">
{values[field.name].map((eachField, fieldIndex) => {
const IconButtonList = !field.iconButtonDisable && (
<Grid key={`container-${fieldIndex}`} container>
<Grid item xs={10}>
<Typography variant="h4">{`${field.subHeading} ${
fieldIndex + 1
}`}</Typography>
</Grid>
<Grid
item
xs={2}
sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}
>
<IconButton onClick={() => handleAdd(push)}>
<AddIcon />
</IconButton>
<IconButton
onClick={() => {
handleRemove(eachField.id, values, remove);
}}
disabled={values[field.name].length === 1}
>
<DeleteOutlineIcon />
</IconButton>
</Grid>
</Grid>
);
const QuestionList = field.choice.map((eachChoice, choiceIndex) => {
return (
<Grid key={`question-${choiceIndex}`} item xs={12}>
<InputLabel>{eachChoice.label}</InputLabel>
<TextField
fullWidth
placeholder={eachChoice.placeholder}
name={`${field.name}[${fieldIndex}].${eachChoice.name}`}
label={eachChoice.innerLabel}
type={eachChoice.type}
size="medium"
onBlur={handleBlur}
onChange={handleChange}
/>
<ErrorMessage
{...{
errors,
touched,
name: `${field.name}[${fieldIndex}].${eachChoice.name}`
}}
/>
</Grid>
);
});
return [IconButtonList, QuestionList];
})}
</Grid>
);
}}
</FieldArray>
</Grid>
);
}
})}
<Button type="submit" variant="contained" onSubmit={handleSubmit} disabled={isSubmitting}>
{btnLabel || 'Test Button'}
</Button>
</>
);
};
export default FormFields;
It's how I thought of creating a reusable component using formik. Am I doing it right ?
Form.tsx
import FormikContainer from 'components/formik/FormikContainer';
import FormFields from 'components/formik/FormFields';
import * as Yup from 'yup';
import { v4 as uuid } from 'uuid';
import AddPhotoAlternateOutlinedIcon from '#mui/icons-material/AddPhotoAlternateOutlined';
import { formFields } from 'views/userManagement/appUsers/constants/variables';
const initialValues = {
content: [{ id: uuid(), question: '', answer: '' }]
};
const fields = [
{
name: 'content',
type: 'fieldArray',
mainHeading: { align: 'center', title: 'Main heading' },
subHeading: 'Section',
iconButtonDisable: false,
choice: [
{ name: 'question', type: 'text', label: 'Question', placeholder: 'Enter question' },
{ name: 'answer', type: 'text', label: 'Answer', placeholder: 'Enter answer' }
]
}
];
const Form = () => {
const handleSubmit = (values, setSubmitting: (isSubmitting: boolean) => void) => {
console.log(values);
setSubmitting(false);
};
const handleAdd = (push) => {
push({ id: uuid(), question: '', answer: '' });
};
const handleRemove = (id, values, remove) => {
// const target = values.content.findIndex((value) => value.id === id);
// console.log(target);
// remove(target);
console.log(values);
values.content = values.content.filter((value) => value.id !== id);
};
return (
<FormikContainer<typeof initialValues>
title="Formik Reusable components"
initialValues={initialValues}
validationSchema={Yup.object().shape({
content: Yup.array().of(
Yup.object().shape({
question: Yup.string().required('Question is a required field'),
answer: Yup.string().required('Answer is a required field')
})
)
})}
handleSubmit={handleSubmit}
>
<FormFields formFields={fields} btnLabel="Test Button" handleAdd={handleAdd} handleRemove={handleRemove} />
</FormikContainer>
);
};
export default Form;

Using a single Keyboarddatepicker how do I add a validation from date can not be greater than thru date in ReactJS (react hooks) Material UI

Using one Single stateless Datepicker component present in datePicker.js which I am repurposing as hire date & exit date how can i add a validation from date can not be greater than Thru date before i submit the form (EmployeeForm.js?
I have added my codes for DatePicker component where I have added some props to it and KeyboardDatepicker is from Material UI. I have also added my form details where in I am using this Datepicker component.
DatePicker.js
import React from 'react'
import { MuiPickersUtilsProvider, KeyboardDatePicker } from "#material-ui/pickers";
import DateFnsUtils from "#date-io/date-fns";
import { FormControl, InputLabel, Select as MuiSelect, FormHelperText } from '#material-ui/core';
export default function DatePicker(props) {
const { name, label, value, error=null,onChange } = props
const convertToDefEventPara = (name, value) => ({
target: {
name, value
}
})
return (
<FormControl variant="outlined"
{...(error && {error:true})}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker disableToolbar variant="inline" inputVariant="outlined"
label={label}
format="MM/dd/yyyy"
name={name}
value={value}
onChange={date =>onChange(convertToDefEventPara(name,date))}
// minimumdate={value}
selectsStart
InputLabelProps={{ style: { fontSize: 17 } }}
/>
</MuiPickersUtilsProvider>
{error && <FormHelperText>{error}</FormHelperText>}
</FormControl>
)
}
Employee.Js
import React, { useState, useEffect } from 'react'
import { Grid, } from '#material-ui/core';
import Controls from "../../components/controls/Controls";
import { useForm, Form } from '../../components/useForm';
import * as employeeService from "../../services/employeeService";
const genderItems = [
{ id: 'male', title: 'Male' },
{ id: 'female', title: 'Female' },
{ id: 'other', title: 'Other' },
]
const initialFValues = {
id: 0,
fullName: '',
email: '',
mobile: '',
city: '',
gender: 'male',
departmentId: '',
hireDate: new Date(),
exitDate: new Date(),
isPermanent: false,
}
export default function EmployeeForm(props) {
const { addOrEdit, recordForEdit } = props
const validate = (fieldValues = values) => {
let temp = { ...errors }
if ('fullName' in fieldValues)
temp.fullName = fieldValues.fullName ? "" : "This field is required."
if ('email' in fieldValues)
temp.email = (/$^|.+#.+..+/).test(fieldValues.email) ? "" : "Email is not valid."
if ('mobile' in fieldValues)
temp.mobile = fieldValues.mobile.length > 9 ? "" : "Minimum 10 numbers required."
if ('departmentId' in fieldValues)
temp.departmentId = fieldValues.departmentId.length != 0 ? "" : "This field is required."
setErrors({
...temp
})
if (fieldValues == values)
return Object.values(temp).every(x => x == "")
}
const {
values,
setValues,
errors,
setErrors,
handleInputChange,
resetForm
} = useForm(initialFValues, true, validate);
const handleSubmit = e => {
e.preventDefault()
if (validate()) {
addOrEdit(values, resetForm);
}
}
useEffect(() => {
if (recordForEdit != null)
setValues({
...recordForEdit
})
}, [recordForEdit])
return (
<Form onSubmit={handleSubmit}>
<Grid container>
<Grid item xs={6}>
<Controls.Input
name="fullName"
label="Full Name"
value={values.fullName}
onChange={handleInputChange}
error={errors.fullName}
/>
<Controls.Input
label="Email"
name="email"
value={values.email}
onChange={handleInputChange}
error={errors.email}
/>
<Controls.Input
label="Mobile"
name="mobile"
value={values.mobile}
onChange={handleInputChange}
error={errors.mobile}
/>
<Controls.Input
label="City"
name="city"
value={values.city}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={6}>
<Controls.RadioGroup
name="gender"
label="Gender"
value={values.gender}
onChange={handleInputChange}
items={genderItems}
/>
<Controls.Select
name="departmentId"
label="Department"
value={values.departmentId}
onChange={handleInputChange}
options={employeeService.getDepartmentCollection()}
error={errors.departmentId}
/>
<Controls.DatePicker
name="hireDate"
label="Date of Hire "
value={values.hireDate}
onChange={handleInputChange}
/>
<Controls.DatePicker
name="exitDate"
label="Date of Exit"
value={values.exitDate}
onChange={handleInputChange}
/>
<Controls.Checkbox
name="isPermanent"
label="Permanent Employee"
value={values.isPermanent}
onChange={handleInputChange}
/>
<div>
<Controls.Button
type="submit"
text="Submit" />
<Controls.Button
text="Reset"
color="default"
onClick={resetForm} />
</div>
</Grid>
</Grid>
</Form>
)
}

React Autocomplete with Material UI

I'm implementing a component Autocomplete using Material UI library.
But there's a problem - I'm not sure how to pass value and onChange properly, because I have a custom implementation of TextField that requires value and onChange as well. Should I pass value and onChange twice - to Autocomplete and TextField? Or maybe there's a better solution? Would appreciate any help!
Here's my code:
import { Autocomplete as MuiAutocomplete } from '#material-ui/lab'
import { FormControl } from 'components/_helpers/FormControl'
import { useStyles } from 'components/Select/styles'
import { Props as TextFieldProps, TextField } from 'components/TextField'
export type Props = Omit<TextFieldProps, 'children'> & {
options: Array<any>
value: string
onChange: (value: string) => void
disabled?: boolean
}
export const Autocomplete = (props: Props) => {
const classes = useStyles()
return (
<FormControl
label={props.label}
error={props.error}
helperText={props.helperText}
>
<MuiAutocomplete
options={props.options}
// value={props.value}
// onChange={event =>
// props.onChange((event.target as HTMLInputElement).value as string)
// }
classes={{
option: classes.menuItem,
}}
disabled={props.disabled}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange}
/>
)}
renderOption={option => {
return <Typography>{option.label}</Typography>
}}
/>
</FormControl>
)
}```
Material UI has props built in to handle the state of the Autocomplete vs input values.
You can see it in use in the docs here: https://material-ui.com/components/autocomplete/#controllable-states
In your example, you would want to add the inputChange and onInputChange props to the Autocomplete component. These will get passed down to your TextField through the params passed to the renderInput function.
So your final code would look something like the below snippet copied from the linked documentation:
<Autocomplete
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" variant="outlined" />}
/>
import React, { useEffect, useState } from "react";
import { Autocomplete } from "#mui/material/node";
import { Controller, useFormContext } from "react-hook-form";
import { TextField } from "#mui/material";
import PropTypes from "prop-types";
const valueFunc = (arr, id) => {
const temp = arr.length > 0 && arr?.find((element) => element.id === id);
return temp;
};
AutocompleteSearch.propTypes = {
options: PropTypes.arrayOf({
title: PropTypes.string,
id: PropTypes.string,
}),
name: PropTypes.string,
};
export default function AutocompleteSearch({
name,
options,
label,
id,
...other
}) {
const [temp, setTemp] = useState({});
const { control, setValue } = useFormContext();
useEffect(async () => {
const found = valueFunc(options, id);
await setTemp(found);
}, [options, id]);
return (
<Controller
control={control}
name={name}
rules={{ required: true }}
render={({ fieldState: { error } }) => (
<>
<div >
<Autocomplete
id="controllable-states-demo"
onChange={(_, v) => {
setValue(name, v?.id);
setTemp(v);
}}
onBlur={(e) => {
e.target.value == "" && setValue(name, "");
}}
value={temp}
options={options}
getOptionLabel={(item) => (item.title ? item.title : "")}
renderInput={(params) => (
<>
<TextField
{...params}
label={label}
InputLabelProps={{
style: {
fontSize: "14px",
fontWeight: "400",
color: "#FF5B00",
},
}}
size="small"
error={temp === null && !!error}
helperText={temp === null && error?.message}
{...other}
/>
</>
)}
/>
</div>
</>
)}
/>
);
}
<AutocompleteSearch
name="pharmacy_group_title"
label="Pharmacy Group"
options={pharmacyGroups} // Array {id , title}
id={defaultValues?.pharmacy_group_title} // ID
/>

how to show error note for only when a particular object value is empty in text field in reactjs

My Objective is to show error to the particular object id when we dont enter any value in the textfield in onChange method. But it is appearing in the other id text field.
JSON data looks in this way:
"details": [
{ "id": "12wer1", "name": "ABC", "age": 15 },
{ "id": "78hbg5", "name": "FRE", "age": 21 }
]
I've tried in this way:
{this.state.details.map((y) => (
<Grid container justify="center" spacing={2}>
<Grid item xs={3}>
<TextField label="Name" name="dName" variant="outlined" value={y.name}
onChange={(e)=>this.onChange(e.target.name, e.target.value)}
/>
</Grid>
<Grid item xs={3}>
<TextField label="Age" name="age"variant="outlined" value={y.age} onChange={(e)=>this.onChange(e.target.name, e.target.value)}/>
<FormHelperText >
{this.state.errors.age}
</FormHelperText>
</Grid>
</Grid>
))}
OnChange method:
onChange = (name, value)=> {
this.state.details.forEach((item) => {
if (!item.age) {
errors.age = "age is required";
}
else {
errors.age = ""
}
}
I'm getting error in other ids also. for example i want to clear the age for 1st id then error is showing on both the ids.
Can anyone help me to fix this error?
We will have to something like this.
import React from "react";
import { Grid, TextField, FormHelperText } from "#material-ui/core";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: "12wer1", name: "ABC", age: 15 },
{ id: "78hbg5", name: "FRE", age: 21 }
],
errors: {
name: {},
age: {}
}
};
this.onNameChange = this.onChange.bind(this, "name");
this.onAgeChange = this.onChange.bind(this, "age");
}
onChange = (property, id, e) => {
const value = e.target.value;
let { details, errors } = this.state;
details.forEach((d) => {
if (d.id === id) {
d[property] = value;
errors[property][id] = value === "" ? `${property} is required` : "";
}
});
console.log(errors);
this.setState({
details: details,
errors: errors
});
};
render() {
return (
<div className="App">
{this.state.details.map((y) => (
<Grid container justify="center" spacing={2}>
<Grid item xs={3}>
<TextField
label="Name"
name="dName"
variant="outlined"
value={y.name}
onChange={(e) => this.onNameChange(y.id, e)}
/>
<FormHelperText>
{this.state.errors.name[y.id] || ""}
</FormHelperText>
</Grid>
<Grid item xs={3}>
<TextField
label="Age"
name="age"
variant="outlined"
value={y.age}
onChange={(e) => this.onAgeChange(y.id, e)}
/>
<FormHelperText>
{this.state.errors.age[y.id] || ""}
</FormHelperText>
</Grid>
</Grid>
))}
</div>
);
}
}
We basically have to link each error's object with it's specific rendered element.
Here is the running code for the same: codesandbox

Why react-validation hasErrors issue

I am using react-validation to validate a login form. After submitting the form I want to know has an error or not. I can get the error state inside my custom button object in below way.
const customSubmit = ({ hasErrors, ...props }) => {
return <Button disabled={hasErrors} >Login</Button>
{hasErrors.toString()}/>
};
const SubmitButton = button(customSubmit);
Is there any way to get hasError state when submit a from?
I can share a similar code when I use State and handleChange to get the errors in the button.
First you need a function were you declare the useState.
import React from "react";
import { Box, Button, Collapse, Grid, TextField } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
function FormVal() {
const [phone, setPhone] = React.useState<string>();
const [errors, setErrors] = React.useState<{ phone: string }>();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { value },
} = event;
setErrors({ phone: "" });
setPhone(value);
let reg = new RegExp(/^\d*$/).test(value);
if (!reg) {
setErrors({ phone: "Only numbers" });
}
};
Now in the return section you need to declare the handleChange. There may be some elements that you don't know such as and that is because I am using Material-ui.
return (
<Box className={classes.root} width="100%" padding="5px">
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
id="outlined-basic"
autoComplete="off"
value={phone}
label="phone number"
inputProps={{ maxLength: 255 }}
onChange={handleChange}
required
error={Boolean(errors?.phone)}
helperText={errors?.phone}
variant="outlined"
/>
</Grid>
<Collapse in={phone?.length! > 0}>
<div style={{ width: "100%" }}>
<Button variant="contained">Save</Button>
</div>
</Collapse>
</Grid>
</Box>
);
}
export default FormVal;

Categories

Resources