Maximum update depth exceeded error in react app - reactjs

I am getting this error when trying to type a password longer than 6 characters in the following react form
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
for each character the user inputs in password/confirm password, I need to validate them.
How can I do this avoiding this infinite loop?
export const Signup: FC<InjectedFormProps<string>> = (props) => {
const {handleSubmit} = props
const {t} = useTranslation()
const [password, setPassword] = useState();
const [confirmPassword, setConfirmPassword] = useState();
const [isValid, setValidity] = useState(false);
const errorMessage = useSelector(getErrorMessage)
const onChangePassword = (event: any) => {
setPassword(event.target.value)
}
const onChangeConfirmPassword = (event: any) => {
setConfirmPassword(event.target.value)
}
const validatePasswords = () => {
if (password && confirmPassword) {
setValidity(password === confirmPassword)
}
}
useEffect(() => {
validatePasswords();
}, [password, confirmPassword, isValid])
const minLength = minLengthPassword(6)
return (
<form onSubmit={handleSubmit}>
<FRow className="full-width">
<FGroup>
<Field validate={minLength} onChange={onChangePassword} name="password" component="input" type="password" placeholder="Password"/>
</FGroup>
</FRow>
<FRow className="full-width">
<FGroup>
<input onChange={onChangeConfirmPassword} name="confirmPassword" type="password" placeholder="Confirm Password"/>
</FGroup>
</FRow>
<FRow>
{isValid ? '' : <Error>{t("auth.lbl_passwords_must_match")}</Error>}
</FRow>
<FullWidthSvgButton disabled={!isValid}>
<span>{t("buttons.btn_sign_up")}</span>
</FullWidthSvgButton>
{errorMessage && (
<Error>{errorMessage}</Error>
)}
</form>
)
}
export const SignupForm = reduxForm<any>({
form: "Signup",
})(Signup)
export default SignupForm

Try to remove isValid from the dependencies.
useEffect(() => {
validatePasswords();
}, [password, confirmPassword])
You should run your setValidity only when password or confirmPassword changed. If you call it when your isValid changed - you are getting the infinite loop (because setValidity changes isValid)

Please update your hooks like the following.
Wrap validatePasswords by useCallback with dependencies of 2 states.
And add this function in dependency of useEffect.
const validatePasswords = useCallback( () => {
if (password && confirmPassword) {
setValidity(password === confirmPassword)
}
}, [password, confirmPassword])
useEffect(() => {
validatePasswords();
}, [validatePasswords])

Related

ReactJS form need to submit 2 times to work

const [name, setName] = useState("");
const [age, setAge] = useState("");
const initialValues = {
name: "",
age: "",
};
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
setIsSubmit(true);
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name is required";
}
if (!values.age) {
errors.age= "Age is required";
}
return errors;
};
const userCreate = async () => {
await api.post("/createuser", {
name,
age,
});
};
return (
<div class="container">
<Form
onSubmit={
Object.keys(formErrors).length === 0 && isSubmit
? userCreate
: handleSubmit
}
>
<Form.Field>
<label>Name</label>
<input
name="name"
onChange={(e) => {
setName(e.target.value);
handleChange(e);
}}
values={formValues.name}
/>
<span className="error-message">{formErrors.name}</span>
</Form.Field>
<Form.Field>
<label>Age</label>
<input
name="age"
onChange={(e) => {
setAge(e.target.value);
handleChange(e);
}}
values={formValues.age}
/>
<p className="error-message">{formErrors.age}</p>
</Form.Field>
<Button type="submit">Submit</Button>
</Form>
</div>
);
I'm trying to use axios to do POST method for creating user.
I got everything works fine but there's one small problem but I don't know how to fix.
The problem is that I always need to submit the form 2 times to make the POST request. There's nothing happen in the first submit, but it will work in the second submit.
Does anyone know what's wrong with my code?
Edited
According to #DBS solution.
I'm trying to follow the steps but now the form can't submit anymore. Can someone let me know if I missed something?
const [name, setName] = useState("");
const [age, setAge] = useState("");
const initialValues = {
name: "",
age: "",
};
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
if (!Object.keys(formErrors).length && !isSubmitting) {
setFormErrors(validate(formValues));
} else {
userCreate();
setisSubmitting(true);
}
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name is required";
}
if (!values.age) {
errors.age= "Age is required";
}
return errors;
};
const userCreate = async () => {
await api.post("/createuser", {
name,
age,
});
};
return (
<div class="container">
<Form
onSubmit={handleSubmit}
>
<Form.Field>
<label>Name</label>
<input
name="name"
onChange={(e) => {
setName(e.target.value);
handleChange(e);
}}
values={formValues.name}
/>
<span className="error-message">{formErrors.name}</span>
</Form.Field>
<Form.Field>
<label>Age</label>
<input
name="age"
onChange={(e) => {
setAge(e.target.value);
handleChange(e);
}}
values={formValues.age}
/>
<p className="error-message">{formErrors.age}</p>
<
The issue here is your isSubmit, it is required to be true for userCreate to be called:
onSubmit={
Object.keys(formErrors).length === 0 && isSubmit
? userCreate
: handleSubmit
}
But it starts as false:
const [isSubmit, setIsSubmit] = useState(false);
And is only ever updated when handleSubmit is called (which, confusingly, is only called when the validation fails)
So your current code does this:
isSubmit is false
Submit is clicked, handleSubmit is called and isSubmit is set to true
Submit is clicked again, now isSubmit is true it will call userCreate
To solve this, there are a few different approaches, but I would:
Move all the submit handler logic into onSubmit={handleSubmit} (To keep things clear)
Inside there, do your error length check (0 error) and isSubmit (Which I would probably rename to isSubmitting for clarity, and make sure it's false) (E.g. !Object.keys(formErrors).length && !isSubmitting)
If there are errors, show the appropriate message (Leaving isSubmitting as false)
If not, call userCreate (And set isSubmitting to true)
Lastly, if this can be submitted multiple times, add an effect/callback/then to reset isSubmitting once the call is complete.
Are you using the isSubmitting flag for something? if not below might be work for you.
If there is no error, calling the create method
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
if(Object.keys(formErrors).length === 0) {
userCreate();
}
};
if isSubmitting is used to check the submit or create in progress
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
if(Object.keys(formErrors).length === 0) {
setIsSubmitting(true);
userCreate();
}
};
The flag isSubmitting should be set as false on API is success or failed
setIsSubmitting(false)

