I'm very new to React, and I'm trying to make a table using react that displays information about a car. Currently, I'm trying to implement two buttons on each row that would increment/decrement a certain field of the table.
This is the code for the table:
render() {
return (
<table>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Options</th>
<th></th>
</tr>
{
this.state.cars.map(car =>
<tr key={car.model}>
<td>{car.manufacturer}</td>
<td>{car.model}</td>
<td>{car.year}</td>
<td>{car.stock}</td>
<td>${car.price}</td>
<td><button type="button" onClick={this.increaseStock.bind(this)}>Increment</button></td>
<td><button type="button" onClick={this.decreaseStock.bind(this)}>Decrement</button></td>
</tr>
))
}
</table>
);
};
This so far is just fine for displaying the data and buttons, however, I'm not actually sure how to implement the buttons' functions
All the implementations for increaseStock and decreaseStock so far have done nothing on click, although I think they're being called. I'm trying to get it to either add to the stock value or subtract from it(not going below 0).
I've tried things using states, but I'm not really sure how those work.
Here's an example of how your component could be implemented. I used the functional component and a state hook because those are what I'm familiar with.
import React, { useState } from "react";
export default function CarTable() {
const [cars, setCars] = useState([
{ id: 1, name: "Car 1", stock: 5 },
{ id: 2, name: "Car 2", stock: 10 }
]);
const increase = (index) => {
const oldCar = cars[index];
// copying the car with the updated stock
const newCar = { ...oldCar, stock: oldCar.stock + 1 };
//copying the car array
const newCars = [...cars];
//replacing the old car with a new one in a new array
newCars[index] = newCar;
//updating state
setCars(newCars);
};
const decrease = (index) => {
// same as above except decreasing the stock now
const oldCar = cars[index];
const newCar = { ...oldCar, stock: oldCar.stock - 1 };
const newCars = [...cars];
newCars[index] = newCar;
setCars(newCars);
};
return (
<table>
<thead>
<tr>
<th>name</th>
<th>stock</th>
<th></th>
</tr>
</thead>
<tbody>
{cars.map((car, i) => (
<tr key={car.id}>
<td>{car.name}</td>
<td>{car.stock}</td>
<td>
<button onClick={() => increase(i)}>increase</button>
<button onClick={() => decrease(i)}>decrease</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
In short what you need to do is create a new car object with the updated stock property. Then create a new cars array with the new car object just mentioned and then set the state to this new cars array.
Here's also a CodeSandbox link where you can try it out:
https://codesandbox.io/s/charming-dijkstra-8brqz1?file=/src/CarTable.js
Hope that helps.
Related
I am trying to make form project. When person submmit to personal info to form. I want add this info to Table. But I get this error when I am adding info to table.
Uncaught Error: Objects are not valid as a React child (found: object with keys {inputValue}). If you meant to render a collection of children, use an array instead.
I know this is related to const newToTable part. But I don't know how to make it right. Please help me, ıf you know how can ı solve this problem.
This is my contact.js
const buttonOnClick = () => {
if (inputValue === "" || emailValue === "" || phoneNumberValue === "") {
setShowModal(false);
} else {
setShowModal(true);
// setInputValue("");
//setEmailValue("");
//setPhoneValue("");
// sa
}
const newToTable = [...addFormData, { fullName:{inputValue},email:{emailValue}, phoneNumber:{phoneNumberValue},country:{countryValue} }];
setAddFormData(newToTable);
console.log(`Form submitted, ${showModal}`);
}
This is my AddTable.js
<div className="app-container">
<form>
<Table>
<thead>
<tr>
<th>Full Name</th>
<th>Email </th>
<th>Phone Number</th>
<th>Country</th>
</tr>
</thead>
<tbody>
{addFormData.map((addForm) => (
<tr>
<td>{addForm.fullName}</td>
<td>{addForm.email}</td>
<td>{addForm.phoneNumber}</td>
<td>{addForm.counry}</td>
</tr>
))}
</tbody>
</Table>
</form>
</div>
You've accidently wrapped your form values in objects here:
const newToTable = [...addFormData, { fullName:{inputValue},email:{emailValue}, phoneNumber:{phoneNumberValue},country:{countryValue} }];
For example fullName:{inputValue} will evalute to fullName: { inputValue: 'the value' }
Instead you need:
const newToTable = [
...addFormData,
{
fullName: inputValue,
email: emailValue,
phoneNumber: phoneNumberValue,
country: countryValue,
},
];
This is what the error means by Objects are not valid as a React child - when it tries to render your table, the values being passed are objects such as { inputValue: 'the value' } (this is the (found: object with keys {inputValue}) part of the error - an object with inputValue as a key).
I have an object:
{
cities: {
MCR: "Manchester",
LDN: "London",
NYC: "New York"
}
}
and I want to iterate over these and display them in a table format which will show the city code and the long name next to it. Something like:
<tbody>
<tr>
<th>City Code</th>
<th>City Name</th>
</tr>
<tr>
{Object.entries(cities).map(([key, value]) => {
<>
<td>{key}</td>
<td>{value}</td>
</>
}
)}
</tr>
</tbody>
However this doesn't display anything and also shows an error when hovering over value:
Type 'unknown' is not assignable to type 'ReactNode'.ts(2322)
I'm quite new to React and wondering how best to display this information?
Thanks
let say you have an object as:
const obj = {
cities: {
MCR: "Manchester",
LDN: "London",
NYC: "New York"
}
};
It would be better to loop over cities and create a new tr so that It looks like a table
CODESANDBOX LINK
You want to map over the cities then you should return it from map as:
<tbody>
<tr>
<th>City Code</th>
<th>City Name</th>
</tr>
{ Object.entries( obj.cities ).map( ( [key, value] ) => {
return ( // PROBLEM SHOULD RETURN
<tr>
<td>{ key }</td>
<td>{ value }</td>
</tr>
);
}
) }
</tbody>
You need to return the result in each iteration. Also, each iteration would return a tr with a key prop:
{
Object.entries(obj.cities).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
))
}
Note: since city is a key in an object, you'll need to access it as above
You are just missing the return in your map's callback function. Always remember to include a return in your arrow functions when using it like this: () => {}. It is easy to miss because ()=>() would return whatever you write in the second () but in the former example this is not true.
In my React Table, I have a row with checkboxes. Each time I check any of the boxes, I want to send the Id of that particular item to an empty array and remove from the same array when I uncheck. I tried to get this done but what I have presently is not working as expected. At first click, it returns an empty array but add to the array on second click. To remove from the array also, I need to click twice. Link to codesandbox Codesanboxlink
Here is what I have done so far with it
import "./styles.css";
import { useState } from "react";
export default function App() {
const Defaultdata = [
{
date_listed: "4 hours ago",
id: "7857699961",
delivery_time_frame: "2021-10-25 - 2021-11-14",
distance: "22.8 km",
time_stamp: "2021-10-25 16:36:54",
watched: "yes"
},
{
date_listed: "3 days ago",
id: "8358962006",
delivery_time_frame: "2021-10-18 - 2021-10-24",
distance: "4.3 km",
time_stamp: "2021-10-22 16:54:12"
},
{
date_listed: "4 hours ago",
id: "8146462294",
delivery_time_frame: "2021-10-25",
distance: "4.3 km",
time_stamp: "2021-10-25 16:12:32"
}
];
const [newdatatoshow, setnewdatatoshow] = useState([]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<table>
<thead>
<th></th>
<th>Item 1</th>
<th>Item 2</th>
<th>Item 3</th>
<th>Item 4</th>
</thead>
<tbody>
{Defaultdata.map((item, index) => {
return (
<tr key={index}>
<td>
{" "}
<span
onClick={(e) => {
setnewdatatoshow([...newdatatoshow, item.id]);
if (!e.target.checked) {
setnewdatatoshow(newdatatoshow.filter(function(n){ return n !== item.id }))
//setnewdatatoshow(newdatatoshow.splice(newdatatoshow.findIndex(data) => data === item.id))
}
console.log(newdatatoshow);
}}
>
{" "}
<input type="checkbox" />{" "}
</span>{" "}
</td>
<td>{item.id}</td>
<td>{item.distance}</td>
<td>{item.date_listed}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
Setting value to useState is an asynchronous operation.You need to wait till the value is set.
One way to do this is from an useEffect
useEffect(() => {
console.log(newdatatoshow)
}, [newdatatoshow])
You can't access the updated state from the current state. See useState set method not reflecting change immediately for more info.
What you can do is trigger an effect when a state variable gets updated and print your updated array there
useEffect(() => {
console.log(newdatatoshow)
}, [newdatatoshow])
For more details check this Array not updating on first click
I created the table I mentioned below using React js. When I click on the button below the table, I want to add a new row to the table. I have listed the react code I wrote below. how can I do that?
My React Code
const PPP13 = (props) => {
return (
<Jumbotron>
<p className="btn-group">13- List all owners of 20% or more of the equity of the Applicant</p>
<Table striped bordered hover>
<thead>
<tr>
<th>Owner Name</th>
<th>Title</th>
<th>Ownership %</th>
<th>TIN (EIN, SSN)</th>
<th>Address</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<FormControl aria-label="DDD"/>
</td>
<td>
<FormControl aria-label="DDD"/>
</td>
<td>
<FormControl aria-label="DDD"/>
</td>
<td>
<FormControl aria-label="DDD"/>
</td>
<td>
<FormControl aria-label="DDD"/>
</td>
</tr>
</tbody>
</Table>
<Button className="btn-group" name="add" value="No">
Add more owners
</Button>
</Jumbotron>
)
}
Here is what you can do. Lets say you have a Main component which will get all details.
class Products extends React.Component {
constructor(props) {
super(props);
// this.state.products = [];
this.state = {};
this.state.filterText = "";
this.state.products = [
{
id: 1,
category: 'Sporting Goods',
price: '49.99',
qty: 12,
name: 'football'
}, {
id: 2,
category: 'Sporting Goods',
price: '9.99',
qty: 15,
name: 'baseball'
}, {
id: 3,
category: 'Sporting Goods',
price: '29.99',
qty: 14,
name: 'basketball'
}, {
id: 4,
category: 'Electronics',
price: '99.99',
qty: 34,
name: 'iPod Touch'
}, {
id: 5,
category: 'Electronics',
price: '399.99',
qty: 12,
name: 'iPhone 5'
}, {
id: 6,
category: 'Electronics',
price: '199.99',
qty: 23,
name: 'nexus 7'
}
];
}
handleAddEvent(evt) {
var id = (+ new Date() + Math.floor(Math.random() * 999999)).toString(36);
var product = {
id: id,
name: "empty row",
price: "mpty row",
category: "mpty row",
qty: 0
}
this.state.products.push(product);
this.setState(this.state.products);
}
handleProductTable(evt) {
var item = {
id: evt.target.id,
name: evt.target.name,
value: evt.target.value
};
var products = this.state.products.slice();
var newProducts = products.map(function(product) {
for (var key in product) {
if (key == item.name && product.id == item.id) {
product[key] = item.value;
}
}
return product;
});
this.setState({products:newProducts});
};
render() {
return (
<div>
<ProductTable onProductTableUpdate={this.handleProductTable.bind(this)} onRowAdd={this.handleAddEvent.bind(this)} products={this.state.products} />
</div>
);
}
}
This contains the code for adding row.Then for the table do something like this.
class ProductTable extends React.Component {
render() {
var onProductTableUpdate = this.props.onProductTableUpdate;
var product = this.props.products.map(function(product) {
return (<ProductRow onProductTableUpdate={onProductTableUpdate} product={product} key={product.id}/>)
});
return (
<div>
<button type="button" onClick={this.props.onRowAdd} className="btn btn-success pull-right">Add</button>
<table className="table table-bordered">
<thead>
<tr>
<th>Name</th>
<th>price</th>
<th>quantity</th>
<th>category</th>
</tr>
</thead>
<tbody>
{product}
</tbody>
</table>
</div>
);
}
}
Now for the row Comoponent:
class ProductRow extends React.Component {
render() {
return (
<tr className="eachRow">
<td>
{this.props.product.id}
</td>
<td>
{this.props.product.price}
</td>
<td>
{this.props.product.qty}
</td>
<td>
{this.props.product.category}
</td>
</tr>
);
}
}
Working Example:
https://jsfiddle.net/mrAhmedkhan/nvgozjhy/
Ok here's my plan:
First we create a state to hold all the data for the table. I've used an object instead of an array as it's much easier to do the change handling. With arrays you always end up doing all this awkward splicing. You can always parse the object out into an array when you're ready to use it elsewhere.
Then we render out each row of the table by mapping over the entries in our table state. Note we also write the change handler inside the map, meaning we can easily use the rowId (tableData key) to set our new state when a change comes in.
Finally we plop in a button to add more rows. This has a click handler associated with it (handleAddRowClick) which counts the number of rows we have and uses this to generate a new rowId. We use the new rowId to expand the tableData state to include a new defaultRow. I defined defaultRow outside of the function, this prevents it from being redeclared on every render.
import React, { useState } from 'react'
import { Table, Input, Button } from 'reactstrap'
const defautRow = { colA: '', colB: '' }
const IncreasableTable = props => {
const [tableData, setTableData] = useState({
row1: { colA: '', colB: '' }
})
const handleAddRowClick = () => {
const extantRowsCount = Object.keys(tableData).length
setTableData(s => ({
...s,
[`row${extantRowsCount}`]: defautRow
}))
}
return (
<>
<Table>
{
Object.entries(tableData).map(([rowId, data]) => {
const handleChange = ({ target: { name, value } }) => {
setTableData(s => ({
...s,
[rowId]: {
...s[rowId],
[name]: value
}
}))
}
return (
<tr key={rowId}>
<td>
<Input name="colA" value={data.colA} onChange={handleChange}/>
<Input name="colB" value={data.colB} onChange={handleChange}/>
</td>
</tr>
)
})
}
</Table>
<Button onClick={handleAddRowClick}>Click me to add more rows</Button>
</>
)
}
export default IncreasableTable
I am using react-accessible-accordion. I want to populate the innermost child component with the data received as props from the parent component.
To do so, I am doing something like below in my respective components:
Parent Component —
import React from 'react';
import ReactDOM from 'react-dom';
import ChildAccordion1 from './ChildAccordion1'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
class ParentAccordion extends React.Component {
getMappedData = () =>{
const myObj = [
{"Name":"John Doe", "City": "Chicago","ID":1,"address": "207 Mills Town"},
{"Name":"Ramph Brown", "City": "LA","ID":2,"address":"508 Mills Town"}
];
if (myObj) {
return myObj.map(item =>{
console.log(item);
return (
<Accordion>
<AccordionItem>
<AccordionItemTitle>
</AccordionItemTitle>
<AccordionItemBody>
<ChildAccordion1 {...item} />
</AccordionItemBody>
</AccordionItem>
</Accordion>
)
})
}
else {
return "";
}
}
render(){
return (
// RENDER THE COMPONENT
<div>
{this.getMappedData()}
</div>
);
}
}
export default ParentAccordion
Here, if you see I am passing only item as props to my child component i.e only objects.
Child Component1 —
import React from 'react';
import ReactDOM from 'react-dom';
import InnerMostChildComp from './InnerMostChildComp'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
class ChildAccordion1 extends React.Component {
render(){
const propToChild = this.props;
return (
// RENDER THE COMPONENT
<Accordion>
<AccordionItem>
<AccordionItemTitle>
</AccordionItemTitle>
<AccordionItemBody>
<table className="accordionTable">
<thead className="row">
<th className="col-sm-6">Name</th>
<th className="col-sm-6">City</th>
</thead>
<tbody>
<tr className="row">
<td className="col-sm-6">Name</td>
<td className="col-sm-6">City</td>
</tr>
</tbody>
</table>
<InnerMostChildComp propFromParent = {propToChild}/>
</AccordionItemBody>
</AccordionItem>
</Accordion>
);
}
}
export default ChildAccordion1
InnerMost Child Component —
import React from 'react';
import ReactDOM from 'react-dom';
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
const InnerMostChildComp = (props) => {
return (
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h4>List</h4>
</AccordionItemTitle>
<AccordionItemBody>
<table className="accordionTable">
<thead className="row">
<th className="col-sm-2">Name </th>
<th className="col-sm-2">City </th>
<th className="col-sm-2">Id </th>
</thead>
<tbody>
{
Object.keys(props.propFromParent).map((key, index) => {
console.log(key, index);
return (
<tr className="row">
<td className="col-sm-2">{props.propFromParent[key].Name}</td>
<td className="col-sm-2">{props.propFromParent[key].City}</td>
<td className="col-sm-2">{props.propFromParent[key].Id}</td>
</tr>
)
})
}
</tbody>
</table>
</AccordionItemBody>
</AccordionItem>
</Accordion>
)
};
export default InnerMostChildComp
I want each tr to have the selected attributes from the objects index wise so to do so, I am using Object.keys as the props we get in child is always a component.
Problem I am facing is that in my InnerMostChildComp, in my props.propsfromParent, I get first object of an array i.e object related to ID 1 and then when I put debugger on td, it appears that instead of iterating through the number of objects I have in my array which is 2, it is iterating over all the individual attributes(keys and values) of my first object and it prints all the keys and values. Then the control goes back again to props.propsfromParent where my second object is displayed and again the iteration happens inside the same object over the keys and values which is not what I want. For instance, when I debug my InnerMostChildComp, Object.keys iterates over each attributes of the first object instead of iterating over both the objects. My props.propFromParent does show the second object after the iteration of first object gets completed.Probably I am not using the Object.keys right way or the problem is with my props.propFromParent.
EDIT:
It is hard to understand what you want to get in the result, so to keep it simple, your table (presumably) should look like the following:
const myObj = [
{ Name: 'John Doe', City: 'Chicago', ID: 1 },
{ Name: 'Ramph Brown', City: 'LA', ID: 2 } // Note `Name` here instead of your `accessKey`
];
<table className="accordionTable">
<thead className="row">
<th className="col-sm-2">Name</th>
<th className="col-sm-2">City</th>
<th className="col-sm-2">Id</th>
</thead>
<tbody>
{myObj.map(item => {
return (
<tr className="row">
<td className="col-sm-2">{item['Name']}</td>
<td className="col-sm-2">{item['City']}</td>
<td className="col-sm-2">{item['ID']}</td>
</tr>
)
})}
</tbody>
</table>
EDIT 2:
So, propFromParent is equal to {Name: "John Doe", City: "Chicago", ID: 1}. Therefore, this:
<tbody>
{
Object.keys(props.propFromParent).map((key, index) => {
console.log(key, index);
return (
<tr className="row">
<td className="col-sm-2">{props.propFromParent[key].Name}</td>
<td className="col-sm-2">{props.propFromParent[key].City}</td>
<td className="col-sm-2">{props.propFromParent[key].Id}</td>
</tr>
)
})
}
</tbody>
should be changed to this:
<tbody>
<tr className="row">
<td className="col-sm-2">{props.propFromParent.Name}</td>
<td className="col-sm-2">{props.propFromParent.City}</td>
<td className="col-sm-2">{props.propFromParent.ID}</td>
</tr>
</tbody>
This will fix the table in the nested accordion, but still, you'll have two accordion components per row (so, you'll always have one row per table per accordion (as layout built)).
EDIT 3:
In result, you should get rid of first iteration and just pass entire array into the Child Accordion component like <ChildAccordion1 myObj={myObj} /> and then just iterate it in the InnerMostChildComp with
<tbody>
{
props.propFromParent.myObj.map(item => {
return (
<tr className="row">
<td className="col-sm-2">{item.Name}</td>
<td className="col-sm-2">{item.City}</td>
<td className="col-sm-2">{item.ID}</td>
</tr>
)
})
}
</tbod