Uncaught Error: Maximum update depth exceeded. error in functional component - reactjs

I created a separate sharable MUI react Autocomplete component for having formik state as below
import * as React from 'react';
import Autocomplete from '#mui/material/Autocomplete';
import TextField from '#mui/material/TextField';
import { useDispatch, useSelector } from 'react-redux';
import CircularProgress from '#mui/material/CircularProgress';
import { Formik, useField, Form, FieldArray } from "formik";
export default (props) => {
const [field, meta, helpers] = useField(props);
const ERROR_TEXT = meta.error && meta.touched ? meta.error : '';
// alert(props.inputValue)
return (
<Autocomplete
{...props}
{...field}
helperText={ERROR_TEXT}
error={!!ERROR_TEXT}
fullWidth
size="small"
value={field.value}
onChange={(event, newValue) => {
helpers.setValue(newValue);
}}
onInputChange={(event, newInputValue) => {
props.setInputValue(newInputValue);
}}
renderInput={(params) => (
<TextField
{...params}
placeholder={props.label}
label={props.label}
/>
)}
/>
)
}
Now on one of my pages, I imported the above component two times. like below
<Grid item xs={12} sm={6}>
<MyAsyncSelectField
name="fromAirport"
label="Pickup Airport"
multiple
inputValue={inputValue}
setInputValue={setInputValue}
options={options}
/>
</Grid>
{/*------------ To Airport Start ----------------*/}
<Grid item xs={12} sm={6}>
<MyAsyncSelectField
name="toAirport"
label="Destination Airport"
inputValue={inputValue}
setInputValue={setInputValue}
options={options}
/>
</Grid>
Now here's the case, when I create a state for the input query string like this
const [inputValue, setInputValue] = React.useState("");
React.useEffect(() => {
dispatch(search_airport_list_action(inputValue))
}, [inputValue]);
and pass it as a props in both the custom select components, I get the error as I've mentioned in my title.
But if I create separate states to pass them as props to the respective custom select components, it works like charm. No error at all.
const [inputValue1, setInputValue1] = React.useState("");
const [inputValue2, setInputValue2] = React.useState("");
React.useEffect(() => {
dispatch(search_airport_list_action(inputValue))
}, [inputValue1, inputValue2]);
<Grid item xs={12} sm={6}>
<MyAsyncSelectField
name="fromAirport"
label="Pickup Airport"
multiple
inputValue={inputValue1}
setInputValue={setInputValue1}
options={options}
/>
</Grid>
{/*------------ To Airport Start ----------------*/}
<Grid item xs={12} sm={6}>
<MyAsyncSelectField
name="toAirport"
label="Destination Airport"
inputValue={inputValue2}
setInputValue={setInputValue2}
options={options}
/>
</Grid>
So I'm curious to know why can't I use the same state for the query string? Why am I required to create two separate states for the input query string?
In the console, I'm getting the below error message which is totally absurd to me because I'm using the Functional component and useEffect hook instead of class components and lifecycle methods:
This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Related

React Hook Form & Material UI: errors are undefined

