Why getting too many re-renders? - reactjs

I have a Calculator component for calculating the exponent of a given base.
My code is as follows:
//Exponential Calculator (Power/Squre-Root/Cube-Root)
const Exponents=()=>{
const [result,setResult]=useState(null);
const [choice,setChoice]=useState("Power");
const [x,setX]=useState(null);
const [n,setN]=useState(null);
useEffect(()=>{
},[choice,x,n,result]);
const calcResult=()=>{
let res=1;
if(choice=="Power")
{
for(var i=1;i<=n;i++)
res*=x;
}
else if(choice=="SquareRoot")
res=Math.sqrt(x);
else
res=Math.cbrt(x);
setResult(res);
}
const handleChange=(e)=>{
reset();
setChoice(e.target.value);
}
function reset(){
setResult(null);
setX(null);
setN(null);
}
const choiceData=()=>{
if(choice==="Power"){
return {
name:"Power",
quantities:["Base","Exponent"],
disabled:false
}
}
else if(choice==="SquareRoot")
{
setN(0.50);
return{
name:"Square-Root",
quantities:["Base","Exponent"],
disabled:true
}
}
else if(choice==="CubeRoot")
{
setN(0.34);
return{
name:"Cube-Root",
quantities:["Base","Exponent"],
disabled:true
}
}
}
return(
<>
<Form>
<Form.Group className="mb-4" controlId="choice">
<Form.Label>Select the type of calculation</Form.Label>
<Form.Control
as="select"
className="select-custom-res"
onChange={(e) => handleChange(e)}
>
<option value="Power">Power</option>
<option value="SquareRoot">Square Root</option>
<option value="CubeRoot">Cube Root</option>
</Form.Control>
</Form.Group>
<Form.Group className="mb-4" controlId="text">
<Form.Text className="text">
<strong>
To find the {choiceData().name}, Enter the following values
</strong>
<br />
</Form.Text>
</Form.Group>
<Form.Group className="mb-4">
<Form.Label>{choiceData().quantities[0]}</Form.Label>
<Form.Control
onChange={(e) => setX(e.target.value)}
type="number"
placeholder={"Enter the Base"}
value={x === null ? "" : x}
/>
</Form.Group>
<Form.Group className="mb-4">
<Form.Label>{choiceData().quantities[1]}</Form.Label>
<Form.Control
onChange={(e) => setN(e.target.value)}
type="number"
placeholder={"Enter the Exponent"}
value={n === null ? "" : n}
disabled={choiceData().disabled}
/>
</Form.Group>
<Form.Group className="mb-4">
<Form.Control
readOnly
type="number"
placeholder={result === null ? "Result" : result + " "}
/>
</Form.Group>
</Form>
<div className="button-custom-grp">
<Button variant="primary" onClick={calcResult}>
Calculate
</Button>
<Button variant="dark" onClick={() => reset()} type="reset">
Reset
</Button>
</div>
</>
)
}
On changing the choice from Power to SquareRoot I get an error:Too many rerenders. Interestingly, when I remove the setState line in the choiceData function,the error vanishes. Although I have used useEffect to prevent re-renders, it's not working.

