React Autocomplete with Material UI - javascript

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
/>

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;

How to add a validation from the select renderer using the rsuite in react

const [application, setApplication] = useState([])
const [app, setApp] = useState([
{
id: null,
code: null,
name: null
}
]);
useEffect(() => {
let ignore = false;
(async function load() {
let response = await getAllData();
if (!ignore) setApplication(response['data'])
})()
return () => ignore = true;
},[]);
{
label: (
<div className="flex items-center">
<label className="flex-1">Application</label>
<div className="text-right">
<ButtonGroup>
<IconButton icon={<Icon icon="plus" />} onClick={() => appendApp()} />
<IconButton onClick={() => removeApp()} size="md" icon={<Icon icon="minus" />} style={{ display: app.length > 1 ? 'inline-block' : 'none' }} />
</ButtonGroup>
</div>
</div>
),
name: 'applications',
renderer: (data) => {
const { control, register, errors } = useFormContext();
return (
<div className="flex flex-col w-full">
{
app.map((item, index) => (
<div key={index} className="flex flex-col pb-2 -items-center">
<div className="flex pb-2 w-full">
<SelectPicker
placeholder="Select Application"
data={application['data']}
labelKey="name"
valueKey="code"
style={{ width: '100%' }}
disabledItemValues={Array.isArray(control.getValues()['applications']) ? control.getValues()['applications'].map(x => x.id) : []}
onChange={(value) => control.setValue('applications', _setApp(value, index, 'code'))}
value={control.getValues()['applications']?.code}
/>
</div>
</div>
))
}
</div>
)
}
const appendApp = () => {
let i = 0;
for (i = 0; i < noOfApp; i++) {
setApp(arr => [...arr, {
id: null,
code: null,
name: null,
role: null
}]);
return app;
}
}
const removeAppRole = () => {
setApp([...app.slice(0, -1)]);
}
const _setApp = (value, idx, status) => {
app[idx][status] = value;
setApp(app);
return app;
}
How do I add a validation on the select? for example when the select field is empty it should validation that it is required to select. also for example when there's a existing data which is like this:
data = [{
id: 1,
name: 'IOS',
code: 'ios'
}]
and how do I display this data on the select field? cause I have a create and edit.
when I try to edit it doesn't display the value.
I do not use the register, I prefer to use Controller, for these cases it is more practical, in the v7 of react-hook-form, see this example:
My select component:
import { ErrorMessage } from "#hookform/error-message";
import { IonItem, IonLabel, IonSelect, IonSelectOption } from "#ionic/react";
import { FunctionComponent } from "react";
import { Controller } from "react-hook-form";
import React from "react";
const Select: FunctionComponent<Props> = ({
options,
control,
errors,
defaultValue,
name,
label,
rules
}) => {
return (
<>
<IonItem className="mb-4">
<IonLabel position="floating" color="primary">
{label}
</IonLabel>
<Controller
render={({ field: { onChange, onBlur, value } }) => (
<IonSelect
value={value}
onIonChange={onChange}
onIonBlur={onBlur}
interface="action-sheet"
className="mt-2"
>
{options ? options.map((opcion) => {
return (
<IonSelectOption value={opcion.value} key={opcion.value}>
{opcion.label}
</IonSelectOption>
);
}):""}
</IonSelect>
)}
control={control}
name={name}
defaultValue={defaultValue}
rules={rules}
/>
</IonItem>
<ErrorMessage
errors={errors}
name={name}
as={<div className="text-red-600 px-6" />}
/>
</>
);
};
export default Select;
Use the component in other component:
import Select from "components/Select/Select";
import { useForm } from "react-hook-form";
import Scaffold from "components/Scaffold/Scaffold";
import React from "react";
let defaultValues = {
subjectId: "1"
};
const options = [
{
label: "Option1",
value: "1",
},
{
label: "Option2",
value: "2",
},
];
const ContactUs: React.FC = () => {
const {
control,
handleSubmit,
formState: { isSubmitting, isValid, errors },
} = useForm({
defaultValues: defaultValues,
mode: "onChange",
});
const handlerSendButton = async (select) => {
console.log(select);
};
const rulesSubject = {
required: "this field is required",
};
return (
<Scaffold>
<Scaffold.Content>
<h6 className="text-2xl font-bold text-center">
Contact us
</h6>
<Select
control={control}
errors={errors}
defaultValue={defaultValues.subjectId}
options={options}
name="subjectId"
label={"Subject"}
rules={rulesSubject}
/>
</Scaffold.Content>
<Scaffold.Footer>
<Button
onClick={handleSubmit(handlerSendButton)}
disabled={!isValid || isSubmitting}
>
Save
</Button>
</Scaffold.Footer>
</Scaffold>
);
};
In this case i use Ionic for the UI but you can use MaterialUI, ReactSuite o other framework, its the same.
I hope it helps you, good luck.
EDIT
A repository : Ionic React Select Form Hook
A codeSandBox: Ionic React Select Form Hook

How can I add onChange to my onClick function?

Been struggling to try and solve this one,
I have a <form> that is wrapped around a Controller using a render. The render is a list of <Chip>'s. I want the list of chips to act as a field, each user toggled chip would act as the data inputted to the form, based on the label value of a chip. The expected data can be either one string or an array of strings based on how many <Chips> are toggled.
I've tried to replicate examples that use <Checkbox>, but I wasn't able to re-create the component.
I have a state of total chips toggled, which re-produces the data I would like to submit via my button. Is there a way to pass this state to my form data?
I'm currently only able to submit empty data through my form because my chip values and form data are not linked up correctly.
To solve this I need to add onChange to my onClick parameter for <Chip>, I've tried doing this:
onClick={(value, e) => { handleToggle(value); onChange(value) }}
But that does not work?
Here is my Form
<form noValidate onSubmit = { handleSubmit(onSubmit) }>
<Controller
render={({ onChange ,...props }) => (
<Paper component="ul" className={classes.root}>
{stocklist.map((value, index) =>
{
return (
<li key={index}>
<Chip
name="stock_list"
variant="outlined"
label={value}
key={index}
color={checked.includes(value) ? 'secondary' : 'primary'}
onClick={handleToggle(value)}
className={classes.chip}
ref={register}
/>
</li>
);
})}
</Paper>
)}
name="stock_list"
control={control}
defaultValue={[]}
onChange={([, data]) => data}
/>
{checked && checked.length > 0 &&
<Fab
color="primary"
aria-label="delete"
type="submit"
>
<DeleteIcon />
</Fab>
}
</form>
Here is how I toggle the chips, and create a state checked which holds the values for every toggled chip
const { register, control, handleSubmit } = useForm();
const [checked, setChecked] = React.useState([]);
const handleToggle = (value) => () => {
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
};
Here is my onSubmit function
const onSubmit = (data, e) =>
{
console.log(data);
axiosInstance
.patch('url', {
stock_list: data.stock_list,
})
.then((res) =>
{
console.log(res);
console.log(res.data);
});
};
Check it out https://codesandbox.io/s/cocky-roentgen-j0pcg Please toggle one of the chips, and then press the button that appears. If you then check the console, you will notice an empty form array being submitted.
I guess this will work for you
import React, { useState } from "react";
import { Chip, Paper, Fab, Grid } from "#material-ui/core/";
import { makeStyles } from "#material-ui/core/styles";
import { Controller, useForm } from "react-hook-form";
import DeleteIcon from "#material-ui/icons/Delete";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "center",
flexWrap: "wrap",
listStyle: "none",
padding: theme.spacing(0.5),
margin: 0,
"#media (max-width: 600px)": {
overflowY: "auto",
height: 200
}
},
chip: {
margin: theme.spacing(0.5)
}
}));
export default function app() {
const { register, control, handleSubmit } = useForm();
const classes = useStyles();
const [checked, setChecked] = React.useState([]);
const stocklist = ["AAPL", "AMD", "TSLA"];
const onSubmit = (data, e) => {
console.log("data.stock_list");
console.log(data.stock_list);
console.log("data.stock_list");
};
const handleToggle = (value) => {// <== I changed this part
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
return newChecked;// <== I changed this part
};
return (
<>
{(!stocklist || stocklist.length === 0) && (
<p style={{ textAlign: "center" }}>Your Bucket is empty...</p>
)}
{stocklist && stocklist.length > 0 && (
<Grid>
<form noValidate onSubmit={handleSubmit(onSubmit)}>
<Controller
name="stock_list"// <== I changed this part
render={({ onChange, ...props }) => (
<Paper component="ul" className={classes.root}>
{stocklist.map((value, index) => {
return (
<li key={index}>
<Chip
// name="stock_list"// <== I changed this part
variant="outlined"
label={value}
key={index}
color={
checked.includes(value) ? "secondary" : "primary"
}
onClick={(e) => {
onChange(handleToggle(value));// <== I changed this part
}}
className={classes.chip}
ref={register}
/>
</li>
);
})}
</Paper>
)}
control={control}
defaultValue={{}}
onChange={([, data]) => data}
/>
{checked && checked.length > 0 && (
<Fab color="primary" aria-label="delete" type="submit">
<DeleteIcon />
</Fab>
)}
</form>
</Grid>
)}
</>
);
}

