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

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

Related

Mui TextField placeholder is displayed with value on first refresh

I'm getting this weird behavior that I don't know how to solve, on edit mode of this form if I refresh the page I get a bug where both the value and the placeholder are displayed in the field
-- This is my form component
const Form = () => {
// fetch hook to get the settings data.
const settings = useGetSettingsQuery();
// initialize the useFormik hook with the data just fetched
const form = useSettingsForm({ initialValues: settings.data?.data ?? {} });
return (
<Box>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
fullWidth
name={'id'}
label={'id'}
placeholder={'ticket id'}
variant="outlined"
value={form.values.id}
onChange={form.handleChange}
/>
</Grid>
<Grid item xs={12}>
initial values
<pre>{JSON.stringify({ id: form.initialValues.id }, null, 2)}</pre>
</Grid>
<Grid item xs={12}>
current value values
<pre>{JSON.stringify({ id: form.values.id }, null, 2)}</pre>
</Grid>
</Grid>
</Box>
);
};
-- and this is my hook, right now I've deleted everything in my hook, and this is what's left:
export const useSettingsForm = ({ initialValues }: Props) => {
return useFormik<Partial<ISetting>>({
enableReinitialize: true,
initialValues: {
...initialValues,
},
validationSchema: Yup.object().shape({}),
onSubmit: async (values) => {
console.log('submitted -> ', values);
},
});
};
the current behavior
For my useGetSettings hook, I'm using RTK query to fetch the data and handle the server state, this is the a snippet of apiSlice:
export const settingApiSlice = apiSlice.injectEndpoints({
endpoints(builder) {
return {
getSettings: builder.query<IGetSettingsResp, void>({
query() {
return `/setting`;
},
providesTags: ['setting'],
}),
};
},
});
export const { useGetSettingsQuery } = settingApiSlice;
as you can see in the picture the placeholder text and value are displayed, is there any way to fix this bug, thank you
In Formik, the name of the input ties into the property of it's value inside of form.values. So this:
<TextField
fullWidth
name={'ticket number'}
label={'ticket number'}
placeholder={'ticket number'}
variant="outlined"
value={form.values.id}
onChange={form.handleChange}
/>
Should be this:
<TextField
fullWidth
name="id"
label={'ticket number'}
placeholder={'ticket number'}
variant="outlined"
value={form.values.id}
onChange={form.handleChange}
/>
When you use name={'ticket number'} (or name="ticket number"), it's literally trying to set the value on form.values.ticket number instead of form.values.id, as you want it to be since that's your value.
The id in value={form.values.id} is connected to name="id".

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

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.

I am using mui 5 and for textfields the label for variant="outlined" is not working properly. See the image to properly understand the issue

The image shows the issue which I am facing
I even tried removing the codes of theme inside of theme provider. But still not working.
The component where I am using it is below:
import { useState } from "react";
import {
Grid,
TextField,
} from "#mui/material";
const AccountProfileDetails = (props) => {
const [values, setValues] = useState({
firstName: "Katarina",
lastName: "Smith",
email: "demo#devias.io",
phone: "",
state: "Alabama",
country: "USA",
});
const handleChange = (event) => {
setValues({
...values,
[event.target.name]: event.target.value,
});
};
return (
<form autoComplete="off" noValidate {...props}>
<Grid container spacing={3}>
<Grid item md={6} xs={12}>
<TextField
fullWidth
helperText="Please specify the first name"
label="First name"
name="firstName"
onChange={handleChange}
required
value={values.firstName}
variant="outlined"
/>
</Grid>
</Grid>
</form>
);
};
export default AccountProfileDetails;
Solved it. Actually, I had also installed bootstrap, which was causing the issue, after removing bootstrap, it worked as it was supposed to be.

Prevent useState re-rendering Component

I have a TextField thats updates state with useState on change. Im facing an issue where upon state change the entire component is re-rendering, I expect only the TextField to change.
const Uploader = ({ onUploadComplete }) => {
const [fields, setFields] = useState({});
const handleName = (event) => {
const { name, value } = event.target;
setFields((fields) => ({
...fields,
[name]: { value: value },
}
<React.Fragment>
<Grid item xs={4}>
<Card>{console.log('Card Media Rendered')}</Card>
</Grid>
<Grid item xs={8}>
<FormControl fullWidth>
<TextField
value={fields[file.id]?.value || ''}
onChange={handleName}
/>
</FormControl>
</Grid>
</React.Fragment>
}
You need to move the useState inside the functional component like this:
const Uploader = ({ onUploadComplete }) => {
const [fields, setFields] = useState({});
const handleName = (event) => {
const { name, value } = event.target;
setFields((fields) => ({
...fields,
[name]: { value: value },
}))
}
return (
<React.Fragment>
<Grid item xs={4}>
<Card>{console.log('Card Media Rendered')}</Card>
</Grid>
<Grid item xs={8}>
<FormControl fullWidth>
<TextField
value={fields[file.id]?.value || ''}
onChange={handleName}
/>
</FormControl>
</Grid>
</React.Fragment>
)
}
Your component did rerender because the variable that it depends on, wasn't in the react scope. So it couldn't trigger the update only to the Textfield

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