Issue
You call choiceData in the render return, which should be free of side-effects, like enqueueing state updates. When you call setN in the choiceData function it triggers a rerender, which calls choiceData again, which triggers a rerender... repeat ad nauseam.
Solution
I suggest converting choiceData into a chunk of state, and use an useEffect hook to update it and the n state depending on the choice state. In the render return, instead of calling a function to get a value, i.e. choiceData().quantities[0], you instead just access a property of the new choiceData state, i.e. choiceData.quantities[0].
const Exponents = () => {
const [result, setResult] = useState(null);
const [choice, setChoice] = useState("Power");
const [choiceData, setChoiceData] = useState({});
const [x, setX] = useState(null);
const [n, setN] = useState(null);
useEffect(() => {
if (choice === "Power") {
setChoiceData({
name: "Power",
quantities: ["Base", "Exponent"],
disabled: false
});
} else if (choice === "SquareRoot") {
setN(0.5);
setChoiceData({
name: "Square-Root",
quantities: ["Base", "Exponent"],
disabled: true
});
} else if (choice === "CubeRoot") {
setN(0.34);
setChoiceData({
name: "Cube-Root",
quantities: ["Base", "Exponent"],
disabled: true
});
}
}, [choice]);
useEffect(() => {
// is this effect used for anything?
}, [choice, x, n, result]);
const calcResult = () => {
let res = 1;
if (choice == "Power") {
for (var i = 1; i <= n; i++) res *= x;
} else if (choice == "SquareRoot") res = Math.sqrt(x);
else res = Math.cbrt(x);
setResult(res);
};
const handleChange = (e) => {
reset();
setChoice(e.target.value);
};
function reset() {
setResult(null);
setX(null);
setN(null);
}
return (
<>
<Form>
<Form.Group className="mb-4" controlId="choice">
<Form.Label>Select the type of calculation</Form.Label>
<Form.Control
as="select"
className="select-custom-res"
onChange={(e) => handleChange(e)}
>
<option value="Power">Power</option>
<option value="SquareRoot">Square Root</option>
<option value="CubeRoot">Cube Root</option>
</Form.Control>
</Form.Group>
<Form.Group className="mb-4" controlId="text">
<Form.Text className="text">
<strong>
To find the {choiceData.name}, Enter the following values
</strong>
<br />
</Form.Text>
</Form.Group>
<Form.Group className="mb-4">
<Form.Label>{choiceData.quantities[0]}</Form.Label>
<Form.Control
onChange={(e) => setX(e.target.value)}
type="number"
placeholder={"Enter the Base"}
value={x === null ? "" : x}
/>
</Form.Group>
<Form.Group className="mb-4">
<Form.Label>{choiceData.quantities[1]}</Form.Label>
<Form.Control
onChange={(e) => setN(e.target.value)}
type="number"
placeholder={"Enter the Exponent"}
value={n === null ? "" : n}
disabled={choiceData.disabled}
/>
</Form.Group>
<Form.Group className="mb-4">
<Form.Control
readOnly
type="number"
placeholder={result === null ? "Result" : result + " "}
/>
</Form.Group>
</Form>
<div className="button-custom-grp">
<Button variant="primary" onClick={calcResult}>
Calculate
</Button>
<Button variant="dark" onClick={() => reset()} type="reset">
Reset
</Button>
</div>
</>
);
};

Related

form input not resetting with useState

I have a react bootstrap number form and state.
I would like to reset hours state to 0 after performing the button click and API POST transaction.
hours state updates just fine for the onChange function where I call setHours() in form and I'm using this strategy to reset form data on other components. Any ideas why I can't get it to work elsewhere after button click (i.e. in my try/catch block)??
const [hours, setHours] = useState(0);
// ...
const handleAddWorkItem = async (sprintId, date, hours) => {
try {
await addWorkItemToAirtable(sprintId, date, hours);
const workItems = await fetchWorkItemsByJobId(sprint.job.id);
setworkItems(workItems);
setHours(0); // not working
} catch (error) {
console.log({ error });
}
};
// ...
<Form>
<Form.Group
className="mb-2"
controlId="addTimeFormEmployee"
>
<Form.Label className="mb-0">
<strong>Employee:</strong>
</Form.Label>
<Form.Select
disabled
defaultValue={sprint.employee.id}
onChange={(e) => {
setEmployee(e.target.value);
}}
aria-label="Select Employee"
>
{employees &&
employees.map((employee) => {
return (
<option key={employee.id} value={employee.id}>
{`${employee.firstName} ${employee.surname}`}
</option>
);
})}
</Form.Select>
</Form.Group>
<Form.Group className="mb-2" controlId="addTimeFormDate">
<Form.Label className="mb-0">
<strong>Date:</strong>
</Form.Label>
<Form.Control
type="text"
onFocus={(e) => (e.target.type = "date")}
onChange={(e) => {
setDate(e.target.value);
}}
defaultValue={date}
placeholder={date}
/>
</Form.Group>
<Form.Group className="mb-2" controlId="addTimeFormHours">
<Form.Label className="mb-0">
<strong>Hours:</strong>
</Form.Label>
<Form.Control
type="number"
defaultValue={0}
onChange={(e) => setHours(parseInt(e.target.value, 10))}
placeholder={0}
min={0}
/>
</Form.Group>
</Form>
<Button
variant="success"
onClick={() => {
handleAddWorkItem(sprint.id, date, hours);
}}
>
Add Time
</Button>
Thank you!

How to rerender fields selected by a dropdown?