I'm using React Hook Form v7 with Material UI v5. I have a simple form component as shown below. I have marked the TextField as required, however, the error state and helper text are never shown when the value of the field is empty. The value of errors.title is always undefined.
What am I doing wrong here? How can I get the React Hook Form validation working, i.e. errors?
import React from 'react';
import Grid from '#mui/material/Grid';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
import { useForm, Controller } from 'react-hook-form';
import backEndApi from '../api/backEndApi';
const UserForm = function () {
const { control, handleSubmit, formState: { errors }, getValues } = useForm();
const onFormSubmit = (event) => {
event.preventDefault();
backEndApi.post(getValues());
};
return (
<Grid container alignItems="center" justify="center" direction="column" paddingTop="10%">
<form onSubmit={handleSubmit(onFormSubmit)}>
<Grid container alignItems="center" justify="center" direction="column" spacing={5}>
<Grid item>
<Controller
name="title"
control={control}
rules={{ required: true }}
render={({ field }) => {
return (
<TextField
{...field}
id="title"
label="Title"
required
error={errors.title}
helperText={errors.title && 'Title is required'}
/>
);
}}
/>
</Grid>
<Grid item>
<Button id="job-submit" color="primary" type="submit" variant="contained">
Create Job
</Button>
</Grid>
</Grid>
</form>
</Grid>
);
};
export default UserForm;
I think rules conflicts with required of TextField.
I confirmed it works. (https://codesandbox.io/s/practical-chebyshev-9twrle?file=/src/userForm.js)
<Controller
name="title"
control={control}
rules={{ required: true }}
render={({ field }) => {
return (
<TextField
{...field}
id="title"
label="Title"
error={Boolean(errors.title)}
helperText={errors.title && "Title is required"}
/>
);
}}
/>
Also you don't have to wrap with Controller for such a simple case.
Example: https://codesandbox.io/s/practical-chebyshev-9twrle?file=/src/userFormWithoutController.js

How can I use useForm's register with dynamic parameter?

I want to create a form by using react-hook-form, but I am having trouble with it. As I found, there was a big change at v7 so the code I used as a reference is not working. I tried to modify it, and I figured out the problem is with the registering, but I cannot pass that name parameter dynamically.
My main component.
import React from 'react';
import i18next from 'i18next';
import { Button, Grid, Typography } from '#material-ui/core';
import { useForm, FormProvider } from 'react-hook-form';
import { Link } from 'react-router-dom';
import FormInput from './CustomTextField'
const AddressForm = ({ next }) => {
const methods = useForm();
return (
<>
<Typography variant="h6" gutterBottom>
{i18next.t('shipping_address')}
</Typography>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit((data) => {
console.log(data);
next({ ...data })})}>
<Grid container spacing={3}>
<FormInput required register={methods.register} name='lastName' label={i18next.t('last_name')} />
<FormInput required register={methods.register} name='firstName' label={i18next.t('first_name')} />
<FormInput required register={methods.register} name='email' label={i18next.t('mail')} />
<FormInput required register={methods.register} name='zip' label={i18next.t('zip_code')} />
<FormInput required register={methods.register} name='city' label={i18next.t('city')} />
<FormInput required register={methods.register} name='address1' label={i18next.t('address_1')} />
</Grid>
<br />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button component={Link} to="/cart" variant="outlined">
{i18next.t('back_to_cart')}
</Button>
<Button type="submit" variant="contained" color="primary">
{i18next.t('next_step')}
</Button>
</div>
</form>
</FormProvider>
</>
)
}
export default AddressForm
CustomTextField component:
import React from 'react';
import { TextField, Grid } from '#material-ui/core';
import { useFormContext, Controller } from 'react-hook-form';
const CustomTextField = ({ name, label, register, required}) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={name}
render = {(field) => (
<TextField
{...register({name})} // <--- It is not working like this
label={label}
required={required}
/>
)}
/>
</Grid>
)
}
export default CustomTextField
By the doc: https://react-hook-form.com/api/useform/register
it takes the input field's name as a parameter, if I pass it in as a string, it works fine. How can I pass the name's value as a parameter to the register() function?
The problem is you're mixing RHF's <Controller /> and register. As Mui's <TextField /> is an external controlled component you should use <Controller />. Check the docs here for more info.
const CustomTextField = ({ name, label, register, required}) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={name}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
label={label}
required={required}
/>
)}
/>
</Grid>
)
}
If you really want to use register here, you have to remove the wrapping <Controller /> and pass a name as a string instead as an object like you are doing right now. But i would recommend to use <Controller /> as with register you are losing the functionality of setting up the correct ref for your <TextField /> input element as it is linked via the inputRef prop instead of using ref which RHF register uses.
const CustomTextField = ({ name, label, register, required}) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<TextField
{...register(name)}
label={label}
required={required}
/>
</Grid>
)
}

react-hook-form Controller issues

Hi im trying to do one form with react-hook-form and material-ui. I don't want to write Controller every time for all TextFields. Because of that i declare it in another file and call it in my form but its not working i didn't understand why, because in some videos that i watched is working. What is the problem and how i can fix it ?
Form Field
import React from 'react'
import { TextField, Grid } from '#material-ui/core'
import { useForm, Controller, useFormContext } from 'react-hook-form'
const FormField = ({name, label}) => {
const { control } = useForm()
return (
<Grid item xs={12} sm={6} >
<Controller
render = {({field}) =>
<TextField
{...field}
label={label} required
/>}
name={name}
control = {control}
defaultValue=""
/>
</Grid>
)
}
export default FormField
Adress Form
import React from 'react'
import { InputLabel, Select, MenuItem, Button, Grid, Typography, TextField } from '#material-ui/core'
import { useForm, FormProvider, Controller } from 'react-hook-form'
import FormField from './FormField'
import { Link } from 'react-router-dom'
const AdressForm = ({next}) => {
const {handleSubmit, control} = useForm()
return (
<>
<Typography variant="h6" gutterBottom>Shipping Address </Typography>
<form onSubmit={handleSubmit((data) => console.log(data) )}>
<Grid container spacing={3}>
<FormField name='firstName' label='First Name' required='required'/>
<FormField name='lastName' label='Last Name' />
<FormField name='email' label='Email' />
<FormField name='phoneNumber' label='Phone Number' />
</Grid>
<br/>
<div style={{ display: 'flex', justifyContent: 'space-between'}}>
<Button component={Link} to="/cart" variant="outlined">Back to Cart</Button>
<Button type="submit" variant="contained" color="primary">Next</Button>
</div>
</form>
</>
)
}
export default AdressForm
You must use one useForm hook for each form, in your code, you call useForm in every Field components, creating multiple independent form states, which leads to unexpected result.
What you need to do is to call useForm in the parent element and pass the dependencies (register, formState, error...) down the child components, so your form can have one unified state. If you have a deeply nested components, you can use useFormContext to pass the form context to the nested children easily:
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input {...register("test")} />;
}

React re-renders all form controls on every text input change

