Editable table react - reactjs

I am trying to target a specific row to edit on my table in React.
My code looks something like this...
const [rowData, setRowData] = useState({ kind: { str: '', row: '' }});
const onChange = e => {
setRowData({...rowData, [e.target.name]: e.target.value }}
}
arr.map((ele, index) => (
<tr>
<td><input type='text' name='kind' value={kind.str} row={index} onChange={e => onChange(e)}></td>
</tr>
))
I didn't write all my code but I think that should be sufficient enough for an answer.
Basically I press a button edit that makes the row editable. When I press this button actually all rows and columns become editable which is not what I want but regardless of this outcome - I go to update the table and only that one input gets updated, unfortunately I get this error
A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
After reading the error I then decided to change my onChange function to this...
const onChange = e => {
setRowData({...rowData, [e.target.name]: { str: e.target.value, row: e.target.getAttribute('row')}}
}
the above solves the issue from making my controlled component becoming uncontrolled however all the input fields on each row that match the kind name get updated which is not the desired functionality I want.
How can I solve this issue. Eventually what I want is an editable table that the user can update a single row and update the database with the data they input.
The arr variable I am using is actually named files.
I have another component which gets all my files from my database and sets it to the files variable
const [files, setFiles] = useState([]);
useEffect(() => {
const fetchFiles = async () => {
setLoading(true);
const res = await fetch('http://localhost:5000/api/files/all', {
method: 'GET',
});
const data = await res.json();
setFiles(data);
setLoading(false);
};
fetchFiles();
}, []);
I then pass in as props files to my Items component which is where the code that I wrote for my question lives in.
Most of the code is here...
ShowList component
const ShowList = () => {
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [filesPerPage, setFilesPerPage] = useState(5);
const [yourUploads, setYourUploads] = useState(false);
useEffect(() => {
const fetchFiles = async () => {
setLoading(true);
const res = await fetch('http://localhost:5000/api/files/all', {
method: 'GET',
});
const data = await res.json();
setFiles(data);
setLoading(false);
};
fetchFiles();
}, []);
const indexOfLastPage = currentPage * filesPerPage;
const indexOfFirstPage = indexOfLastPage - filesPerPage;
const currentFiles = files.slice(indexOfFirstPage, indexOfLastPage);
const paginate = (pageNumber) => setCurrentPage(pageNumber);
return (
<Fragment>
<div className='container'>
<h3 className='text-center'>
{yourUploads ? 'Your uploads' : 'All uploads'}
</h3>
<div className='d-flex dropdown'>
<button
className='btn mb-3 mr-3'
type='button'
id='dropdownMenuButton'
data-toggle='dropdown'
aria-haspopup='true'
aria-expanded='false'
>
Pages per row {filesPerPage}
</button>
<div className='dropdown-menu' aria-labelledby='dropdownMenuButton'>
<button
className='dropdown-item'
onClick={() => setFilesPerPage(5)}
>
5
</button>
<button
className='dropdown-item'
onClick={() => setFilesPerPage(10)}
>
10
</button>
</div>
<div className='dropdown'>
<button
className='btn mb-3'
type='button'
id='dropdownUploadsButton'
data-toggle='dropdown'
aria-haspopup='true'
aria-expanded='false'
>
{yourUploads ? 'your uploads' : 'all uploads'}
</button>
<div
className='dropdown-menu'
aria-labelledby='dropdownUploadButton'
>
<button
className='dropdown-item'
onClick={() => setYourUploads(true)}
>
your uploads
</button>
<button
className='dropdown-item'
onClick={() => setYourUploads(false)}
>
all uploads
</button>
</div>
</div>
</div>
<table id='myTable' className='table table-striped w-100'>
<thead>
<tr>
<th scope='col'>
<small>Title</small>
</th>
<th scope='col'>
<small>Kind</small>
</th>
<th scope='col'>
<small>Size</small>
</th>
<th scope='col'>
<small>Strength</small>
</th>
<th scope='col'>
<small>Combinations</small>
</th>
<th scope='col'>
<small>Favors</small>
</th>
<th scope='col'>
<small>Stock</small>
</th>
<th scope='col'>
<small>Carousel</small>
</th>
<th scope='col'>
<small>Owner</small>
</th>
<th scope='col'>
<small>Edit</small>
</th>
<th scope='col'>
<small>Delete</small>
</th>
</tr>
</thead>
<Items
files={currentFiles}
loading={loading}
yourUploads={yourUploads}
/>
</table>
<Pagination
filesPerPage={filesPerPage}
totalFiles={files.length}
paginate={paginate}
/>
</div>
</Fragment>
);
};
Items component
const Items = ({ files, loading, yourUploads }) => {
const [myUploads, setMyUploads] = useState([]);
const [editable, setEditable] = useState(false);
const [rowData, setRowData] = useState({
kind: { str: '', row: '' },
});
const { kind } = rowData;
useEffect(() => {
const fetchMyUploads = async () => {
const res = await fetch('http://localhost:5000/api/files/all/mine', {
method: 'GET',
});
const data = await res.json();
setMyUploads(data);
};
fetchMyUploads();
}, [files]);
const onChange = (e) => {
setRowData({ ...rowData, [e.target.name]: e.target.value });
};
const onSubmit = async (e, file_id) => {
e.preventDefault();
if (!editable) {
setEditable(!editable);
} else {
await fetch(`http://localhost:5000/api/files/${file_id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(rowData),
});
setEditable(!editable);
}
};
if (loading) {
return (
<tbody>
<tr>
<td>loading...</td>
</tr>
</tbody>
);
}
const list = yourUploads
? myUploads.map((file) => (
<tr key={file._id}>
<td>
<small>{file.title}</small>
</td>
<td>
<small>{file.kind}</small>
</td>
<td>
<small>{file.size}</small>
</td>
<td>
<small>{file.strength}</small>
</td>
<td>
<small>{file.combinations}</small>
</td>
<td>
<small>{file.favors}</small>
</td>
<td>
<small
className={file.availability ? 'alert-success' : 'alert-danger'}
>
{file.availability ? 'in stock' : 'out of stock'}
</small>
</td>
<td>
<small>{file.isCarousel ? 'carousel' : 'not caorousel'}</small>
</td>
<td>
<small>{file.owner}</small>
</td>
<td>
<button className='btn btn-dark'>
<small>edit</small>
</button>
</td>
<td>
<button className='btn btn-danger'>
<small>delete</small>
</button>
</td>
</tr>
))
: files.map((file, index) => (
<tr key={file._id}>
<td>
<small>{file.title}</small>
</td>
<td>
<small>
{editable ? (
<input
type='text'
name='kind'
value={kind.str}
row={index}
onChange={(e) => onChange(e)}
/>
) : (
file.kind
)}
</small>
</td>
<td>
<small>{file.size}</small>
</td>
<td>
<small>{file.strength}</small>
</td>
<td>
<small>{file.combinations}</small>
</td>
<td>
<small>{file.favors}</small>
</td>
<td>
<small
className={file.availability ? 'alert-success' : 'alert-danger'}
>
{file.availability ? 'in stock' : 'out of stock'}
</small>
</td>
<td>
<small>{file.isCarousel ? 'carousel' : 'not carousel'}</small>
</td>
<td>
<small>{file.owner}</small>
</td>
<td>
<button
className='btn btn-dark'
onClick={(e) => onSubmit(e, file._id)}
>
<small>{editable ? 'save' : 'edit'}</small>
</button>
</td>
<td>
<button className='btn btn-danger'>
<small>delete</small>
</button>
</td>
</tr>
));
return <tbody>{list}</tbody>;
};

This error message usually comes up when you pass null or undefined to the input value prop. If value prop is empty, then react thinks it became uncontrolled component.
So you need to fix the error here.
arr.map((ele, index) => (
<tr>
<td><input type='text' name='kind' value={kind.str} row={index} onChange={e => onChange(e)}></td>
</tr>
))
Change value={kind.str} to value={rowData.kind.str}

Thanks everyone for answering my question. I was able to figure it out by splitting my components. So from my Items I render an Item component that renders the item and an Edit component that brings props in for that row only. By doing this I was able to eliminate the need for a row field which therefore gets rid of my error. React knows which row I am editing.

Related

How to check one item in a list of checkboxes in react?

I have a list of items from API with 2 checkboxes for each item. one checkbox for approve and the other for decline. But I encountered 2 problems, all the checkboxes are checked using the checked attribute.
function AllReferral() {
const [status, setStatus] = React.useState(false)
const handleChange = (item, checked) => {
setStatus((prevState) => !prevState);
checked
? setChecked((prev) => [
...prev,
{
serviceName: item?.label,
status: status,
},
])
: setChecked((prev) => [...prev, item].filter((c) => c !== item));
};
return (
<section>
<div>
{user?.services?.length > 0 && (
<div>
<table>
<thead>
<tr>
<th >Approve</th>
<th >Decline</th>
<th >Service Name</th>
</tr>
</thead>
{user?.services?.map((item, index) => (
<tbody key={index}>
<tr>
<td>
<input
name={item?.name}
type="checkbox"
checked={status}
onChange={(e) => handleChange(item, e.target.checked)}
/>
</td>
<td>
<input
name={item?.name}
type="checkbox"
checked={!status}
onChange={(e) => handleChange(item, e.target.checked)}
/>
</td>
<td>{item?.label}</td>
</tr>
</tbody>
))}
</table>
</div>
)}
</div>
</section>
);
}
from above implementation, if I have 4 items, all 4 is checked by default. I want each item in the list to be checked separately either approve or decline. Secondly I want the setChecked to return an array of both approved and declined item differentiated by status true or false.

Add and Edit do at one button React

I am beginner of react. I want to add and edit both operations at one button.
but it is not working. what i tried so far is the attached code along with the screenshot image.
I will able to add the records and able to view the records when i click the table row particular row record will be passing to form successfully. But I want edit and add the record of same button. i create the method save inside method I called save and edit functions
screenshot
this is the function i wrote the below
async function save(users)
import axios from 'axios';
import {useEffect, useState } from "react";
function EmployeeLoad()
{
const [name, setName] = useState("");
const [address, setAddress] = useState("");
const [mobile, setMobile] = useState("");
var currentEmployeeID = "";
const [users, setUsers] = useState([]);
useEffect(()=>
{
Load();
},[])
async function editEmployee(users)
{
setName(users.name);
setAddress(users.address);
setMobile(users.mobile);
currentEmployeeID = users.id;
console.log(users.id);
}
async function Load()
{
const result = await axios.get(
"http://127.0.0.1:8000/api/employees");
setUsers(result.data);
console.log(result.data);
}
async function save(users)
{
if(users.id == '')
{
await axios.post("http://127.0.0.1:8000/api/save",
{
name: name,
address: address,
mobile: mobile
}
);
alert("Employee Registation success");
}
else
{
alert("edit");
}
}
return (
<div>
<h1>Employee Details</h1>
<div class="container mt-4" >
<form>
<div class="form-group">
<label>employeeName</label>
<input type="text" class="form-control" id="employeeName"
value={name}
onChange={(event) =>
{
setName(event.target.value);
}}
/>
</div>
<div class="form-group">
<label>employeeAddress</label>
<input type="text" class="form-control" id="employeeAddress"
value={address}
onChange={(event) =>
{
setAddress(event.target.value);
}}
/>
</div>
<div class="form-group">
<label>Mobile</label>
<input type="text" class="form-control" id="employeeMobile"
value={mobile}
onChange={(event) =>
{
setMobile(event.target.value);
}}
/>
</div>
<button class="btn btn-primary mt-4" onClick={save}>Register</button>
</form>
</div>
<table class="table table-dark" align="center">
<thead>
<tr>
<th scope="col">Employee Id</th>
<th scope="col">Employee Name</th>
<th scope="col">Employee Address</th>
<th scope="col">Employee Mobile</th>
<th scope="col">Option</th>
</tr>
</thead>
{users.map(function fn(item)
{
return(
<tbody>
<tr>
<th scope="row">{item.id} </th>
<td>{item.name}</td>
<td>{item.address}</td>
<td>{item.mobile}</td>
<td>
<button type="button" class="btn btn-warning" onClick={() => editEmployee(item)} >Edit</button>
<button type="button" class="btn btn-dark" >Delete </button>
</td>
</tr>
</tbody>
);
})}
</table>
</div>
);
}
export default EmployeeLoad;
Inside your table use the currentEmployeeID and render a save button if you are in edit mode
{currentEmployeeID === item.id ? (
<button type="button" class="btn btn-warning" onClick={save} >Save</button> : (
<button type="button" class="btn btn-warning" onClick={() => editEmployee(item)} >Edit</button>
)}
So if this specific row is in edit mode, by pressing the button, it will save the entry, or else it will show "Edit". Dont forget to reset the currentEmployeeID when the save process completes.

How to enable optional chaining -React

I want to add a checkbox to each row in the table I have already created.
In addition, there should be a select all button and it should be able to select all rows.
I tried this but I'm getting the error
support for the experimental syntax 'optional chaining' isn't currently enabled.
import React, { useState, useEffect } from "react";
/* const userData = [{name: "Didem1"}, {name : "Didem2"}] */
const UserTable = (props) => {
const [users, setUsers] = useState([]);
const userData = [props.users];
useEffect(() => {
setUsers(userData);
}, []);
const handleChange = (e) => {
const { name, checked } = e.target;
if (name === "allSelect") {
let tempUser = users.map((user) => {
return { ...user, isChecked: checked };
});
setUsers(tempUser);
} else {
let tempUser = users.map((user) =>
user.name === name ? { ...user, isChecked: checked } : user
);
setUsers(tempUser);
}
};
return (
<table className="table table-dark">
<thead>
<tr>
<th scope="col">
<input
type="checkbox"
className="form-check-input"
name="allSelect"
onChange={handleChange}
checked={
users.filter((user) => user?.isChecked !== true).length < 1
}
/>
Select All
</th>
<th scope="col">Hostname</th>
<th scope="col">Username</th>
<th scope="col">Stop</th>
<th scope="col">Sleep</th>
<th scope="col">Start</th>
<th scope="col">Status</th>
<th scope="col">CPU Temperature(°C)</th>
<th scope="col">GPU Info</th>
<th scope="col">Edit/Delete</th>
</tr>
</thead>
<tbody>
{props.users.length > 0 ? (
props.users.map((user) => (
<tr key={user.id}>
<th scope="row">
<input
type="checkbox"
className="form-check-input"
/* user?.isChecked || false */
name={user.name}
checked={user?.isChecked || false}
onChange={handleChange}
/>
</th>
<td>{user.name}</td>
<td>{user.username}</td>
<td>
<button
onClick={() => props.editStopPC(user)}
className="btn btn-danger"
>
Stop
</button>
</td>
<td>
<button
onClick={() => props.editSleepPC(user)}
className="btn btn-warning"
>
Sleep
</button>
</td>
<td>
<button
onClick={() => props.editStartPC(user)}
className="btn btn-success"
>
Start
</button>
</td>
<td>{user.status}</td>
<td>{user.cpu}</td>
<td>{user.info}</td>
<td className="center-align">
<button
className="btn btn-info"
onClick={() => props.editRow(user)}
>
edit
</button>
<button
className="btn btn-danger"
onClick={() => props.deleteUser(user.id)}
>
delete
</button>
</td>
</tr>
))
) : (
<tr>
<td colSpan={9}>{props.users[0]} No Users</td>
</tr>
)}
</tbody>
</table>
);
};
export default UserTable;
So I installed react-scripts#3.3.0 and #babel/plugin-proposal-optional-chaining and now I am getting error:
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
I'm not sure what causes this. I would be glad if you help.

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

edit and update table cell dynamically in react

I am new to react. I want to achieve the below functionality in react for a grading table of students. How can I do that?
Please refer to the images.
Change the table cell into an input box
Editable cells in the table
Here is the snippet I have tried till now
Improved your code. you can change as per your requirement. Live demo
const Grades = () => {
const [grade, setGrade] = useState("");
const [data, setData] = useState([]);
const [showEdit, setShoEdit] = useState(0);
var count = 0;
const handleChange = (event) => {
setGrade(event.target.value);
};
const addGrade = () => {
setData([...data, { Grade: grade, id: Math.floor(Math.random()*100) }]);
setGrade("");
};
const editGrade = (row) => {
console.log(row)
setShoEdit(row.id);
setGrade(row.id);
}
const saveGrade = (row) => {
let update = data.map(list =>
list.id === row.id ? ({...list, Grade: grade}) : list
);
setData([...update]);
}
return (
<div className="container-xl">
<div className="form-group row">
<div className="col-md-1">
<label className="col-form-label ">Grade</label>
</div>
<div className="col-md-4">
<input
type="text"
value={grade}
className="form-control"
name="Grade"
onChange={handleChange}
/>
</div>
{!showEdit && <div className="form-group col-md-6">
<button
type="button"
className="btn btn-outline-primary"
onClick={addGrade}
>
Add Grade
</button>
</div>}
</div>
<div className="form-group row">
<table className="table">
<thead className="thead-dark">
<tr>
<th>#</th>
<th>Grades</th>
<th>Grade Id</th>
<th>Operations</th>
</tr>
</thead>
<tbody>
{data.map((row,i) => (
<tr key={row.id}>
<td>{++count}</td>
<td>{row.Grade}</td>
<td>{row.id}</td>
<td>
{!(row.id === showEdit) ? <button
onClick={()=> editGrade(row)}
key={row.id}
className="btn btn-outline-primary"
type="button"
>
Edit
</button>:
<button
type="button"
className="btn btn-outline-primary"
onClick={()=>saveGrade(row)}
>
Save
</button>}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default Grades;

Resources