React.js Tables showing key error in console - javascript

I rendered a table of inventory a small business carries (stored in JSON file).
I get this error in my console:
"Warning: Each child in a list should have a unique "key" prop.
Check the render method of Table
My App returns Table
<Table wines={wines}/>
My Table component:
import React from 'react'
import Row from './Row'
const Table = ({ wines,wine }) => {
return (
<div >
<table >
<tbody >
{wines.map(wine =>(
<Row wine={wine}/>
))}
</tbody>
</table>
</div>
)
}
export default Table
Row component:
import React from 'react'
import Cell from './Cell'
const Row = ({ wine }) => {
return (
<tr>
{Object.entries(wine).map(([key, value]) => {
return (
<Cell key={key} cellData={JSON.stringify(value)}/>
)
} ) }
</tr>
)
}
export default Row
Cell component:
import React from 'react'
const Cell = ({cellData,wine}) => {
return (
<td >
{cellData}
</td>
)
}
export default Cell
The table renders fine with the data, but I cannot understand why that error above still appears in the console. I am new to React and in the learning process. Thank you.

In your Table component, there is a key prop missing, eg:
{wines.map(wine =>(
<Row key={wine} wine={wine}/>
))}
It's important that the key prop is something unique to the item being iterated, as this is used to ensure the correct items are being updated, in the case where the component has to be re-rendered.

Related

Creating a table in react using multiple component calls not working

I am trying to create a dynamic table of a sudoku app I am working on but I am unable to see why this code wouldnt work.
my base app passes in a ref hook and sends the 9x9 array to the Grid component
return(
<Grid arr={sudokuBoard.current.board} />
)
Then in my grid app, it makes a call to the RowComponent using the array.map function
export default function Grid(props){
let sudokuArray = props.arr;
return(
<table>
{sudokuArray.map((row, ind)=> {
<RowComponent setkey={ind} rowval={row} />
})
}
</table>
)
}
Which then calls a tablecell generation function
export default function RowComponent(props){
return(
<tr key={props.setkey}>
{props.rowval.map((item,ind) => {
<TableItem keyval={item} value={item}/>
})}
</tr>
)
}
And the table cell generating component looks like this
export default function TableItem(props){
let tableValue = props.value
console.log(tableValue)
return(
<td>{props.value}</td>
)
}
Any pointers on what could be going wrong? The array gets passed into Grid just fine, but its in the chained calls where things stop working.
Thanks
In react/JSX you have to use ( ) around your code block inside the map, instead of { }. Like this:
{sudokuArray.map((row, ind)=> (
<RowComponent setkey={ind} rowval={row} />
))}
{props.rowval.map((item,ind) => (
<TableItem keyval={item} value={item}/>
))}
Difference between {} and () with .map with Reactjs this is an answer that explains why.

Cannot load table in React.js