I am using react with typescript and functional component and Material UI, I have a large form. Small portion is given below.
import React, { useState } from 'react';
import { Grid, TextField } from '#material-ui/core';
const PublicProfileTest = () => {
const [state, setState] = useState<{ town: string; county: string }>({
town: '',
county: '',
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
<>
<Grid container justify='center' alignItems='center' spacing={2}>
<Grid item xs={12} md={6}>
<TextField
variant='outlined'
fullWidth
id='town'
label='Town'
name='town'
autoComplete='town'
placeholder='Town *'
value={state.town || ''}
onChange={handleChange}
/>
</Grid>
</Grid>
<Grid container justify='center' alignItems='center' spacing={2}>
<Grid item xs={12} md={6}>
<TextField
variant='outlined'
fullWidth
id='county'
label='County'
name='county'
autoComplete='county'
placeholder='County'
value={state.county || ''}
onChange={handleChange}
/>
</Grid>
</Grid>
</>
);
};
export default PublicProfileTest;
I have used React dev tools in chrome to check which dome elements are re-rendering.
When i change the town or county input, the whole component gets updated. It's not a problem here but in a large form that i am building, it degrades the performance.
I expect react to re-render only the changed portion of the dom, why it is updating the whole component.
Any idea and solution to make react re-render the changed component only.
Regards,
Iaq

useEffect to trigger address verification based on filled out inputs

I need to run the address verification api call .During these scenarios
*when all associated fields are filled out.
*when above call is done , it should be calling when any of the fields value has
changed.
I tried triggering giving all the fields as dependencies in the useEffects second parameter array,but its calls the effect repeatedly
const Address = props => {
const { countries, usStates, caStates, title, binding, formik } = props;
var zip = formik.values.Client.Address.Residential.Zip;
var city = formik.values.Client.Address.Residential.City;
var line1 = formik.values.Client.Address.Residential.Address1;
var country = formik.values.Client.Address.Residential.Country;
var state = formik.values.Client.Address.Residential.State;
useEffect(() => {
if (zip && city && country && state && country) {
console.log("call address verification")
}
}, [zip, city, country, state, country])
return (
<TransactConsumer>
{({ userSave, getFormApi, formFunction, formStart }) => {
return (
<Fragment>
{title && <Grid item xs={12}>
<Typography variant="body1">{title}</Typography>
</Grid>}
<Grid item xs={12}>
<SectionField
title={title}
name={binding + ".Country"}
required
defaultValue={{ label: "United States", value: "US" }}
label="Country"
suggestions={countries}
component={MuiReactSelect}
/>
</Grid>
<Grid item xs={12}>
<SectionField
title={title}
name={binding + ".Address1"}
required
label="Address Line 1"
fullWidth
component={TextField}
/>
</Grid>
<Grid item xs={12}>
<SectionField
title={title}
name={binding + ".Address2"}
label="Address Line 2"
fullWidth
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={6}>
<SectionField
title={title}
name={binding + ".City"}
required
label="City"
fullWidth
component={TextField}
/>
</Grid>
<Grid item xs={12} sm={4}>
<SectionField
title={title}
name={binding + ".State"}
required
label={isUsCountry() ? "State" : isCaCountry() ? "Province" : "State / Province"}
fullWidth
component={ MuiReactSelect}
/>
</Grid>
<Grid item xs={12} sm={2}>
<SectionField
title={title}
name={binding + ".Zip"}
required
label="Zip"
fullWidth
component={TextField}
/>
</Grid>
</Fragment >
)
}}
</TransactConsumer>
)
}
====SectionField====
import React, { useEffect } from 'react'
import useSectionData from './useSectionData';
import { Field } from 'formik';
import PropTypes from 'prop-types';
const SectionField = ({ children, title, name, ...rest }) => {
const { addField } = useSectionData();
useEffect(() => {
addField(title, name)
}, [title, name])
return (
<Field name={name} {...rest}>
{children}
</Field>
)
}
SectionField.propTypes = {
title: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node),
PropTypes.node]),
};
export default SectionField
Section Field component is wrapper for the formik Field Element.
what would be the best way to make sure I can call only after all the
fields have been filled out . Right now , the it gets called for every
click , like lets say zip is 60000 it calls useEffect 10 times
what can be an other option rather than using formik values to
as dependencies.Any best practices could be helpful. Thanks .
You can have a variable you keep in state that indicates whether all of the fields have been filled out or not. You'd set that variable in the current useEffect that you have. It'd look something like this:
const [ allFieldsFilled, setAllFieldsFilled ] = useState(false);
useEffect(() => {
setAllFieldsFilled(zip && city && country && state && country)
}, [zip, city, country, state, country])
Once you have an indication of whether the fields have all been filled out or not, you could have a second useEffect that'd be responsible for triggering the validation (you could maybe combine them into one, but I think separating them would make the intent a bit clearer):
useEffect(() => {
if(allFieldsFilled){
performValidation();
}
}, [zip, city, country, state, country])
To keep yourself from having to type all the fields you want to be triggering the validation, you could do something like this:
const validationFields = [zip, city, country, state];
useEffect(()=>{
//Code
}, [...validationFields])

Resources