Too many re-renders. React limits the number of renders to prevent an infinite loop. Updating state of a functional component inside the render method - reactjs

Every time I display the cart items I want to update the total price and count of the items using the useState function inside the render method. But, immediately after the UI is rendered I get a react error mentioned above.
Is there a better way of doing what I'm trying to achieve without getting the error?
const Cart = () => {
const cartItems = useItems()
const firebase = useFirebase()
//Items count
//Total amount of the items
let [total, updateTotal] = useState(0)
let [count, updateCount] = useState(1)
//Method to add items to the cart
useEffect(() => {
if (!firebase) return
}, [firebase, cartItems])
return (
<Layout>
<SEO title="Cart" />
<Navbar count={count} />
<MDBContainer>
<MDBCol lg="12" className="">
<MDBTable responsive className="mt-5 z-depth-1">
<MDBTableHead>
<tr className="bg-light">
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Product
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Price
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Quantity
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Remove
</div>
</th>
</tr>
</MDBTableHead>
<MDBTableBody id="products-list">
{cartItems.map(product => {
updateTotal((total += product.price))
updateCount((count += 1))
return (
<tr>
<td class="px-3 font-weight-normal">
{product.name} <span class="d-none">{product.id}</span>{" "}
</td>
<td width="10%" class="text-center font-weight-normal">
{product.price}
<span>/kg </span>{" "}
</td>
<td width="10%" class="text-center font-weight-normal">
{product.count}
</td>
<td width="10%" class="text-center">
<div class="px-3 font-weight-normal">
{" "}
<button
class="bg-transparent border-0"
id="delete-button"
>
<i class="fa fa-trash-alt delete-icon"></i>
</button>
</div>
</td>
</tr>
)
})}
</MDBTableBody>
<MDBTableFoot>
<tr>
<td className="px-3 text-uppercase font-weight-bold">Total</td>
<td className="font-weight-bold px-5">₹{total}</td>
<td className="font-weight-bold pr-2 text-center">{count}</td>
</tr>
</MDBTableFoot>
</MDBTable>
</MDBCol>
</MDBContainer>
</Layout>
)
}
export default Cart

use blank [] instead of [firebase, cartItems]

The below code should work for you:
const cartItems = useItems();
// I assume this gives you cartItems.
let [total, updateTotal] = useState(() => {
if (cartItems) {
// item object {id: "2", name: "Cucumber", category: "vegetable", price: 50, // //count: 0}
return cartItems.reduce((acc, elem) => {
acc += elem.price * elem.count;
return acc;
}, 0);
}
return 0;
});
let [count, updateCount] = useState(cartItems.length);
React.useEffect(() => {
if (cartItems) {
updateCount(cartItems.length);
// item object {id: "2", name: "Cucumber", category: "vegetable", price: 50, // //count: 0}
const total = cartItems.reduce((acc, elem) => {
acc += elem.price * elem.count;
return acc;
}, 0);
updateTotal(total);
}
}, [cartItems]);
So, basically you need to initiate the state once you get some value from the useItems and then also need to update it when cartItems reference changes

Putting [firebase, cartItems] tells react to always re-render if any change is made to these two parametres. So that is why there are too many re-renders.

Related

Why is React not unsetting the value in the form input in my modal?

