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 ]);
Related
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.
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;
Backstory
Note: This question is an expansion to the answer T.J. Crowder provided here.
I expanded on this by creating a state to hold the rows array so that I could update the array (remove from) using setState.
I added a handleClick function to handle what I would like the user to be able to do to the array based on the index of the element being clicked. (currently all that is included is that right click removes the target index from the array.
/rowList.js
import React, { Component } from "react";
import Row0 from "./../rows/row0";
import Row1 from "./../rows/row1";
import Row2 from "./../rows/row2";
import Row3 from "./../rows/row3";
const row0 = () => <Row0 />;
const row1 = () => <Row1 />;
const row2 = () => <Row2 />;
const row3 = () => <Row3 />;
class RowInfo {
static id = 0;
constructor(Comp) {
this.Comp = Comp;
this.id = RowInfo.id++;
}
}
class RowList extends Component {
constructor() {
super();
this.state = {
rows: [
new RowInfo(row0),
new RowInfo(row1),
new RowInfo(row2),
new RowInfo(row3)
]
};
}
handleClick = (a, b) => e => {
e.preventDefault();
if (e.nativeEvent.which === 1) {
//left click
console.log(a); //for testing
console.log(b); //for testing
} else if (e.nativeEvent.which === 3) {
//right click
this.setState({
rows: this.state.rows.filter((_, i) => i !== a)
});
}
};
render() {
return (
<>
{this.state.rows.map(({ id, Comp }) => (
<tr
key={id}
onClick={this.handleClick(id)}
onContextMenu={this.handleClick(id)}
>
<Comp />
</tr>
))}
</>
);
}
}
export default RowList;
I then tested calling the <RowList /> component twice so that I can test removing rows across two components.
Note: Mod0 is imported into a /main.js file which is imported to /index.js which is rendered to <div id="root"></div> in index.html
/mod0.js
import React, { Component } from "react";
import RowList from "./../rows/rowList";
class Mod0 extends Component {
render() {
return (
<>
<table>
<tbody>
<RowList />
</tbody>
</table>
<table>
<tbody>
<RowList />
</tbody>
</table>
</>
);
}
}
Problem
When testing the removal I realised a crucial error, the nature of which my current knowledge can only make me relatively certain of so my explanation may be innacurate/flawed. It would seem I can only remove from the first 4 rows that are rendered in <RowList /> as the second 4 rows do not corrospond to the array in this.state.rows.
I think the solution is to build a new array based on the original state array and the amount of times <RowList /> is called. And render that array instead.
Perhaps I could update the state could look something like this to begin with:
constructor() {
super();
this.state = {
rows: [
new RowInfo(row0),
new RowInfo(row1),
new RowInfo(row2),
new RowInfo(row3)
],
rendRows: this.rendRowsTemp
};
this.rendRowsTemp = []; //push to build a new array into here?
}
And then use the new array instead like so:
render() {
return (
<>
{this.state.newRows.map(({ id, Comp }) => (
<tr
key={id}
onClick={this.handleClick(id)}
onContextMenu={this.handleClick(id)}
>
<Comp />
</tr>
))}
</>
);
}
Expected Result
I need a method to build the new array based on the original state array and the amount of times <RowList /> is called.
I think the problem is that in the filter's condition (this.state.rows.filter((_, i) => i !== a)) you're using the RowInfo's index in the array not it's ID, try: this.state.rows.filter(({ id }) => id !== a)
I am learning programming and have an issue. I would like to iterate and loop (i think are the terms) a row. The row will contain 3 columns who's data comes from a REST api. Once the row has 3 columns fulled, I would like a new row to be created with 3 more columns.
What is the easiest way to achieve this thanks.
My code is here https://jsfiddle.net/Ory4n/8k7dabyr/1/ and I found a possible solution code here https://jsfiddle.net/dya52m8y/2/ however I do not know how to implement that code into my own project as of yet.
class Test extends Component {
constructor(){
super();
this.state = {
pages: []
}
}
componentDidMount() {
let pagesURL = "http://creartem.nz/wp-json/wp/v2/projects";
fetch(pagesURL)
.then(response => response.json())
.then(response => {
this.setState({
pages: response
})
})
}
render() {
let pages = this.state.pages.map((page, index) => {
return (
{/* Start Loop */}
<div key={index}>
<h2>{page.title.rendered}</h2>
<p>{page.acf.technologies}</p>
<p>{page.acf.team}</p>
</div>
{/* end Loop */}
)
})
return (
<section>
{pages}
</section>
);
}
}
export default Test;
If you want to render an array of components, you must call a function that in turn returns a call to the .map() method you had specified. See below:
render() {
const renderPages = () => {
return this.state.pages.map((page, index) => {
return (
<div key={index}>
<h2>{page.title.rendered}</h2>
<p>{page.acf.technologies}</p>
<p>{page.acf.team}</p>
</div>
);
})
}
return (
<section>
{renderPages()}
</section>
);
}
I could be wrong, but if I understand correctly, you would like to create a new row containing a h2 and two p tags. You could try using a table to achieve this output. Something like:
const renderPages = () => {
return (
<div>
<table>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
{this.state.pages.map(page => {
return (
<tr key={page.id}>
<td><h2>{page.title.rendered}</h2></td>
<td><p>{page.acf.technologies}</p></td>
<td><p>{page.acf.team}</p></td>
</tr>
)
})}
</table>
</div>
)
}
Also, avoid using index as your key. It causes weird issues. Use something like an id.
Here is a similar example on codepen: https://codepen.io/mkempinsky/pen/gBapqg?editors=1111
Hi I want to have multiple TRs and inside one, have multiple TDs using react I want to loop through my comparedProperties object and create the table in render method dynamically but I get this error:
Uncaught Error: Objects are not valid as a React child (found: object with keys {id, address, long, lat, cityId, cityDistrict, phone,
name, userId, city}). If you meant to render a collection of
children, use an array instead or wrap the object using
createFragment(object) from the React add-ons. Check the render method
of Comparison.
my data object is like this and I can not change its structure:
//this is a samle data, keys in this object can dynamically change elsewhere
let comparedProperties = {
id: [1001,1002],
address: ["abc","def"],
};
this is my code:
class Comparison extends Component {
render() {
let comparedProperties = {
id: [1001, 1002],
address: ["abc", "def"]
};
let comparedItemsData = [];
for (var key in comparedProperties) {
if (comparedProperties.hasOwnProperty(key)) {
let newTR = <tr key={Math.random()} className="compare-table-row">
<td className="table-item-header">
{key}
</td>
{comparedProperties[key].map((item) => {
return <td key={Math.random()} className="table-item">{item}</td>;
})}
</tr>;
comparedItemsData.push(newTR)
}
}
return (
<table className="compare-table">
<tbody>
{comparedItemsData}
</tbody>
</table>
)
}
}
const mapStateToProps = (state) => ({
...state
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(Actions, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Comparison);
update answer:
so I figuerd where the problem was but I expexted better error message from react
the problem was that in my comparedProperties I had an object inside the array that caused the error
let comparedProperties = {"id":[101,102],"estateAgency":[{"id":1},{"id":2}]}
Are you trying to do something like that ?
render(){
let comparedProperties = {
id: [1001, 1002],
address: ["abc", "def"],
};
return (
<table>
{Object.keys(comparedProperties).map(key=>(
<tr key={Math.random()} className="compare-table-row">
<td className="table-item-header">
{key}
</td>
{comparedProperties[key].map((item) => (
<td key={Math.random()} className="table-item">{item}</td>
))}
</tr>
))}
</table>
)
}
Or if you want to try as a a stateless comp you insert in your table :
const ComparedItemsData = ({ comparedProperties }) =>(
<React.Fragment>
{Object.keys(comparedProperties).map(key => (
<tr key={Math.random()} className="compare-table-row">
<td className="table-item-header">{key}</td>
{comparedProperties[key].map(item => (
<td key={Math.random()} className="table-item">
{item}
</td>
))}
</tr>
))}
</React.Fragment>
)
const App = ()=>{
let comparedProperties = {
id: [1001, 1002],
address: ["abc", "def"]
};
return (
<table className="compare-table">
<tbody>
<ComparedItemsData comparedProperties={comparedProperties}/>
</tbody>
</table>
)
}
You just need to return the td elements from within map function. Also never specify Math.random() as a key to the react elements because everytime render is called a new key will be assigned and it will force React to re-render the entire component even though nothing would have changed.
for (var key in comparedProperties) {
if (comparedProperties.hasOwnProperty(key)) {
let newTR = <tr key={key} className="compare-table-row">
<td className="table-item-header">
{key}
</td>
//comparedProperties[key] is an array of
// values that I want to make them as td elements
{ comparedProperties[key].map((item) => {
return <td key={item} className="table-item">{item}</td>;
})}
</tr>;
comparedItemsData.push(newTR)
}
}