im create a form using react-hook-from, and one of the fields that ive created is dynamic dropdown.
whenever im choosing one of the options im rendering some other text fields, depends on the the dropdown selection.
the problem is that when im submitting, im getting the fields of the other dropdown selection,
here is my code:
<FormGroup>
<Label>Automation Team</Label>
<Controller
name="team"
control={control}
as={Select}
options={ teams.map((team, index) => (
{ value: team.name, label: team.name}
)) }
defaultValue="Select component"
rules={{required: "Role is required" }}
isClearable
/>
</FormGroup>
<Row form>
{
getFields(teamComponent).map((field,index) => (
<Col key={index} md={4}>
<Label for={field}>{field}</Label>
<Input name={field} placeholder="with a placeholder" innerRef={register({required: true})}/>
{errors?.field?.types?.required &&
<small className="text-danger">Database name required</small>}
</Col>
) )
}
</Row>
so the problem was that I didnt used useEffect and didnt unregister from
last fields that was rendered.
Not the cleanest code bit it works for me :) .
so I refactored my code to look like this:
function TicketForm () {
const { register, unregister, handleSubmit, reset, formState: {isSubmitSuccessful, errors}, control, watch } = useForm({
mode: "onBlur",
criteriaMode: "all"
});
const teamComponent = watch("team")
const {isAuthenticated} = useAuthentication();
const [teams, setTeams] = useState([])
const [fields, setFields] = useState(0);
useEffect( () => {
if (isAuthenticated === true) {
getConfigurations(async response => {
setTeams(response.data.teams.Teams)
})
}
if (teamComponent )
onTeamChange(teamComponent)
}, [teamComponent])
const handleRegistration = async data => {
console.log(data)
}
const onTeamChange = c => {
if (!c) {
setFields([])
return
}
if (fields) {
fields.map((field,index) => {
unregister(field)
})
}
const result = teams.filter((team, index) => {
if(team.name === c.value) {
return team
}
})
if (result) {
setFields(result[0].Fields)
console.log(fields)
}
}
return (
<Form onSubmit={handleSubmit(handleRegistration)}>
<FormGroup>
<Label>Summary</Label>
<Input name="summary" innerRef={register({required: true})} />
{errors?.summary?.types?.required && <small className="text-danger">summary required</small>}
</FormGroup>
<FormGroup>
<Label>Description</Label>
<Input
type="textarea"
name="description"
innerRef={register({required: true})}
/>
{errors?.description?.types?.required && <small className="text-danger">description required</small>}
</FormGroup>
<FormGroup>
<Label for='team'>Automation Team</Label>
<Controller
control={control}
name="team"
innerRef={register({required: true})}
as={Select}
options={ teams.map((team, index) => (
{ value: team.name, label: team.name}
)) }
defaultValue=""
isClearable
/>
</FormGroup>
<Row form>
{
fields && fields.map((field,index) => {
//const fieldName = `field[${index}]`;
return (
<fieldset name={field} key={index}>
<Label for={field}>{field}</Label>
<Input name={field} placeholder="" innerRef={register({required: true})}/>
</fieldset>
)
}/*(
<Col key={index} md={4}>
<Label for={field}>{field}</Label>
<Input name={field} placeholder="with a placeholder" innerRef={register({required: true})}/>
{errors?.field?.types?.required &&
<small className="text-danger">Database name required</small>}
</Col>
)*/ )
}
</Row>
<Button type='submit' color="primary">Submit</Button>
</Form>
);}export default TicketForm;

ReferenceError: Cannot access 'steps' before initialization