Disable backspace deleting of options in Autocomplete

I want to use an Autocomplete (from the Material-Ui library) component from material ui to select multiple options, and those options should not be able to be removed (directly) by the user.
The problem I'm facing is that the user can delete the options from the Autocomplete if they focus the component and press backspace as if they are deleting text.
Code
This is the component I'm using:
<Autocomplete multiple
options={options}
getOptionLabel={option => option.title}
renderInput={params =>
<TextField {...params} label="Autocomplete" variant="outlined" />
}
onChange={this.onAutocompleteChange.bind(this)}
getOptionSelected={(option: Option, value: Option) => option.value === value.value}
filterSelectedOptions={true}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option.value} label={option.title} color="primary" />
))
}
disableClearable={true}
/>
What I tried
Disabling the TextField from the renderInput prop with disable={true} has no effect.
Adding InputProps={{ disabled: true }} or InputProps={{ readOnly: true }} to TextField disables the Autocomplete completely.
Adding ChipProps={{ disabled: true }} to the Autocomplete has no effect.
Thanks for reading!
To control this aspect, you need to use a controlled approach to the Autocomplete's value as demonstrated in this demo.
In the documentation for the onChange prop you will find the following:
onChange Callback fired when the value changes.
Signature:
function(event: object, value: T | T[], reason: string) => void
event: The event source of the callback.
value: The new value of the component.
reason: One of "create-option", "select-option", "remove-option", "blur" or "clear".
The third argument to onChange is the "reason" for the change. In your case, you want to ignore all of the "remove-option" changes:
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
Here's a full working example:
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Chip from "#material-ui/core/Chip";
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState([options[0]]);
const [inputValue, setInputValue] = React.useState("");
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple
value={value}
disableClearable
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
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" />
)}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option} label={option} color="primary" />
))
}
/>
</div>
);
}

