I need to build a table in order to organize some data. I'm using the "onclick" function in order to call a separate function, which is supposed to increment a state variable up by one. My Chrome Devtools isn't giving me any errors but also isn't updating the stock variable. I'm not sure how to get the state to update and display. Here's my source code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: [
{
manufacturer: "Toyota",
model: "Rav4",
year: 2008,
stock: 3,
price: 8500
},
{
manufacturer: "Toyota",
model: "Camry",
year: 2009,
stock: 2,
price: 6500
},
{
manufacturer: "Toyota",
model: "Tacoma",
year: 2016,
stock: 1,
price: 22000
},
{
manufacturer: "BMW",
model: "i3",
year: 2012,
stock: 5,
price: 12000
},
]
};
this.renderCar = this.renderRow.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return {stock: this.stock + 1}
})
}
renderRow(car, index) {
return (
<tr key={index}>
<td key={car.manufacturer}>{car.manufacturer}</td>
<td key={car.model}>{car.model}</td>
<td key={car.year}>{car.year}</td>
<td key={car.stock}>{car.stock}</td>
<td key={car.price}>${car.price}.00</td>
<td key={index}><input type="button" onClick={car.handleClick} value="Increment" /></td>
</tr>
)
}
render() {
return (
<div>
<table>
<thead>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Option</th>
</tr>
</thead>
<tbody>
{this.state.cars.map(this.renderRow)}
</tbody>
</table>
</div>
);
};
}
ReactDOM.render(<App />, document.getElementById("app"))
I'd make a separate component for the row, so that you can easily update that component to increment the stock value in state:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: [
{
manufacturer: "Toyota",
model: "Rav4",
year: 2008,
stock: 3,
price: 8500
},
{
manufacturer: "Toyota",
model: "Camry",
year: 2009,
stock: 2,
price: 6500
},
{
manufacturer: "Toyota",
model: "Tacoma",
year: 2016,
stock: 1,
price: 22000
},
{
manufacturer: "BMW",
model: "i3",
year: 2012,
stock: 5,
price: 12000
},
]
};
}
render() {
return (
<div>
<table>
<thead>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Option</th>
</tr>
</thead>
<tbody>
{this.state.cars.map(
(car, i) => <Row car={car} key={i} />
)}
</tbody>
</table>
</div>
);
};
}
const Row = ({ car }) => {
const [stock, setStock] = React.useState(car.stock);
return (
<tr>
<td>{car.manufacturer}</td>
<td>{car.model}</td>
<td>{car.year}</td>
<td>{stock}</td>
<td>${car.price}.00</td>
<td><input type="button" onClick={() => setStock(stock + 1)} value="Increment" /></td>
</tr>
);
}
ReactDOM.render(<App />, document.getElementById("app"))
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id='app'></div>
You could put it all in one component if you had to, but it'd be a bit cumbersome. While rendering, you'd have to keep track of the render index of a row and pass that along to the click handler, then immutably update the stock property in the state array at that index. A separate component is easier.
handleClick(e){
const index = Number(e.currentTarget.value);
this.setState(this.state.cars.map(car, i)=> {
return i === index ? {...car, stock: car.stock + 1}: car
})
}
renderRow(){
....
<input type="button" onClick={this.handleClick} value={index} />
...
}
Related
I can't figure out how to get more values to show in my table using REACT. The only thing that I am able to show is the cost. I can't show Manufacturer and Item. So When I choose Iphone12 - Manufacturer will be "Apple" and Item will be "iPhone 12" & Galaxy S21 will be "Android" and "Galaxy 21",from the data structure in index.
This is my Index.js :
import React from "react";
import ReactDOM from "react-dom";
import "./Home.css";
import "bootstrap/dist/css/bootstrap.min.css";
import MTWork from "./MTWork";
let inventory = [
{ id: 0, manufact: "None", item: "None", descr: "None", avail: 0, price: 0 },
{
id: 100,
manufact: "Samsung",
item: "Galaxy S22",
descr: "The Lastest Phone",
avail: 22,
price: 1399.99,
},
{
id: 101,
manufact: "Samsung",
item: "Galaxy S21",
descr: "Recently refurbished like new",
avail: 5,
price: 699.99,
},
{
id: 102,
manufact: "Samsung",
item: "Galaxy S20",
descr: "Great phone works well",
avail: 3,
price: 399.99,
},
{
id: 103,
manufact: "Apple",
item: "iPhone 13",
descr: "New and Shiny, Nothing better",
avail: 13,
price: 1299.99,
},
{
id: 104,
manufact: "Apple",
item: "iPhone 12",
descr: "Refurbished but perfect",
avail: 13,
price: 899.99,
},
{
id: 105,
manufact: "Apple",
item: "iPhone 11",
descr: "Works great!",
avail: 12,
price: 599.99,
},
];
let warranty = [
{ id: 0, plan: "None", cost: 0 },
{ id: 1, plan: "Extended", cost: 0.15 },
{ id: 2, plan: "Normal", cost: 0.1 },
{ id: 3, plan: "Base", cost: 0.05 },
];
let title = "Teds Technology Treasures";
ReactDOM.render(
<React.StrictMode>
<MTWork title={title} phones={inventory} wrnty={warranty} />
</React.StrictMode>,
document.getElementById("root")
);
THIS IS MY MTWork.js
import { useState } from "react";
function MTWork(props) {
const { phones, title, wrnty } = props;
const [name, setName] = useState("Kels");
const [itm, setAndroid] = useState();
const [Atem, setApple] = useState();
const [Warrant, setWarranty] = useState(0);
const [total, setTotal] = useState(0);
function updateName(n) {
setName(n.target.value);
}
function updateAndroid(a) {
setAndroid(a.target.value);
}
function updateApple(ap) {
setApple(ap.target.value);
}
function updateWarranty(w) {
setWarranty(w.target.value);
}
function doOrder() {
let total = 0;
let total2 = parseFloat(itm) + parseFloat(Atem);
let wTotal = total2 * parseFloat(Warrant);
total += total2 + wTotal;
return setTotal(total);
}
return (
<div>
<div className="container-fluid">
<div className="row">
<div className="col">
<h1>
<span className="title">{title}</span>
</h1>
</div>
</div>
<div className="row">
<div className="col-4">
Name: <input type="text" value={name} onChange={updateName} />
<br />
Android Phone:
<select onChange={updateAndroid} value={itm}>
{phones.map((phn) => (
<option key={phn.id} value={phn.price}>
{phn.item}
</option>
))}
</select>
<br />
Apple Phone:
<select onChange={updateApple} value={Atem}>
{phones.map((Apn) => (
<option key={Apn.id} value={Apn.price}>
{Apn.item}
</option>
))}
</select>
<br />
Warranty:
<select onChange={updateWarranty} value={Warrant}>
{wrnty.map((wrn) => (
<option key={wrn.id} value={wrn.cost}>
{wrn.plan}
</option>
))}
</select>
<br />
<button className="btn btn-dark" onClick={() => doOrder()}>
Order
</button>
<h2 style={{ color: "blue" }}>Results For Name: {name}</h2>
<table className="table">
<tbody className="color">
<tr>
<th>Manufacturer:</th>
<th>Item:</th>
<th>Cost:</th>
</tr>
<tr>
<td scope="row">{phones.maunfact}</td>
<td scope="row">{phones.price}</td>
<td scope="row">{itm}</td>
</tr>
<tr>
<td scope="row">{phones.manufact}</td>
<td scope="row">{phones.price}</td>
<td scope="row">{Atem}</td>
</tr>
<tr>
<td>Warranty</td>
<td scope="row">{props.plan}</td>
<td scope="row">{Warrant}%</td>
</tr>
<tr>
<td scope="row">Total </td>
<td scope="row"></td>
<td scope="row">{total} </td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
export default MTWork;
You are storing the price in your selection state.
You should instead store the selection by the id (which will be unique).
That way you can always find the item in your phones/inventory array by id and then get any of the properties (manufact, item, price, desc, avail).
function MTWork(props) {
const { phones } = props;
const [selectedId, setSelectedId] = useState(); // store id
function updatePhone(event) {
setSelectedId(event.target.value);
}
const selectedPhone = phones.find((phone) => phone.id == selectedId) || {};
return (
<div>
Phone:
<select onChange={updatePhone} value={selectedId}>
{phones.map((phone) => (
<option key={phone.id} value={phone.id}>
{phone.item}
</option>
))}
</select>
<table>
<tbody>
<tr>
<th>manufact:</th>
<th>item:</th>
<th>price:</th>
<th>desc:</th>
<th>avail:</th>
</tr>
<tr>
<td>{selectedPhone?.manufact}</td>
<td>{selectedPhone?.item}</td>
<td>{selectedPhone?.price}</td>
<td>{selectedPhone?.descr}</td>
<td>{selectedPhone?.avail}</td>
</tr>
</tbody>
</table>
</div>
);
}
const selectedPhone = phones.find((phone) => phone.id == selectedId) || {};
Uses Array.find on the phones array to get the phone that has the same id as selectedId. If it can't find a match it will return undefined, so we assign a value of empty object {} to selectedPhone.
Im getting the warning Warning: Each child in a list should have a unique "key" prop.. My understanding of react was that when you pass to a new component you give it the Id of a prop, which i am doing, yet im still getting this warning. In my code i have some dummy data that is used to set the state of table data i.e
const DumData =
{ id: 1,
UserGroup:[
{
id: "1",
Name: "Dax Johnson",
AddressLine: "82 Mahu road",
Email: 'DaxIng#Gmail.co.nz',
ContactNumber: "02791743",
Zip: '8801',
Notes: 'His dog is swag',
Animals: [
{ id: "1",
PatientID: "23",
AnimalName: 'SwagDog',
Species: "Canine",
Breed: "Dog",
Gender: "Male",
DOB: "",
Vists: [{
id:1 ,
time: 'October 31st 2021'
},
{
id:2 ,
time: 'October 21st 2021'
}]
},
{ id: '2',
AnimalName: 'CoolCat',
Species: "Feline",
Breed: "Cat",
Gender: "Female",
DOB: "",
Vists: [{
id:1 ,
time: 'March 4th 2021'
}]
}
],
},
{
id: "12",
Name: "Willam McDonald",
AddressLine: "2 Wacky Ave",
Email: 'WILLIAM#hotmail.co.nz',
Zip: '8661',
ContactNumber: "033777300",
Notes: 'His cat is cool',
Animals: [
{
id: "1",
PatientID: "23",
AnimalName: "Molder",
Species: "Feline",
Breed: "Cat",
Gender: "Female",
DOB: "2008",
Vists: [{
id:1 ,
time: 'February 4th 2022'
}]
}
],
},
{
id: "3",
Name: "Oscar Issac",
AddressLine: "2 Wacky Ave",
Email: 'Oscar#hotmail.co.nz',
Zip: '7041',
ContactNumber: "0279000",
Notes: 'His cat is cool',
Animals: [
{
id: "1",
PatientID: "23",
AnimalName: "Cool cat",
Species: "Feline",
Breed: "Cat",
Gender: "Female",
DOB: "2008",
Vists: [{
id:1 ,
time: 'June 24th 2021'
}]
}
],
} ]
};
and then later const [tableData, settableData] = useState(DumData);
I create a component table called Hometable where i pass it the tableData and the key id
<div className='Hometable-div'>
<Hometable
data={tableData}
key={tableData.id}
></Hometable>
</div>
and then i map the data so it is displayed in the table in the Hometable component. like so
function Hometable(props) {
var OwnerName;
var Animalname;
var breed;
return (
<div className='table-container'>
<table>
<thead>
<tr>
<th>Owner</th>
<th>Animal Name</th>
<th>Type/Breed</th>
<th>Vist Time</th>
</tr>
</thead>
<tbody>
{props.data.UserGroup.map((person) => (
OwnerName = person.Name,
person.Animals.map((Animal) => (
Animalname = Animal.AnimalName,
breed = Animal.Breed,
Animal.Vists.map((vist) => (
<tr>
<td> <i class="bi bi-person-fill"></i> {OwnerName} </td>
<td> {Animalname}</td>
<td> {breed} </td>
<td> {vist.time} </td>
</tr>
))
))
))}
<tr>
<td className='footer'>
</td>
<td className='footer'>
</td>
<td className='footer'>
</td>
<td className='footer'>
<button className='TableButton'> Page 1</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
export default Hometable;
I understand i dont use the key value in Hometable so this might be an easy fix if anyone can help me resolve this warning?
Try passing the key here in the code
{props.data.UserGroup.map((person) => (
OwnerName = person.Name,
person.Animals.map((Animal) => (
Animalname = Animal.AnimalName,
breed = Animal.Breed,
Animal.Vists.map((vist, index) => (
// or visit.id if available
<tr key={index}>
<td> <i class="bi bi-person-fill"></i> {OwnerName} </td>
<td> {Animalname}</td>
<td> {breed} </td>
<td> {vist.time} </td>
</tr>
))
))
))}
It's recommended to use keys coming from the data source such as visit.id. Last resort should be using index. For more information you can read here.
Try using this code :
function Hometable(props) {
var OwnerName;
var Animalname;
var breed;
return (
<div className='table-container'>
<table>
<thead>
<tr>
<th>Owner</th>
<th>Animal Name</th>
<th>Type/Breed</th>
<th>Vist Time</th>
</tr>
</thead>
<tbody>
{props.data.UserGroup.map((person) => (
OwnerName = person.Name,
person.Animals.map((Animal) => (
Animalname = Animal.AnimalName,
breed = Animal.Breed,
Animal.Vists.map((vist,index) => (
<tr key={index} >
<td> <i class="bi bi-person-fill"></i> {OwnerName} </td>
<td> {Animalname}</td>
<td> {breed} </td>
<td> {vist.time} </td>
</tr>
))
))
))}
<tr>
<td className='footer'>
</td>
<td className='footer'>
</td>
<td className='footer'>
</td>
<td className='footer'>
<button className='TableButton'> Page 1</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
export default Hometable;
In my opinion:
const array = [...];
...
const new_array_for_props = array.map(function(arr, index){
<target_comp props_01={arr.props_01} key={index} />
});
sub component(target_comp) unnecessary uses props.key.
Every time you are in a loop, each element like those ones must define a unique key. And it is a good practice
- tr
- ul
- Component
- etc ...
ex:
Users?.map( (user: User) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.birthdate}</td>
<td>
<UserEdit key={user.id} user={user}>Edit</UserEdit>
</td>
...
</tr>
));
I currently have a vue component and template in which I'm listing employees and their hours/scans by date. The problem is my current map is totaling all hours and scans by the first record and it's date.
I need to modify this because my table headers are dates (today, tomorrow and the day after). So I need to be able to use a v-if statement for each to compare the date in the column header to the date of the record. In this instance, I should only have one record for employee A123 but I should have 2 records for employee D432 because the two records for that employee have different dates.
How can I also factor date into the unique mapping here?
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
rows: [{
employee: "A123",
hours: "15",
date: "2021-08-31",
scans: "4"
},
{
employee: "A123",
hours: "25",
date: "2021-08-31",
scans: "4"
},
{
employee: "D432",
hours: "82",
date: "2021-09-02",
scans: "2"
},
{
employee: "D432",
hours: "40",
date: "2021-09-01",
scans: "5"
}
]
},
methods: {
groupByField(list, field) {
const result = {};
list.forEach(item => {
const value = item[field];
if (value) {
if (!result[value]) {
result[value] = [];
}
result[value].push(item);
}
});
return result;
}
},
computed: {
compRows() {
const a = this.groupByField(this.rows, 'employee');
let b = Object.values(a)
return b.map(item => {
return {
employee: item[0].employee,
hours: item.reduce((acc, _item) => (+acc) + (+_item.hours), 0),
scans: item.reduce((acc, _item) => (+acc) + (+_item.scans), 0),
date: item[0].date
}
})
}
}
});
th,td{
padding:8px
}
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app" class="container">
<table class="table">
<thead>
<tr>
<th>Employee</th>
<th>hours</th>
<th>scans</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in compRows">
<td>{{row.employee}}</td>
<td>{{row.hours}}</td>
<td>{{row.scans}}</td>
<td>{{row.date}}</td>
</tr>
</tbody>
</table>
</div>
You can group based on employee and then on date and sum up date and scans in array#reduce.
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
rows: [{employee: "A123", hours: "15", date: "2021-08-31", scans: "4" }, { employee: "A123", hours: "25", date: "2021-08-31", scans: "4" }, { employee: "D432", hours: "82", date: "2021-09-02", scans: "2" }, { employee: "D432",hours: "40",date: "2021-09-01",scans: "5"}]
},
computed: {
compRows() {
const grouped = this.rows.reduce((r, o) => {
r[o.employee] ??= {};
r[o.employee][o.date] ??= {employee: o.employee, date: o.date, scans: 0, hours: 0};
r[o.employee][o.date].scans += +o.scans;
r[o.employee][o.date].hours += +o.hours;
return r;
}, {});
return Object.values(grouped).map(o => Object.values(o)).flat();
}
}
});
th,td{
padding:8px
}
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app" class="container">
<table class="table">
<thead>
<tr>
<th>Employee</th>
<th>hours</th>
<th>scans</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in compRows">
<td>{{row.employee}}</td>
<td>{{row.hours}}</td>
<td>{{row.scans}}</td>
<td>{{row.date}}</td>
</tr>
</tbody>
</table>
</div>
I am using React for my app, I am fetching data from an API at http://localhost:8080/api/suppliers/supplier/list
Here is the data structure that I am getting in the Google Chrome console:
0:{
id: 4
supplierFirstname: "Tom"
supplierLastName: "ABC"
supplierTitle: "TomTheSupplier"
accountNumber: 1122234444
address: "111 ADrive, 1234 ST."
companyName: "TheTom Company & Associates"
email: "tomtomjayjay#email.com"
hourlyRate: 29
phoneNumber: 123456789
otherPhoneNumber: 1023456789
paymentTerms: "Credit"
notes: "Some Supplier"
createdAt: null
typeOfGoods: "Supplies"
website: "www.abc_123.com"
products: [{…}]
components:
[
0: {id: 5, name: "AComponent", unit: null, quantity: 0, componentCost: 0, …}
]
}
Here is my code:
class SupplierData {
constructor(props){
super(props);
this.state = {
supplier: [
{
id: 0,
supplierTitle: "Supplier Title",
supplierFirstName: "First Name",
supplierLastName: "Last Name",
accountNumber: 1122234444,
address: "",
companyName: "",
email: "tomtomjayjay#email.com",
hourlyRate: 29,
phoneNumber: 123456789
otherPhoneNumber: 1023456789
paymentTerms: "Credit"
notes: "Some Supplier"
createdAt: null
typeOfGoods: "Supplies"
website: "www.abc_123.com"
products: [{…}]
components:
[
0: {id: 5, name: "AComponent", unit: null, quantity: 0,
componentCost: 0, …}
]
},
],
errorMessage: [],
};
}
ListAllSuppliers = async () =>{
return await axios.get(`http://localhost:8080/api/suppliers/supplier/list`)
.then((response) =>{
let apiResults = response.data;
this.setState({supplier: apiResults});
}).catch((error) =>{
this.setState({errorMessage: this.state.errorMessage.push(error)});
});
}
TableRow = ({id, supplierTitle, supplierFirstName, supplierLastName}) => {
return (
<tr>
<td>
<span className="fw-normal">
{id}
</span>
</td>
<td>
<span className="fw-normal">
{supplierTitle}
</span>
</td>
<td>
<span className="fw-normal">
{supplierFirstName}
</span>
</td>
<td>
<span className="fw-normal">
{supplierLastName}
</span>
</td>
</tr>
);
};
render(){
return(
<table>
<thead>
<tr>
<th className="border-bottom">#ID</th>
<th className="border-bottom">Title</th>
<th className="border-bottom">FirstName</th>
<th className="border-bottom">LastName</th>
</tr>
</thead>
<tbody>
{this.state.supplier.map((t) => this.TableRow(t))}
</tbody>
</table>
);
}
I am getting the following error, here is what it states:
Error: Objects are not valid as a React child (found: object with keys {id, name, productComponents,
quantity, productCost, productPrice, createdAt, status}). If you meant to render a collection of
children, use an array instead.
in span (at SupplierData.js:158)
in td (at SupplierData.js:157)
in tr (at SupplierData.js:78)
in tbody (at SupplierData.js:259)
why is it pointing out such error .
My Question:
What is a possible fix to the above error ?
You forgot to close the tr tag:
return (
<tr>
<td>
<span className="fw-normal">
{id}
</span>
</td>
<td>
<span className="fw-normal">
{supplierTitle}
</span>
</td>
<td>
<span className="fw-normal">
{supplierFirstName}
</span>
</td>
<td>
<span className="fw-normal">
{supplierLastName}
</span>
</td>
</tr>
);
I try to create a table with two arrays in a state(week_today, week_count) and a repeating sentence when rendering.
I don't know how to use map() function . Please advise.
my code
render() {
const {week_today, week_count} = this.state; // this.state in my array put
return (
<div>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">today</th>
<th scope="col">count</th>
</tr>
</thead>
<tbody>
//<== here my array table
</tbody>
</table>
</div>
)
}
use my arrays
week_today = [ 01,02,03,04,05]
week_count = [ 1,2,3,4,5]
Try below. Just a sample code
constructor(props) {
super(props);
const week_today = ['01', '02', '03', '04', '05'];
const week_count = [1, 2, 3, 4, 5];
this.state = {
week_today: week_today,
week_count: week_count
};
}
render() {
const {week_today, week_count} = this.state; // suggest you to use destructuring your variables:
return (
<div>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">today</th>
<th scope="col">count</th>
</tr>
</thead>
<tbody>
{week_today.map((today, index) =>
<tr key={index}>
<td>{today}</td>
<td>{week_count[index]}</td>
</tr>
)}
</tbody>
</table>
</div>
)
}
Slightly changed the data structure (add this to your state):
week: [
{ today: '01', count: 1 },
{ today: '02', count: 2 },
{ today: '03', count: 3 },
{ today: '04', count: 4 },
{ today: '05', count: 5 }
]
And add this to your render:
<tbody>
{this.state.week.map( element => {
return (
<tr>
<td>
{element.today}
</td>
<td>
{element.count}
</td>
</tr>
)
})
}
</tbody>