I'm trying to make wizards in ReactJS. I'm following an online tutorial, however they haven't explained how to make it by multiple pages. So I tried my own way to apply it, but it didn't work.
Here is the code of the first page:
import React, { useState } from "react";
import { Button, Form } from "react-bootstrap";
import { Link } from "react-router-dom";
import "./style.css";
function NewGame() {
const [activeStep, setActiveStep] = useState(steps[0]);
const [steps, setSteps] = useState([
{
key: "firstStep",
label: "My First Step",
isDone: true,
component: firstStep,
},
]);
const index = steps.findIndex((x) => x.key === activeStep.key);
setSteps((prevStep) =>
prevStep.map((x) => {
if (x.key === activeStep.key) x.isDone = true;
return x;
})
);
const firstStep = () => {
return (
<div>
<div className="box">
<div className="steps">
<ul className="nav">
{steps.map((step, i) => {
return (
<li
key={i}
className={`${
activeStep.key === step.key ? "active" : ""
} ${step.isDone ? "done" : ""}`}
>
<div>
Step {i + 1}
<br />
<span>{step.label}</span>
</div>
</li>
);
})}
</ul>
</div>
<div className="step-component">{activeStep.component()}</div>
<div className="btn-component">
<input
type="button"
value="Back"
onClick={handleBack}
disabled={steps[0].key === activeStep.key}
/>
<input
type="button"
value={
steps[steps.length - 1].key !== activeStep.key
? "Next"
: "Submit"
}
onClick={handleNext}
/>
</div>
</div>
<Form className="column justify-content-center page">
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Benzersiz Ad</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Görünen İsim</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Oyun Açıklaması</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Oyun Türü</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Bireysel</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Oyun Durumu</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
<Form.Label>Açık</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
</Form>
<Link to="/PageTwo">
<Button className="button" variant="outline-secondary">
İ L E R İ
</Button>{" "}
</Link>
</div>
);
const handleNext = () => {
if (steps[steps.length - 1].key === activeStep.key) {
alert("You have completed all steps.");
}
setActiveStep(steps[index + 1]);
};
const handleBack = () => {
const index = steps.findIndex((x) => x.key === activeStep.key);
if (index === 0) return;
setSteps((prevStep) =>
prevStep.map((x) => {
if (x.key === activeStep.key) x.isDone = false;
return x;
})
);
setActiveStep(steps[index - 1]);
};
};
}
export default NewGame;
So when I run this code I have this error in the website:
ReferenceError: Cannot access 'steps' before initialization
NewGame
C:/Users/SAMSUNG/Documents/src/pages/NewGame.js:6
3 | import { Link } from "react-router-dom";
4 | import "./style.css";
5 | function NewGame() {
> 6 | const [activeStep, setActiveStep] = useState(steps[0]);
7 | const [steps, setSteps] = useState([
8 | {
9 | key: "firstStep",
Thank you!
The error is telling you that the variable steps is initialized on line 7, but you're using it on line 6 to set the initial value of the activeStep state variable. You cannot use a variable before it's initialized, hence the message "Cannot access 'steps' before initialization".
Move line 6 after the statement that begins on line 7.

How to add edit button and function in react.js

i want to share this question. So, i have a form which is designed by react-bootstrap. And also use React Hooks and React Function Component. I have a form which is add new form and delete form but i don't do edit form.
This is a return statement
return(
<Container>
<Row>
<Col>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label>Name</Form.Label>
<Form.Control ref = {firstname} type="text" placeholder="Name.." />
</Form.Group>
<Form.Group>
<Form.Label>Surname</Form.Label>
<Form.Control ref = {secondname} type="text" placeholder="Surname.." />
</Form.Group>
<Form.Group>
<Form.Label>Email address</Form.Label>
<Form.Control ref = {email} type="email" placeholder="E-Mail" />
<Form.Text> Please, Enter like "asd#asd.com"</Form.Text>
</Form.Group>
<Form.Group>
<Form.Label>Comment</Form.Label>
<Form.Control ref = {comment} as="textarea" rows={3} placeholder = "Notes :)"/>
</Form.Group>
<Button className = "btn-lg" onClick={handleSubmit} variant="success" type="submit">Submit</Button>
</Form>
</Col>
</Row>
{Formss}
</Container>
)
And then, These are the function of this return
const Formss = input.map((item , index) =>
{
return(
<Lists key = {index} item = {item} index = {index} deleteFunc={handleDelete}/>
)
}
)
const handleSubmit = (event) => {
event.preventDefault();
const name = firstname.current.value
const surname = secondname.current.value
const mail = email.current.value
const mycomment = comment.current.value
const data = {id:id(),
name : name,
surname : surname,
mail : mail,
mycomment : mycomment}
if(data.name && data.surname && data.mail && data.mycomment){
setInput([...input, data])
firstname.current.value = ""
secondname.current.value = ""
email.current.value = ""
comment.current.value =""
}else{
console.log("oopss")
}
}
I use ref hook for handleSubmit. So, How to add edit button and edit function?
To be able to edit data, and to save it in state you can do it as in provided example. Then in handleSubmit function you can process your data further:
import React from "react";
import { Container, Row, Col, Form, Button } from "react-bootstrap";
const App = () => {
const handleSubmit = (e) => {
e.preventDefault();
console.log(state);
};
const initialState = {
firstname: "",
secondname: "",
email: "",
comment: "",
};
const [state, setState] = React.useState(initialState);
const handleChange = ({ target: { value, name } }) => {
setState({ ...state, [name]: value });
};
return (
<Container>
<Row>
<Col>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label>Name</Form.Label>
<Form.Control
name="firstname"
value={state.firstname}
type="text"
placeholder="Name.."
onChange={handleChange}
/>
</Form.Group>
<Form.Group>
<Form.Label>Surname</Form.Label>
<Form.Control
name="secondname"
value={state.secondname}
type="text"
placeholder="Surname.."
onChange={handleChange}
/>
</Form.Group>
<Form.Group>
<Form.Label>Email address</Form.Label>
<Form.Control
value={state.email}
name="email"
type="email"
placeholder="E-Mail"
onChange={handleChange}
/>
<Form.Text> Please, Enter like "asd#asd.com"</Form.Text>
</Form.Group>
<Form.Group>
<Form.Label>Comment</Form.Label>
<Form.Control
name="comment"
value={state.comment}
as="textarea"
rows={3}
placeholder="Notes :)"
onChange={handleChange}
/>
</Form.Group>
<Button className="btn-lg" variant="success" type="submit">
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
);
};
export default App;

How to add validation in Product add Form c- react component

I am doing Form for add product to firebase, but each click on the submit button, although it already has data or not, it still can add in Firebase. I would like to add validation to this case. How could I do that. I would like all file input cannot be left blank, and text file is always text not the number
const ProductForm = ({
products,
createProductRequest,
fetchProductRequest,
loading,
type }) => {
const [values, setValues] = useState({
image: "",
name: "",
price: 0,
description: "",
categoty: "",
});
const [imageAsFile, setImageAsFile] = useState();
useEffect(() => {
if (Object.keys(products).length) {
setValues(products);
}
}, [products]);
useEffect(() => {
if (type === "CREATE_PRODUCT_SUCCESS") {
fetchProductRequest();
}
}, [fetchProductRequest, type]);
const handleInputChange = (event) => {
// Create new product to update
const newPropdudct = {
...values,
[event.target.name]: event.target.value,
};
// Update new product for value
setValues(newPropdudct);
};
const handleFileChange = (event) => {
const image = event.target.files[0]
setImageAsFile(imageAsFile => (image))
console.log(image);
}
const onSave = () => {
createProductRequest(values, imageAsFile);
};
if (loading) {
return (
<Container>
<Loading />
</Container>
);
}
return (
<Container className="product-form">
<Form>
<Form.Group>
<Form.Group>
<Form.File
id="image"
label="Image choose"
value={values.image.name}
onChange={handleFileChange}
/>
</Form.Group>
</Form.Group>
<Form.Group controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter product name"
value={values.name}
name="name"
onChange={handleInputChange}
>
</Form.Control>
</Form.Group>
<Form.Group controlId="categories">
<Form.Label>Categories</Form.Label>
<Form.Control
as="select"
name="category"
value={values.category}
onChange={handleInputChange}
>
{CATEGORIES.map((category, index) => (
<option key={index}>{category.name}</option>
))}
</Form.Control>
</Form.Group>
<Form.Group controlId="price">
<Form.Label>Price</Form.Label>
<Form.Control
type="number"
placeholder="Enter product price"
value={values.price}
name="price"
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="description">
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows="3"
name="description"
value={values.description}
onChange={handleInputChange}
/>
</Form.Group>
<Button btnText="Submit" size="btn-md" handleClick={onSave} />
</Form>
</Container>
);
};
export default ProductForm;
You may start by adding the required attribute to each form control for basic HMTL5 validation.
For more advanced validation logic, you can have this as your onSave content:
const onSave = (event) => {
let invalid = [];
['name', 'description', 'category'].forEach(key => {
if (values[key].match(/[0-9]+/))
invalid.push(key);
});
if (values.name.trim() === '')
invalid.push('name');
if (invalid.length !== 0) {
event.preventDefault();
// Whatever you want to do when the content is invalid
} else {
createProductRequest(values, imageAsFile);
}
};
With this snippet, you have the invalid fields in the invalid array variable.
You can also customize the validation logic.

Resources