Returning 0 when transforming string information inside a useState into parseFloat [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 6 months ago.
import * as C from './styles';
import AddButton from '../../components/AddButton';
import { useEffect, useState } from 'react';
import { useApi } from '../../hooks/useApi';
const AddProduct = () => {
const api = useApi();
const [apiCategories, setApiCategories] = useState<any[]>([]);
const [isCreate, setIsCreate] = useState<boolean>(false);
const [name, setName] = useState<string>('');
const [price, setPrice] = useState<number>(0);
const [desc, setDesc] = useState<string>('');
const [stock, setStock] = useState<number>(0);
const [categories, setCategories] = useState<string>('');
const [priceSTR, setPriceSTR] = useState<string>('');
const [stockSTR, setStockSTR] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
useEffect(() => {
const category = async () => {
const categories = await api.getCategories();
setApiCategories(categories);
}
category();
}, []);
const onSubmit = async () => {
try {
setLoading(true);
const priceFLT = parseFloat(priceSTR);
const stockINT = parseInt(stockSTR);
setPrice(priceFLT);
setStock(stockINT);
setLoading(false);
await api.postProduct(name, price, desc, stock, categories);
setIsCreate(true);
setError(false);
setName('');
setDesc('');
setPriceSTR('');
setStockSTR('');
} catch (err) {
setError(true);
console.log(err);
}
}
return (
<C.Container>
<C.Post>
<C.Input
placeholder='Product name'
value={name}
onChange={e => setName(e.target.value)}
/>
</C.Post>
<C.Post>
<C.Desc
placeholder='Simple desc...'
value={desc}
onChange={e => setDesc(e.target.value)}
/>
</C.Post>
<C.Post>
<C.Input
placeholder='Price'
value={priceSTR}
onChange={e => setPriceSTR(e.target.value)}
/>
</C.Post>
<C.Post>
<C.Input
placeholder='Stock'
value={stockSTR}
onChange={e => setStockSTR(e.target.value)}
/>
</C.Post>
<C.Post>
<C.Categories
onChange={(e) => setCategories(e.target.value)}
>
<option value="Todas categorias">Choose a category</option>
{apiCategories.map(category => {
return (
<option value={`${category.name}`}>{category.name}</option>
)
})}
</C.Categories>
</C.Post>
<C.Add>
<AddButton
children='Send'
type='submit'
onClick={onSubmit}
/>
</C.Add>
{isCreate ? (
<p id='check'>Product Created!</p>
) : null}
{error ? (
<p id='error'>Error!</p>
) : null}
</C.Container>
)
}
export default AddProduct
My real purpose is to get the information from these inputs and send it to the "useApi" hook to validate the registration of a new product. However, the "price" and "stock" states in the API must be sent as float and int respectively, but I would like to get the information in string to then transform to float and int, and then send it to the API. It's what I try to do with
const priceFLT = parseFloat(priceSTR);
const stockINT = parseInt(stockSTR);
setPrice(priceFLT);
setStock(stockINT);
I can ship, but the stock and price always ends up as "0". How can I solve this problem?
ps: I would like to get this information in string in the input to be able to use placeholder and to be able to use "." to set a price.
You don't actually need to update a state for price or stock to provide it to your api call
...
const priceFLT = parseFloat(priceSTR);
const stockINT = parseInt(stockSTR);
setLoading(false);
await api.postProduct(name, priceFLT, desc, stockINT, categories);
...
setPrice or setStock are asynchronous, so the update is not immediate, that is partially why you end up with 0 (the default value) when you try to immediately use price or stock variables (another more complex reason is due to the way useState and reference work, once updated the price variable inside the onSubmit that has been called is not the same as the price variable that has been updated)

State not updating after setting state

I'm creating a contact form, and attempting to setState for each field, then pass that value to an email form.
When I console.log, I'm not getting any output. What am I doing wrong here?
Should I be using useEffect? From what I understand, useEffect is called whenever I set state, so I shouldn't need to. Is there something else I'm missing or doing wrong?
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
setName(document.getElementById('name').value)
setEmail(document.getElementById('email').value)
setSubject(document.getElementById('subject').value)
setMessage(document.getElementById('message').value)
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
Set state is an asynchronous operation, so log the state after setting it, will log the old value.
If you want to sure that the value has been setten correctly, you need to use setState.
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
setShowSuccessMessage(prev => {
console.log(prev)
return prev
})// log the 'Contact form submitted'
setShowFailureMessage(prev => {
console.log(prev)
return prev
})// log false
And setting state, in this case, will not make the component re-render as you return the same value.
The reason is that setXxx methods are async. If you click submit 10 times fast enough, you will see different results.
You have two options:
not preferred: If you don't use 'controlled' input (you are not using it now - in input elements, you are not using things like 'value={name}'). then you don't need the state at all.
Use controlled input. You can check document on reactjs org.
Simply put,
useState
on the input tag, add value={name} onChange={changeHandler},
in changHandler, setState
in formSubmit, use the value in state
There is a section on forms on this excellent tutorial:
Bob Ziroll free React Course
You are using react so you need to set data and use those data after that i have updated your code it should work for you. i.e. you need to set value in onChange function as below:-
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
const style = {};
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' onChange={(e) => setName(e.target.value)} />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' onChange={(e) => setEmail(e.target.value)} />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' onChange={(e) => setSubject(e.target.value)} />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
onChange={(e) => setMessage(e.target.value)}
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
And if you want same for phone you can also set data for the same and use after that.

persist state after page refresh in React using local storage

What I would like to happen is when displayBtn() is clicked for the items in localStorage to display.
In useEffect() there is localStorage.setItem("localValue", JSON.stringify(myLeads)) MyLeads is an array which holds leads const const [myLeads, setMyLeads] = useState([]); myLeads state is changed when the saveBtn() is clicked setMyLeads((prev) => [...prev, leadValue.inputVal]);
In DevTools > Applications, localStorage is being updated but when the page is refreshed localStorage is empty []. How do you make localStorage persist state after refresh? I came across this article and have applied the logic but it hasn't solved the issue. I know it is something I have done incorrectly.
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const localStoredValue = JSON.parse(localStorage.getItem("localValue")) ;
const [localItems] = useState(localStoredValue || []);
useEffect(() => {
localStorage.setItem("localValue", JSON.stringify(myLeads));
}, [myLeads]);
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
// setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true);
};
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
);
}
export default App;```
You've fallen into a classic React Hooks trap - because using useState() is so easy, you're actually overusing it.
If localStorage is your storage mechanism, then you don't need useState() for that AT ALL. You'll end up having a fight at some point between your two sources about what is "the right state".
All you need for your use-case is something to hold the text that feeds your controlled input component (I've called it leadText), and something to hold your display boolean:
const [leadText, setLeadText] = useState('')
const [display, setDisplay] = useState(false)
const localStoredValues = JSON.parse(window.localStorage.getItem('localValue') || '[]')
const handleChange = (event) => {
const { name, value } = event.target
setLeadText(value)
}
const saveBtn = () => {
const updatedArray = [...localStoredValues, leadText]
localStorage.setItem('localValue', JSON.stringify(updatedArray))
setDisplay(false)
}
const displayBtn = () => {
setDisplay(true)
}
const displayLocalItems = localStoredValues.map((item) => {
return <li key={item}>{item}</li>
})
return (
<main>
<input name="inputVal" value={leadText} type="text" onChange={handleChange} required />
<button onClick={saveBtn}> Save </button>
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
)

How to default some useState values and test the useState firing when there is a condition (state array should be equal to some value) on button click

Actual code :
const [success, setSuccess] = React.useState(false);
const [ingredients, setIngredients] = React.useState([]);
const [ingredient, setIngredient] = React.useState("");
<input data-test="app-input" type="text" value={ingredient} onChange={(event) =>
setIngredient(event.target.value)} />
<button data-test="app-button"
onClick={() => {
if (ingredient) {
if (ingredients.length === 2) {
setSuccess(true);
}
setIngredients([...ingredients, ingredient]);
}
}}>Add<button>
Test code below :
Here only the on change input value is called mockSetSuccess
test("success state updated when click of the button", () => {
const mockSetSuccess = jest.fn();
React.useState = jest.fn(() => [false, mockSetSuccess]);
const wrapper = shallow(<App />);
const input = wrapper.find("[data-test='app-input']");
const button = wrapper.find("[data-test='app-button']");
input.simulate("change", { target: { value: "hello" } });
button.simulate("click", {});
expect(mockSetSuccess).toHaveBeenCalledWith(true);
});
Can any body help to check the success state and the ingredients array state value

Resources