Rendering multiple Table Rows with multiple Table Data in table using ReactJS - javascript

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

Related

Make a table and render the data inside a table using React

I'm trying to create a dynamic table so that every time I press the "add" button, a new row will be created.
In my example below, it creates only 1 row with all the data under its header, but with all the 3 values in this case instead of create a new row.
I'm fetching the document from Firestore and the collection includes only 1 document with an array inside, in this case with 3 values.
here is the screenshot of the table it creates:
Expecteed table example:
Code:
export default class NutritionTableOfTheUser extends Component {
constructor() {
super();
this.state = {
tableData: []
}
}
componentDidMount() {
const dbRef = collection(db, 'data');
onSnapshot(dbRef, (querySnapshot) => {
let foods = [];
querySnapshot.forEach(doc => {
foods.push(doc.data())
});
this.setState({ tableData: foods })
})
}
render() {
return (
<div className='container mt-3'>
{/* table */}
<table className='table table-hover'>
<thead>
<tr>
<th>#</th>
<th>Food</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
{this.state.tableData.map((row, index) => {
return (
<tr>
<td>{index + 1}</td>
<td>{row.value}</td>
<td>{row.quantity}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}
}
You should check the data you are getting from firebase collection, because you might have only one document in collection which results in rendering only one row in table.
It looks like you only have a single document, and your .value and .quantity properties of the row are arrays (rather than numbers) - so doing <td>{row.value}</td> mashes all values of the array together.
Turn the .data() into the desired data format first, then set the state with it.
querySnapshot.forEach(doc => {
const [obj] = doc.data();
const tableData = obj.value.map((value, i) => ({
value,
quantity: obj.quantity[i]
}));
this.setState({ tableData })
});

React JS map function

I have the following code and I'm trying to simplify it using a map function, perhaps on the array: const columns = ['Title', 'Author', 'Rating']
export const BookshelfListRow = (props) => {
return (
<tr className="table-row" >
<td>
<input onChange={(e) => { props.Update(e.target.value) }} placeholder={props.book.Title} />
</td>
<td>
<input onChange={(e) => { props.Update(props.book.Title, e.target.value) }} placeholder={props.book.Author} />
</td>
<td>
<input onChange={(e) => { props.Update(props.book.Title, props.book.Author, e.target.value) }} placeholder={props.book.Rating} />
</td>
</tr>
)}
Please note this is simplified - in my actual code I have 30 columns (meaning 30 separate inputs instead of 3) hence why I'm looking for a way to simplify it as it is currently really long - so essentially what is happening above is the placeholder is iterating through the array [Title,Author,Rating], and simultaneously on each new line we are adding an item from the array (in the form of props.book[item]) to the props.Update function. Any ideas how I could use a map function to carry this out?
You can use map to simplify it. The tricky bit will be the calling of Update with different number of parameters, but that too can be achieved using another map.
const columns = ['Title', 'Author', 'Rating'];
export const BookshelfListRow = (props) => {
return (
<tr className="table-row">
{
columns.map((column, i) => (
<td>
<input onChange={ e =>
props.Update(...[ // the parameters to Update consist of
...columns.slice(0, i).map(column => props.book[column]), // the column values from the start until the current column, map is used here to get the values for those columns
e.target.value // and the input value
])
}
placeholder={ props.book[column] } />
</td>
))
}
</tr>
)
}
Another approach:
The Update function is a mess. It can be a lot simpler if it just takes the column that was changed and the value as there is no need for it to send all those props back to the server if only one was changed, like so (this uses computed property names):
const Update = (column, value) => // takes the column that was changed and the value
axios.put('http://localhost:4001/books/update', { [column]: value }); // update only that column
Then the rendering will be much simpler also, like so:
const columns = ['Title', 'Author', 'Rating'];
export const BookshelfListRow = (props) => {
return (
<tr className="table-row">
{
columns.map((column, i) => (
<td>
<input onChange={ e => props.Update(column, e.target.value) } placeholder={ props.book[column] } />
</td>
))
}
</tr>
)
}
If you're using the keys of props.book, you can try something like this:
import React from "react";
const BookshelfListRow = props => {
const args = [];
return (
<tr className="table-row">
{Object.keys(props.book).map((key, idx) => {
if(idx > 0) {
args.unshift(key);
}
const argsCopy = [...args];
return (
<td>
<input
onChange={e => {
props.Update(...argsCopy, e.target.value);
}}
placeholder={props.book[key]}
/>
</td>
);
})}
</tr>
);
};
export default BookshelfListRow;
Otherwise, you can use an array like the one you suggested (const columns = ['Title', 'Author', 'Rating']) and take each value and add it to a copy with each map loop.
Mapping is a very powerful tool in React. It is most useful when you are try to DRY out some repeated code. In your case you are trying to DRY out your td's by mapping over the array columns.
Your columns array will need a little more info to make mapping useful. For instance,
const columns = ['Title', 'Author', 'Rating']
columns.map(column => console.log(column)) // Title, Author, Rating
That's not very helpful for your td because it needs the onChange, and placeholder and both require more information than just the strings 'Title', 'Author', and 'Rating'.
From what I can tell, your book prop is an object that looks something like this:
book: {
Title: 'some string',
Author: 'some other string',
Rating: 'some number maybe'
}
You can map over that by using Object.keys. But again, that only helps with the placeholder not the onChange.
The data that you have and the data you are trying to use for your inputs do not seem to have a common enough pattern to utilize map here.
Possible Solution
Modify your update function to not require so many parameters to keep the input field generic as possible that way you can map over your columns.
export const BookshelfListRow = (props) => {
// if you are using React without hooks, just replace this with
// normal state
const [state, setState] = useState({
title: '',
author: '',
rating: ''
})
const update = (e) => {
const input = e.currentTarget.value;
const attribute = e.currentTarget.name;
setState({...state, [attribute]: input})
}
const apiRequest = (e) => {
e.preventDefault();
// your state is now your request body
request(state)
}
const columns = ['Title', 'Author', 'Rating']
return (
<tr className="table-row" >
{columns.map(column => (
<td key={column}>
<input name={column.toLowerCase()} onChange={update} placeholder={column} />
</td>
))}
</tr>
)}
const columns = ['Title', 'Author', 'Rating']
const update = (val, column) => {
console.log(`${column}: ${val}`)
}
const BookshelfListRow = () => (<table><tbody><tr className="table-row">{
columns.map((column, i) => {
return (<td key={i}><input type="text" onChange = {e => update(e.target.value, column)} placeholder={column} /></td >)
})
}</tr></tbody></table>
)
ReactDOM.render(
<BookshelfListRow />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

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: How to get the data of a table row that has changed

I have a main Table component that maintains the table's state. I have a dumb component which gets props from the main component. I use it to render the table row layout. I am trying to make this table editable. For this reason, I need a way to find out which tr was edited. Is there a way to get access to the tr key using which I can get access to the whole object?
No you can't get the value of a key in a child prop. From the docs:
Keys serve as a hint to React but they don’t get passed to your
components. If you need the same value in your component, pass it
explicitly as a prop with a different name
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
A possible solution right of my head might be the following:
import React from 'react';
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [
{
id: 0,
title: "ABC"
},
{
id: 1,
title: "DEF"
},
{
id: 2,
title: "GHI"
}
]
}
}
render() {
return <table>
<tbody>
{
this.state.rows.map((item) => <Row key={item.id} item={item} updateItem={this.updateItem} />)
}
</tbody>
</table>
}
updateItem = (newItemData) => {
const index = this.state.rows.findIndex((r) => r.id == newItemData.id);
let updatedRows = this.state.rows;
updatedRows.splice(index, 1, newItemData);
this.setState({
rows: updatedRows
});
}
}
const Row = ({item, updateItem}) => {
const [title, setValue] = React.useState(item.title);
return <tr>
<td>{item.id}</td>
<td>
<input type="text" value={title} onChange={(e) => setValue(e.currentTarget.value)} />
</td>
<td>
<button onClick={() => updateItem({...item, title})}>Save</button>
</td>
</tr>
};
If you want to send from nested component to parent some data use a callback function

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