React hook form's FieldArray crashes app when deleting item - reactjs

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

Related

ReactJS search with api params

I want to ask for your help.
Earlier I got a Reactjs search filter tutorial on a web which I forgot where the source came from.
the code is like this =>
import React, { useEffect, useState } from "react";
import axios from "axios";
const Users = () => {
const [users, setUsers] = useState([]);
const [mainUsers, setMainUsers] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => {
setUsers(res.data);
setMainUsers(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
const handleSearch = (e) => {
setUsers(
mainUsers.filter((u) =>
u.name.toLowerCase().includes(e.target.value.toLowerCase())
)
);
console.log(e.target.value);
};
return (
<div className={`mt-5 p-4 container-fluid`}>
<div className="row my-2 mb-4 justify-content-between w-100 mx-0">
<div className="form-group col-10 col-md-6 col-lg-4">
<input
type="text"
className="form-control shadow"
placeholder="Search"
onChange={handleSearch}
/>
</div>
</div>
{users.length ? (
<table className="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">User Name</th>
<th scope="col">Email</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{users.map((u) => (
<tr key={u.id}>
<th scope="row">{u.id}</th>
<td>{u.name}</td>
<td>{u.username}</td>
<td>{u.email}</td>
<td>
<i className="fa fa-edit text-warning"></i>
<i className="fa fa-edit text-warning pointer"></i>
</td>
</tr>
))}
</tbody>
</table>
) : (
<h6 className="text-center text-info">wating ...</h6>
)}
</div>
);
};
export default Users;
then I have an endpoint that has params, like this =>
xxxx.com/api/dashboard/v1/getCountPoiCategoryProvinsi?provinsi=jakarta
The question is,
how to change the initial url (https://jsonplaceholder.typicode.com/users") to this url (xxxx.com/api/dashboard/v1/getCountPoiCategoryProvinsi?provinsi=jakarta) ?
I don't know how to change it Thank you, I really appreciate your help.

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 : "" }

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>
);
}

How to map a nested array in react

Ok. I am super lost here. I am attempting to return an a value from a nested array of objects. Here is what the database looks like.
Each object returns an array of staff as seen in this console log.
I cannot for the life of me display both names out of the array. I used the .flatMap() function to extract the data from the array, but when I go to render it in the component only the first name shows. Here is my codebase. Right now I am just console logging the data using .flatMap()
import React, { Fragment, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Table } from 'react-bootstrap';
import Moment from 'react-moment';
import Button from '../components/Button';
import ActivitySummary from '../components/ActivitySummary';
import { projectsInfoDetails } from '../actions/projectInfoActions';
import { projectContacts } from '../actions/projectContactActions';
import { projectStaff } from '../actions/accountableStaffActions';
import SectionHeader from '../components/SectionHeader';
import Loader from '../components/Loader';
import Message from '../components/Message';
const ProjectScreen = ({ match }) => {
const dispatch = useDispatch();
const projectInfoDetails = useSelector(state => state.projectInfoDetails);
const { loading, error, projects } = projectInfoDetails;
const contactDetails = useSelector(state => state.projectContactDetails);
const { projectContact } = contactDetails;
const projectAccountableStaff = useSelector(state => state.projectStaff);
const { accountableProjectStaff } = projectAccountableStaff;
useEffect(() => {
dispatch(projectsInfoDetails(match.params.id));
dispatch(projectContacts(match.params.id));
dispatch(projectStaff(match.params.id));
}, [dispatch, match]);
const lastName = projectContact.map(l => l.contact.lastName);
const firstName = projectContact.map(f => f.contact.firstName);
const accountableStaffLastNames = accountableProjectStaff.flatMap(
({ accountableStaff }) => accountableStaff.map(data => data.lastName)
);
console.log(accountableStaffLastNames);
return (
<Fragment>
<div>
<SectionHeader sectionName='Project' />
<Button buttonName='Edit Project' />
</div>
{loading ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : (
<div style={{ backgroundColor: '#F8F8F8' }}>
<Table className='borderless'>
<tbody>
<tr>
<td>
<strong>Name: </strong>
{projects.name}
</td>
<td>
<strong>Status: </strong>
{projects.status}
</td>
</tr>
<tr>
<td>
<strong>State: </strong>
{projects.state}
</td>
<td>
<strong>County: </strong>
{projects.county}
</td>
</tr>
<tr>
<td>
<strong>Congressional District: </strong>
{projects.district}
</td>
<td>
<strong>Type: </strong>
{projects.type}
</td>
</tr>
<tr>
<td>
<strong>Funding Source: </strong>
<br />
{`${projects.fundingSource} ${projects.fundingSourceName}`}
</td>
<td>
<strong>Funding Source Goal: </strong>
<br />
{projects.fundingSourceGoal}
</td>
<td>
<strong>Start Date: </strong>
<br />
<Moment format='MM/DD/YYYY'>{projects.startDate}</Moment>
</td>
<td>
<strong>End Date: </strong>
<br />
{projects.endDate === null ? (
''
) : (
<Moment format='MM/DD/YYYY'>{projects.endDate}</Moment>
)}
</td>
<td>
<strong>Funding Percent: </strong>
<br />
{projects.fundingPercent}
</td>
</tr>
<tr>
<td>
<strong>Contact: </strong>
{firstName} {lastName}
</td>
</tr>
<tr>
<td>
<strong>Start Date: </strong>
<Moment format='MM/DD/YYYY'>
{projects.projectStartDate}
</Moment>
</td>
<td>
<strong>End Date: </strong>
{projects.projectEndDate === null ? (
''
) : (
<Moment format='MM/DD/YYYY'>
{projects.projectEndDate}
</Moment>
)}
</td>
</tr>
<tr>
<td colSpan='5'>
<strong>Goals and Objectives: </strong>
{projects.goalsAndObjectives}
</td>
</tr>
<tr>
<td colSpan='5'>
<strong>Success Description: </strong>
{projects.successDescription}
</td>
</tr>
<tr>
<td>
<strong>Accountable Staff: </strong>
</td>
</tr>
<tr>
<td>
<strong>Project ID: </strong>
{projects.projectId}
</td>
</tr>
</tbody>
</Table>
</div>
)}
<ActivitySummary />
</Fragment>
);
};
export default ProjectScreen;

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

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.

Resources