I was experimenting with fragments and was trying to dynamically load data onto a table. I am not
getting any error but the table isn't displaying on the webpage. Please find my attached code
snippets below. There are 3 files called App.js, FragmentDemo.js (I've declared the table and the header here) and FragmentChild.js (The table data will be sent from an array of data)
//**App.js**
import logo from './logo.svg';
import './App.css';
import MountLife from './components/MountLifeCycle'
import Fragment from './components/Fragments/FragmentDemo'
function App() {
return (<div className="App">
<Fragment />
</div>)
}
export default App;
//**FragmentDemo.js**
import React from 'react'
import FragChild from './FragChild'
function FragmentDemo() {
return (
<div>
<table>
<tr>
<th>id</th>
<th>Name</th>
<th>Company</th>
</tr>
<FragChild />
</table>
</div>
)
}
export default FragmentDemo
//**FragChild.js**
import React from 'react'
function FragChild() {
const list = [{
id: 1,
name: "P1",
company:"Google"
},
{
id: 2,
name: "P2",
company:"Microsoft"
},
{
id: 3,
name: "P3",
company:"Uber"
}
]
const paramList = list.map( elem => (
<tr key={elem.id}>
<td>{list.id}</td>
<td>{list.name}</td>
<td>{list.company}</td>
</tr>))
return (
<React.Fragment>
{paramList}
</React.Fragment>
)
}
export default FragChild
In your paramList, you're referring to list.id, list.name, etc -- what you really want is elem.id, elem.name, etc:
const paramList = list.map( elem => (
<tr key={elem.id}>
<td>{elem.id}</td>
<td>{elem.name}</td>
<td>{elem.company}</td>
</tr>))
As an unrelated issue, you will get a warning that you should also have a tbody HTML element -- you should add that to your markup as well (but having it or not will not affect whether the table is rendered or not)
Change your paramList to be a function:
const paramList = () => list.map( elem => (
<tr key={elem.id}>
<td>{elem.id}</td>
<td>{elem.name}</td>
<td>{elem.company}</td>
</tr>))
Then just call it:
<React.Fragment>
{paramList()}
</React.Fragment>

useEffect not triggered by onChange

Im receving some products on props in the OrderContent component to use them in a select component, when I select the product in the select it renders Summary and Product components, in those components I can choose the quantity and with that I can calculate the total all back on the OrderContent Component, the problem is when im trying to use the OnChange in the input type (on Product component), useEffect (inside is the function that calculates the total in the state) doesnt trigger but it does if I add a product from the state or remove it.
import React, { Fragment, useState, useEffect } from "react";
import Select from "react-select";
import Animated from "react-select/lib/animated";
import Summary from './Summary';
function OrderContent({ products }) {
const [productsSelected,setProductsSelected] = useState([]);
const [total,setTotal] = useState(0);
useEffect(() => {
updateTotal()
}, [productsSelected]);
const selectProduct = (prod)=>{
setProductsSelected(prod)
}
const updateQuantity = (val,index)=>{
const tempProds = productsSelected;
tempProds[index].quantity= Number(val);
setProductsSelected(tempProds)
}
const deleteProduct = (id) =>{
const tempProds = productsSelected;
const remProds = tempProds.filter((p)=> p.id !== id );
setProductsSelected(remProds);
}
const updateTotal = () =>{
const tempProds = productsSelected;
if(tempProds.length === 0){
setTotal(0)
return;
}
let newTotal = 0;
tempProds.map((p)=>{
const q = p.quantity ? p.quantity : 0;
newTotal = newTotal + (q * p.price)
})
setTotal(newTotal)
}
return (
<Fragment>
<h2 className="text-center mb-5">Select Products</h2>
<Select
onChange={selectProduct}
options={products}
isMulti={true}
components={Animated()}
placeholder={"Select products"}
getOptionValue={options => options.id}
getOptionLabel={options => options.name}
value={productsSelected}
/>
<Summary
products={productsSelected}
updateQuantity={updateQuantity}
deleteProduct = {deleteProduct}
/>
<p className="font-weight-bold float-right mt-3">
Total:
<span className="font-weight-normal">
${total}
</span>
</p>
</Fragment>
);
}
export default OrderContent;
import React, {Fragment} from 'react';
import Product from './Product';
function Summary({products,updateQuantity,deleteProduct}) {
if(products.length === 0) return null;
return (
<Fragment>
<h2 className="text-center my-5">Summary and Quantities</h2>
<table className="table">
<thead className="bg-success text-light">
<tr className="font-weight-bold">
<th>Product</th>
<th>Price</th>
<th>Inventory</th>
<th>Quantity</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{products.map((p,index)=>{
return (<Product
key={p.id}
id={p.id}
product={p}
index={index}
updateQuantity={updateQuantity}
deleteProduct={deleteProduct}
/>)
})}
</tbody>
</table>
</Fragment>
)
}
export default Summary
import React, { Fragment } from "react";
function Product({ product, updateQuantity, index, deleteProduct }) {
return (
<Fragment>
<tr>
<td>{product.name}</td>
<td>${product.price}</td>
<td>{product.stock}</td>
<td>
<input
type="number"
className="form-control"
onChange={e => updateQuantity(e.target.value, index)}
/>
</td>
<td>
<button type="button" className="btn btn-danger font-weight-bold" onClick={e=> deleteProduct(product.id)}>
× Delete
</button>
</td>
</tr>
</Fragment>
);
}
export default Product;
updateQuantity is mutating state. This means that react will see that you've tried to update state with the same object reference and the re-render will be skipped, meaning no useEffect triggers.
Change it to this to create a new array with new nested objects:
const updateQuantity = (val,index)=>{
const tempProds = [...productsSelected.map(val => {...val})];
tempProds[index].quantity= Number(val);
setProductsSelected(tempProds)
}
deleteProduct doesn't mutate because filter returns a new array. But setting the tempProds is completely unnecessary.
updateTotal also mutates state, but only its nested objects. So this still needs to be fixed, but will probably not cause the same re-render issue.
Based on the use of const tempProds = productsSelected in several places, I think you should do some research on how JavaScript objects are assigned and referenced. There's plenty of resources out there, but I wrote a pretty detailed explanation as part of this answer.
If productsSelected is the same array then useEffect can't detect the change because it's always pointing to the same object
const selectProduct = (prod)=>{
setProductsSelected([...prod])
}
To force the product selected to be a new array

How to link sort function from header icon to the column to be sorted in React

I am having problem with sorting table column in React. My table composes of three components: one defining the individual row (row.js), one rendering headers and mapping the rows that need own states (rows.js) and finally table.js that renders the whole thing. The data of the table comes from the database.
Here is a part of the row.js:
class ProjectTableProjectRow extends Component {
render() {
const { project } = this.props;
return (
<tr>
<td className="projects">
<Body2>
<Link to={`/projects/${project.id}`}>{project.description}</Link>
</Body2>
</td>
export default withRouter(ProjectTableProjectRow);
And here an excerpt from my rows.js:
class ProjectTableProjectRows extends Component {
componentDidMount() {
this.props.projects.getAll(); // This gets all the projects from the store
}
onSortProjects = () => {
let sortedToBe = this.props.projects.list.map(project => project.description);
const sorted = sortedToBe.sort();
};
render() {
return (
<table>
<thead>
<tr>
<th>
<Caption>Project</Caption>
<IconButton onClick={() => this.onSortProjects()}>
<RowsIcon />
</IconButton>
</th>
</tr>
</thead>
{this.props.projects.list.map(project => (
<tbody key={project.id}>
<ProjectTableProjectRow project={project} />
</tbody>
))}
</table>
);
}
}
export default ProjectTableProjectRows;
Finally, there is a projectTable.js (that I am not sure if I need anyway...)
class ProjectTable extends Component {
render() {
return (
<>
<ProjectTableProjectRows projects={this.props.projects} />
</>
);
}
}
export default ProjectTable;
So, I'd like to sort the project.description column (there are project names as strings) in alphabetical order. Naturally the icon and sort function onClick in it is not connected to the table column, so the sort function does nothing to the table. How can I achieve this? I do not know yet how to think "in React".
This is obviously not tested, I'm just making a couple of updates to the code you had but this will allow you to use the sorted values.
class ProjectTableProjectRows extends Component {
constructor(props) {
super(props)
this.state = {
projects: null
}
}
componentDidMount() {
const projects = this.props.projects.getAll();
this.setState({ projects })
}
onSortProjects = () => {
let sortedToBe = this.state.projects.list.map(project => project.description);
const sorted = sortedToBe.sort();
this.setState({ projects: sorted });
};
render() {
return (
<table>
<thead>
<tr>
<th>
<Caption>Project</Caption>
<IconButton onClick={() => this.onSortProjects()}>
<RowsIcon />
</IconButton>
</th>
</tr>
</thead>
{this.state.projects && this.state.projects.list.map(project => (
<tbody key={project.id}>
<ProjectTableProjectRow project={project} />
</tbody>
))}
</table>
);
}
}
export default ProjectTableProjectRows;

ReactJS - reading json data

I am trying to populate table data by importing json. However, getting below error:
Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys { list }). If you meant to render a collection of children, use an array instead.
Below is the code I am using:
import reg from "./data/reg.json";
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<React.Fragment>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</React.Fragment>
</tr>
)//return
})//regionslist
return (
{ list }
);//return
} //render
} //class
class DTable extends Component {
render() {
return (
<Table striped bordered hover>
<TableHeader />
<tbody>
<TableRow data={this.props.data} />
</tbody>
</Table>
);
}
}
class DataTable extends Component {
render() {
return (
<DTable data={reg} />
);//return
} //render
}
Problem
I recreated your setup trying to reproduce your error. You can see what I reproduced here: Stackblitz. The issue seems to lie in the fact that your map creates some table rows, but there is no parent element to wrap them, which is required by JSX.
Solution
This can be fixed by wrapping {list} inside of a React.Fragment:
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</tr>
)//return
})//regionslist
return (
<React.Fragment>
{ list }
</React.Fragment>
);//return
} //render
} //class
Adding the React.Fragment around the list items resolved the issue. You can see the working solution here: Stackblitz
Alternatives
As of React 16.2 (Read more) you also have the option to return empty JSX tags instead of typing out React.Fragment. The solution above would look like this:
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</tr>
)//return
})//regionslist
return (
<>
{ list }
</>
);//return
} //render
} //class
Lastly it is also possible to return an array of elements since React 16.0. If you would choose to do this the above code would look like this then:
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</tr>
)//return
})//regionslist
return [list];//return
} //render
} //class
A limitation of this option is however that a key has to be added to the items as React will otherwise throw this warning in the console:
Warning: Each child in a list should have a unique "key" prop.
Note that even if you omit this key it will still render properly.
Initially, there was a limitation in React that you can only return a single element from render function but it has been changed after React 16, Now you can return the array of elements.
In your case, you are trying to array of the element without mentioning that it's an array due to which you have to group it using Fragment.
This issue can be solved in two ways:
Wrap array inside Fragment or any other container element (But in that case it will add one extra node to the DOM which is bad and to solve that issue Fragment's are introduced. for more details check here: https://reactjs.org/docs/fragments.html)
return (
<React.Fragment>
{ list }
</React.Fragment>
);
Return an array.
return ([ list ]);

Categories