MaterialUI TextField lag issue. How to improve performance when the form has many inputs?

I encountered an annoying problem with Textfield using MateriaUI framework. I have a form with many inputs and it seems to be a bit laggy when typing or deleting values inside the fields. In other components when there are like 2 or 3 inputs there's no lag at all.
EDIT: The problem seems to be with my onChange handler.
Any help is much appreciated. Thanks in advance.
This is my custom input code:
import React, { useReducer, useEffect } from 'react';
import { validate } from '../utils/validators';
import TextField from '#material-ui/core/TextField';
import { ThemeProvider, makeStyles, createMuiTheme } from '#material-ui/core/styles';
import { green } from '#material-ui/core/colors';
const useStyles = makeStyles((theme) => ({
root: {
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
const inputReducer = (state, action) => {
switch (action.type) {
case 'CHANGE':
return {
...state,
value: action.val,
isValid: validate(action.val, action.validators)
};
case 'TOUCH': {
return {
...state,
isTouched: true
}
}
default:
return state;
}
};
const Input = props => {
const [inputState, dispatch] = useReducer(inputReducer, {
value: props.initialValue || '',
isTouched: false,
isValid: props.initialValid || false
});
const { id, onInput } = props;
const { value, isValid } = inputState;
useEffect(() => {
onInput(id, value, isValid)
}, [id, value, isValid, onInput]);
const changeHandler = event => {
dispatch({
type: 'CHANGE',
val: event.target.value,
validators: props.validators
});
};
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const classes = useStyles();
return (
<ThemeProvider theme={theme}>
<TextField
className={classes.input}
InputProps={{
className: classes.root
}}
InputLabelProps={{
className: classes.label
}}
id={props.id}
type={props.type}
label={props.label}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
title={props.title}
error={!inputState.isValid && inputState.isTouched}
helperText={!inputState.isValid && inputState.isTouched && props.errorText}
/>
</ThemeProvider>
);
};
export default Input;
In addition to #jony89 's answer. You can try 1 more workaround as following.
On each keypress (onChange) update the local state.
On blur event call the parent's change handler
const Child = ({ parentInputValue, changeValue }) => {
const [localValue, setLocalValue] = React.useState(parentInputValue);
return <TextInputField
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
onBlur={() => changeValue(localValue)} />;
}
const Parent = () => {
const [valMap, setValMap] = React.useState({
child1: '',
child2: ''
});
return (<>
<Child parentInputValue={valMap.child1} changeValue={(val) => setValMap({...valMap, child1: val})}
<Child parentInputValue={valMap.child2} changeValue={(val) => setValMap({...valMap, child2: val})}
</>
);
}
This will solve your problems if you do not want to refactor the existing code.
But the actual fix would be splitting the state so that update in the state of child1 doesn't affect (change reference or mutate) state of child2.
Make sure to extract all constant values outside of the render scope.
For example, each render you are providing new object to InputLabelProps and InputProps which forces re-render of child components.
So every new object that is not must be created within the functional component, you should extract outside,
That includes :
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
Also you can use react memo for function component optimization, seems fit to your case.
I managed to get rid of this lagging effect by replacing normal <TextField /> by <Controller /> from react-hook-form.
Old code with Typing Lag
<Grid item xs={12}>
<TextField
error={descriptionError.length > 0}
helperText={descriptionError}
id="outlined-textarea"
onChange={onDescriptionChange}
required
placeholder="Nice placeholder"
value={description}
rows={4}
fullWidth
multiline
/>
</Grid>
Updated Code with react-hook-form
import { FormProvider, useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
const methods = useForm({
resolver: yupResolver(validationSchema)
});
const { handleSubmit, errors, reset } = methods;
const onSubmit = async (entry) => {
console.log(`This is the value entered in TextField ${entry.name}`);
};
<form onSubmit={handleSubmit(onSubmit)}>
<Grid item xs={12}>
<FormProvider fullWidth {...methods}>
<DescriptionFormInput
fullWidth
name="name"
placeholder="Nice placeholder here"
size={matchesXS ? 'small' : 'medium'}
bug={errors}
/>
</FormProvider>
</Grid>
<Button
type="submit"
variant="contained"
className={classes.btnSecondary}
startIcon={<LayersTwoToneIcon />}
color="secondary"
size={'small'}
sx={{ mt: 0.5 }}
>
ADD
</Button>
</form>
import React from 'react';
import PropTypes from 'prop-types';
import { Controller, useFormContext } from 'react-hook-form';
import { FormHelperText, Grid, TextField } from '#material-ui/core';
const DescriptionFormInput = ({ bug, label, name, required, ...others }) => {
const { control } = useFormContext();
let isError = false;
let errorMessage = '';
if (bug && Object.prototype.hasOwnProperty.call(bug, name)) {
isError = true;
errorMessage = bug[name].message;
}
return (
<>
<Controller
as={TextField}
name={name}
control={control}
defaultValue=""
label={label}
fullWidth
InputLabelProps={{
className: required ? 'required-label' : '',
required: required || false
}}
error={isError}
{...others}
/>
{errorMessage && (
<Grid item xs={12}>
<FormHelperText error>{errorMessage}</FormHelperText>
</Grid>
)}
</>
);
};
With this use of react-hook-form I could get the TextField more responsive than earlier.

Categories

Resources