I am using Next and Tailwind/Daisy UI.
The code for the page below will fetch a JSON object from the API endpoint and render a top table of source systems and a lower table of domains attached. Clicking on a row in the top table filters the second table to the relevant domains. This all works fine. I also have a modal which is going to be used to create new source systems or edit existing ones. The [Edit] and [Create] buttons call the same function but the [Edit] button passes in the system ID and the [Create] button passes in -1 which is not a valid system id. The function call updates the SelectedSytemID store which is then used to filter the systems list for both the Domains table and the modal.
If when you load the page, you click on [Create] the modal opens and shows the placeholder (because the selectedSystemID is -1 and so not a valid system). If you click on an [Edit] button the modal opens and shows the system name (as it has found the correct system from the filter). If you now click on the [Create] button again, although the selectedSystemID is -1 and the filter function returns undefined, the modal input field is STILL showing the last filtered system name. I don't fully understand why and am looking for both an explanation of why the input value is not re-evaluated and how to fix it. I think I need either a useRef or useEffect but not sure where or how. Any help is much appreciated. I have replaced the API call with hard-coded JSON which is a cut down version of the response.
import { use, useEffect, useState } from "react";
import { listSourceSystems } from "./api/SourceSystems/index";
export default function Sourcesystem() {
const [systems, setSystems] = useState([]);
const [selectedSystemID, setSelectedSystemID] = useState(-1)
const [modalIsOpen, setModalisOpen] = useState(false)
async function fetchData() {
const listSourceSystems = [
{
"id": 1,
"systemName": "Order Management",
"domains": [
{
"id": 1,
"domainName": "Customer"
},
{
"id": 2,
"domainName": "Order"
},
]
},
{
"id": 2,
"systemName": "Warehouse Managment",
"domains": [
{
"id": 9,
"domainName": "Product"
}
]
}
]
// setSystems(await listSourceSystems());
setSystems(listSourceSystems)
console.log(systems)
}
useEffect(() => {
fetchData();
}, []);
function filterDomains(systemID) {
setSelectedSystemID(systemID)
}
function selectedSystem (){
const ss = systems.filter(s=> s.id === selectedSystemID)[0]
return ss
}
function openModal(systemID){
filterDomains(systemID)
setModalisOpen(true)
console.log("openModal")
}
function closeModal(){
setModalisOpen(false)
console.log("closeModal")
}
return (
<>
<div className="flex flex-col mx-10 mt-4">
<h1 className="text-3xl font-bold underline text-center">Source Systems</h1>
<div className="divider"></div>
<div className="grid h-50 card bg-base-300 rounded-box place-items-center">
<table className="table table-compact w-full">
<thead>
<tr>
<th className="font-bold px-5">Name</th>
<th>actions</th>
</tr>
</thead>
<tbody>
{systems && systems.map((system) => (
<tr
key={system.id}
className={`hover ${system.id === selectedSystemID? "active text-secondary font-bold": ""}`}
onClick={() => filterDomains(system.id)}
>
<td className="px-5">{system.systemName}</td>
<td>
<button
className="btn btn-primary btn-sm"
onClick={() => openModal(system.id)}
>
Edit
</button>
</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan="4" className="text-center font-bold accent">Add a new Source System</td>
<td>
<button
className="btn btn-primary btn-wide btn-sm"
onClick={()=> openModal(-1)}
>
click here
</button>
</td>
</tr>
</tfoot>
</table>
</div>
<div className="divider mt-0 before:bg-secondary after:bg-secondary"></div>
<div>
<div className="grid h-20 card bg-primary-800 rounded-box place-items-center">
<table className="table table-compact w-full table-fixed table-zebra">
<thead>
<tr>
<th className="text-left px-5">Domain</th>
<th className="text-right px-5">Source System</th>
</tr>
</thead>
<tbody>
{
selectedSystem()?.domains.map(d => (
<tr key={d.id} className="hover">
<td className="px-5">{d.domainName}</td>
<td className="table-cell-2 text-right px-5">{systems.filter(s => s.id === selectedSystemID).systemName}</td>
</tr>
))
}
</tbody>
</table>
</div>
</div>
{/* !-- Modal --> */}
<input type="checkbox" id="source-system-modal" className=" modal-toggle" checked={modalIsOpen} />
<div className="modal">
<div className="modal-box">
<h3>Source System Maintenance</h3>
<form>
<input
type="text"
placeholder="System Name placeholder"
className="input input-bordered input-primary input-sm w-full"
value={selectedSystem()?.systemName }
>
</input>
</form>
<div className="modal-action">
<label htmlFor="source-system-modal" className="btn btn-info">Submit</label>
<label htmlFor="source-system-modal" className="btn btn-warning btn-outline" onClick={()=> closeModal()}>Cancel</label>
</div>
</div>
</div>
</div>
</>
)}
Once again, as soon as I post it on Stack Overflow, it leads me down a different query path. Turns out the reason is pretty simple. The value in the input must always return a string so I need to do a ternary check and actually return an empty string if the function returns undefined
value={selectedSystem()? selectedSystem().systemName : "" }

I create a todo list system and I use a map to make a loop for all the items I have. But items are stuck for me in the same row in the table

I create a todo list system and I use a map to make a loop for all the items I have.
But items are stuck for me in the same row in the table.
This is a system I used to build in js vanila and now for practice I run it in react.
I would be happy for a solution.
In js vanila I would use insertAdjacentHTML
But I'm looking for the solution in react
demo for the app: https://v1-todolist.netlify.app
My Problem All items in one row.
I need not all items to be on the same line I need it to be dropped line like here
Example of how the item should look properly.
This system I built it in js vanila
if i add Div it not work
It does not sit well in the table and I also get a validateDOMNesting (...) error: cannot appear as a child of .
my code App.js
import { useState } from "react";
import "./App.css";
import Form from "./Form";
import Alert from "./Alert";
function App() {
const [nameTask, setNameTask] = useState("");
const [priority, setPriority] = useState("Low");
const [list, setList] = useState([]);
const [alert, setAlert] = useState({ show: false, type: "", msg: "" });
const handlerSubmit = function (e) {
e.preventDefault();
if (!nameTask) return showAlert(true, "danger", "you cannot input empty");
const newList = {
id: new Date().getTime().toString(),
title: nameTask,
priority: priority,
};
setList([...list, newList]);
};
const showAlert = function (show = false, type, msg) {
setAlert({ show, type, msg });
};
return (
<article className="vh-100 gradient-custom-2">
{alert.show && <Alert {...alert} showAlert={showAlert} />}
<Form
list={list}
setNameTask={setNameTask}
setPriority={setPriority}
handlerSubmit={handlerSubmit}
/>
</article>
);
}
export default App;
Form.js
import React from "react";
const Form = function ({ handlerSubmit, setNameTask, setPriority, list }) {
return (
<div className="container py-5 h-100">
<div className="row d-flex justify-content-center align-items-center h-100">
<div className="col-md-12 col-xl-10">
<div className="card mask-custom">
<div className="card-body p-4 text-white">
<div className="text-center pt-3 pb-2">
<img
src="https://mdbootstrap.com/img/Photos/new-templates/bootstrap-todo-list/check1.png"
alt="Check"
width="60"
/>
<h2 className="my-4">Task List</h2>
</div>
<form className="form-task" onSubmit={(e) => handlerSubmit(e)}>
<div className="col-auto">
<input
name="name-task"
type="text"
className="form-control task-input"
id="autoSizingInput"
placeholder="Add Task"
onChange={(e) => setNameTask(e.target.value)}
/>
</div>
<select
className="form-select"
aria-label="Default select example"
onChange={(e) => setPriority(e.target.value)}
>
<option value="Low">Low</option>
<option value="Normal">Normal</option>
<option value="High">High</option>
</select>
<button type="submit" className="btn btn-primary">
submit
</button>
</form>
<table className="table text-white mb-0">
<thead>
<tr>
<th scope="col">Task</th>
<th scope="col">Priority</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr className="fw-normal">
{list.map((item, index) => {
return (
<React.Fragment key={index}>
<th>
<span className="ms-2" data-name={`${item.title}`}>
{item.title}
</span>
</th>
<td className="align-middle priority-class">
<span className="badge ${value.color}">
{item.priority}
</span>
</td>
<td className="align-middle">
<h6 className="mb-0" data-id="${value.id}">
<a className="remove-link" href="#">
<span className="badge bg-gradient remove">
❌
</span>
</a>
<a className="complete-link" href="#">
<span className="badge bg-gradient complete">
✔
</span>
</a>
</h6>
</td>
</React.Fragment>
);
})}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
export default Form;
Check this CodeSandbox
I was able to solve your issu just by adding a flex propriety to the panel that contain your list and by changing React.fragment by a div.
However it would perhaps be better to swap the node with the class fw-normal to a div and change the React.fragment to the node tr.

Message prints in every Dynamic Accordion in ReactJs

I have a dynamic Accordion in ReactJs. I am getting the message from my backend. but it's printing in every Accordion. I'm sharing the code
import React, { useState, useEffect } from "react";
import ApplicantDash from "./ApplicantDash";
import {
Accordion,
AccordionSummary,
AccordionDetails,
Typography,
} from "#material-ui/core";
import * as FcIcons from "react-icons/fc";
import ApplicantService from "../services/ApplicantService";
export default function AvailJobs() {
const [aplcntEmail, setAplcntEmail] = useState("aman#gmail.com"); //change to aplcntemail
const [isShow, setIsShow] = useState(false);
const [msg, setMsg] = useState([""]);
const [job, setJob] = useState([
{
jobTitle: "",
dateOfPosting: Date,
lastDateToApply: new Date().toLocaleDateString([], {
year: "numeric",
month: "long",
day: "numeric",
}),
preferableSkills: [],
requiredExp: 0,
recruiterEmail: "",
companyName: "",
companyAddress: "",
},
]);
useEffect(() => {
const data = ApplicantService.getAllJobs()
.then((response) => {
console.log(response.data);
setJob(response.data);
})
.catch((error) => {
alert(error.response.data);
});
}, []);
const onApplyButton = (item,key) => {
const data2 = ApplicantService.applyForJob(aplcntEmail, item)
.then((response) => {
console.log(response.data);
setIsShow(true);
setMsg(response.data)
})
.catch((error) => {
setIsShow(true);
setMsg(error.response.data);
});
};
return (
<div>
<ApplicantDash />
<div className="container bg-light">
<div className="card-bodies">
<section className="mb-4">
<h2 className="h1-responsive font-weight-bold text-center my-4">
All Available jobs
</h2>
</section>
{job.map((item, key) => (
<>
<Accordion key={key}>
<AccordionSummary
expandIcon={<FcIcons.FcExpand />}
aria-controls="panel1a-content"
id="panel1a-header"
className="Accordian"
>
<Typography>
<div className="d-flex p-1 justify-content-evenly">
<div className="p-1">
<b> Job: </b> {item.jobTitle}
</div>
<div className="p-2"></div>
<div className="p-1">
<b> Company: </b> {item.companyName}
</div>
<div className="p-2"></div>
<div className="p-1">
<b> Last Date: </b> {item.lastDateToApply}
</div>
</div>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
<div className="container">
<table class="table table-borderless">
<tbody>
<tr>
<td>JOB TITLE</td>
<td>:</td>
<td>
<b>{item.jobTitle}</b>
</td>
</tr>
<tr>
<td>Company</td>
<td>:</td>
<td>
<b>{item.companyName}</b>
</td>
</tr>
<tr>
<td>Address</td>
<td>:</td>
<td>
<b>{item.companyAddress}</b>
</td>
</tr>
<tr>
<td>Last Date to Apply</td>
<td>:</td>
<td>
<b>{item.lastDateToApply}</b>
</td>
</tr>
<tr>
<td>Experience</td>
<td>:</td>
<td>
<b>{item.requiredExp}</b>
</td>
</tr>
<tr>
<td> Skills </td>
<td>:</td>
<td>
<table className="table table-condensed w-auto table-borderless table-hover">
{item.preferableSkills.map((S, index1) => {
return (
<tbody key={index1}>
<td scope="col">
{index1 + 1}.<b>{S}</b>
</td>
</tbody>
);
})}
</table>
</td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<button
type="button"
class="btn btn-primary"
onClick={() => onApplyButton(item,key)}
>
Apply for the job{" "}
</button>
</td>
</tr>
</tbody>
{isShow && <>
{msg}
</>}
</table>
</div>
</Typography>
</AccordionDetails>
</Accordion>
</>
))}
</div>
</div>
</div>
);
}
Now when I click on Apply for this job button. The message I get from backend prints only to Active accordion
Here some pictures which might help.
enter image description here
As you can see the response from backend is prints in the both of the accordion
Issue
The issue here is that you've a single boolean isShow state and a single msg state, and all the accordion detail sections use the same single isShow state to conditionally render the msg state.
Solution
A simple solution would be to store the id, or title, or index, of the accordion to show the message of.
Example:
export default function AvailJobs() {
...
const [isShow, setIsShow] = useState({}); // <-- initially empty object
...
const onApplyButton = (item, key) => {
ApplicantService.applyForJob(aplcntEmail, item)
.then((response) => {
console.log(response.data);
setMsg(response.data);
})
.catch((error) => {
setMsg(error.response.data);
})
.finally(() => {
setIsShow(show => ({
...show,
[key]: true // <-- set true the specific key
}));
});
};
return (
<div>
...
{job.map((item, key) => (
<Accordion key={key}>
...
<AccordionDetails>
<Typography>
<div className="container">
<table class="table table-borderless">
<tbody>
...
<tr>
...
<td>
<button
type="button"
class="btn btn-primary"
onClick={() => onApplyButton(item, key)}
>
Apply for the job
</button>
</td>
</tr>
</tbody>
{isShow[key] && <>{msg}</>} // <-- check if isShow[key] is truthy
</table>
</div>
</Typography>
</AccordionDetails>
</Accordion>
))}
...
</div>
);
}

React hook form's FieldArray crashes app when deleting item

I made a dynamic input table component with react-hook-form. You are able to add, remove and edit fields in the table. Here's how it looks like
import {useFieldArray, useFormContext} from "react-hook-form";
import {cloneElement} from "react";
import {IoMdAdd, IoMdRemoveCircle} from "react-icons/io";
interface TableData {
tableName:string,
inputFields: {
title: string,
name: string,
inputComponent: any,
}[],
inputBlueprint: object,
min?: number
};
const InputTable = ({tableName, inputFields, inputBlueprint, min}: TableData) => {
const {fields, remove, append} = useFieldArray({name: tableName});
const {register, formState: {errors}} = useFormContext();
return (
<table className="table-auto border-collapse block m-auto w-fit max-w-xs max-h-48 overflow-auto sm:max-w-none my-3">
<thead className="text-center">
<tr>
{inputFields.map((input) => (
<td className="border-2 border-gray-400 px-5" key={input.title}>{input.title}</td>
))}
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.id}>
{inputFields.map((input) => (
<td key={input.title} className="border-gray-400 border-2 p-0">
{cloneElement(input.inputComponent, {
className: "bg-transparent outline-none block w-full focus:bg-gray-400 dark:focus:bg-gray-500 p-1",
...register(`${tableName}.${index}.${input.name}` as const)
})}
{errors[tableName]?.[index][input.name] &&
<p className="bg-red-400 p-1">
{errors[tableName][index][input.name]?.message}
</p>
}
</td>
))}
{(min === undefined || min <= index) &&
<td onClick={() => remove(index)}><IoMdRemoveCircle className="text-red-600 text-2xl"/></td>
}
</tr>
))}
<tr>
<td onClick={() => append(inputBlueprint)} className="bg-green-500 border-gray-400 border-2"
colSpan={inputFields.length}>
<IoMdAdd className="m-auto"/>
</td>
</tr>
{errors[tableName] &&
<tr>
<td className="max-w-fit text-center">
{errors[tableName].message}
</td>
</tr>}
</tbody>
</table>
)
}
export default InputTable
Now, whenever I have only one element in the array it works just fine but when having more than one it crashes whenever an input field is emptied through typing. I don't really know how to explain it clearly so here's a gif showing it

React setState not working with useEffect on fetched data

My aim is to get the Data from an API call on first rendering of the page, store it in a state variable for further use. During the call, a loader keeps spinning and when call is successful, the loader disappears and the data is rendered on the screen using the state variable in which the response is saved.
Currently, I successfully make the call and get the data but when I try to store the data on a state variable, it remains undefined. I know that it is due to the fact that I have not initiated it with anything when I declared it.
My concern being when I set it later to the response of the API call then why is it undefined? Also if it is undefined then the loader would be running infinitely but in reality it stops and certain part of data is shown.
I have looked at various solution on Stack overflow and tried the following:
Check the state variable is not empty before rendering
used useLayoutEffect instead of useEffect
put the call function inside useEffect
Nothing works and the loader is stuck on loading.
Thank You.
import React, { useState, useEffect } from "react";
import * as S from "./Service.js";
import { Loader } from "./Loader.js";
function NeedHelp() {
let rspnse;
const [loader, setLoader] = useState(true);
const [data, setData] = useState(); // not setting any value
async function fetchData() {
rspnse = await S.getData();
setData(rspnse);
console.log("data :\n",data);
}
useEffect(() => {
setLoader(true);
fetchData();
setLoader(false);
}, []);
return (
<div className=" pt-3 p-1 mx-1 w-full">
{loader || typeof data === "undefined" ? (
<Loader />
) : (
data && (
<div>
<div id="first row" className="border p-2">
<span className="px-4 px-4 flex border">
<input
className="w-1/5 text-xs rounded py-1 px-1.5 placeholder-gray placeholder-opacity-75 focus:ring-0.5 focus:ring-blue focus:border-blue"
type="text"
placeholder="Filter..."
></input>
<div id="search_clear" className=" mx-1">
<button
onClick=""
className="text-white rounded px-1.5 py-1 mx-0.5 bg-blue transition duration-500 ease-in-out transform hover:translate-x-1 hover:scale-110 hover:bg-blue hover:shadow-lg"
>
Clear
</button>
</div>
<div className="self-center">
<p className="text-sm text-bgray ml-5">
Number of View: {data.data.length}
</p>
</div>
</span>
</div>
<div id="table">
<table className="table_auto w-full">
<thead>
<tr className="">
<th className=" "> Name</th>
<th className=" "> ID</th>
<td className=" "></td>
<td className=" "></td>
</tr>
</thead>
<tbody className="border">
<tr>
<td className="border">1</td>
<td className="border">2</td>
<td className="border">3</td>
<td className="border">4</td>
</tr>
</tbody>
</table>
</div>
</div>
)
)}
</div>
);
}
export default NeedHelp;
No reason to store rsponse as you have, and I cleaned the code up in a couple of other ways that aren't super notable, except maybe that loading will always be shown if no data is set. This should without any hitches, if it doesn't further debugging of what res is needs to be done:
import React, { useState, useEffect } from "react";
import * as S from "./Service.js";
import { Loader } from "./Loader.js";
function NeedHelp() {
const [data, setData] = useState(); // not setting any value
useEffect(() => {
(async () => {
setData(await S.getData());
})();
}, []);
return (
<div className=" pt-3 p-1 mx-1 w-full">
{!data ? (
<Loader />
) : (
<div>
<div id="first row" className="border p-2">
<span className="px-4 px-4 flex border">
<input
className="w-1/5 text-xs rounded py-1 px-1.5 placeholder-gray placeholder-opacity-75 focus:ring-0.5 focus:ring-blue focus:border-blue"
type="text"
placeholder="Filter..."
></input>
<div id="search_clear" className=" mx-1">
<button
onClick=""
className="text-white rounded px-1.5 py-1 mx-0.5 bg-blue transition duration-500 ease-in-out transform hover:translate-x-1 hover:scale-110 hover:bg-blue hover:shadow-lg"
>
Clear
</button>
</div>
<div className="self-center">
<p className="text-sm text-bgray ml-5">
Number of View: {data.data.length}
</p>
</div>
</span>
</div>
<div id="table">
<table className="table_auto w-full">
<thead>
<tr className="">
<th className=" "> Name</th>
<th className=" "> ID</th>
<td className=" "></td>
<td className=" "></td>
</tr>
</thead>
<tbody className="border">
<tr>
<td className="border">1</td>
<td className="border">2</td>
<td className="border">3</td>
<td className="border">4</td>
</tr>
</tbody>
</table>
</div>
</div>
)}
</div>
);
}
export default NeedHelp;
modify your code as below
const [loader, setLoader] = useState(false);
async function fetchData() {
setLoader(true);
rspnse = await S.getData();
setData(rspnse);
console.log("data :\n",data);
setLoader(false);
}
useEffect(() => {
fetchData();
}, []);

Resources