I'm writing a code in react, where in I want to display the json data in HTML table. I want this to be dynamic. i.e. irrespective of the type of json data I'm using, it should render in tabular format.
Here is my code with sample data.
const jsArray = [{"Model":"Mazda RX4","mpg":21,"cyl":6},{"Model":"Mazda RX4 Wag","mpg":21,"cyl":6},{"Model":"Datsun 710","mpg":22.8,"cyl":""},{"Model":"Hornet 4 Drive","mpg":21.4,"cyl":""},{"Model":"Hornet Sportabout","mpg":18.7,"cyl":8},{"Model":"Valiant","mpg":18.1,"cyl":6}];
{jsArray.length > 0 && (
<table>
<thead>
{jsArray.map((item, idx) =>
idx === 0 ? (
<th key={idx}>
{Object.values(item).forEach((val) => (
<td>{val}</td>
))}
</th>
) : (
<tr key={idx}>
{Object.values(item).forEach((val) => (
<td>{val}</td>
))}
</tr>
)
)}
</thead>
</table>
)}
When I run this code, nothing is getting rendered. When I replace <tr key={idx}>{Object.values(item).forEach((val)=> (<td>{val}</td>))}</tr> with null, In my output I see null printed in my front end. Please let me know where I'm going wrong.
You can do it like this:
const data = JSON.parse(jsArray);
const keys = Object.keys(data.length ? data[0] : {});
return (
<div className="App">
{jsArray.length > 0 && (
<table>
<thead>
<tr>
{keys.map((item, idx) => (
<th key={idx}>{item}</th>
))}
</tr>
</thead>
<tbody>
{data.map((item, idx) => (
<tr key={idx}>
{keys.map((key, idx) => (
<td>{item[key]}</td>
))}
</tr>
))}
</tbody>
</table>
)}
</div>
);
It might make it easier if you broke all that logic down into more helpful functions.
getHeadings maps over the first object and grabs its keys.
getRows maps over all the data, and calls getCells with each object's data.
getCells maps over the object and uses Object.values to get the information for each cell.
This way your component is a lot cleaner.
// `map` over the first object in the array
// and get an array of keys and add them
// to TH elements
function getHeadings(data) {
return Object.keys(data[0]).map(key => {
return <th>{key}</th>;
});
}
// `map` over the data to return
// row data, passing in each mapped object
// to `getCells`
function getRows(data) {
return data.map(obj => {
return <tr>{getCells(obj)}</tr>;
});
}
// Return an array of cell data using the
// values of each object
function getCells(obj) {
return Object.values(obj).map(value => {
return <td>{value}</td>;
});
}
// Simple component that gets the
// headers and then the rows
function Example({ data }) {
return (
<table>
<thead>{getHeadings(data)}</thead>
<tbody>{getRows(data)}</tbody>
</table>
);
}
const data = [{"Model":"Mazda RX4","mpg":21,"cyl":6},{"Model":"Mazda RX4 Wag","mpg":21,"cyl":6},{"Model":"Datsun 710","mpg":22.8,"cyl":""},{"Model":"Hornet 4 Drive","mpg":21.4,"cyl":""},{"Model":"Hornet Sportabout","mpg":18.7,"cyl":8},{"Model":"Valiant","mpg":18.1,"cyl":6}];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
table { border: 1px solid #dfdfdf; border-collapse: collapse; }
th { background-color: #efefef; text-align: left; text-transform: uppercase; }
td { border: 1px solid #efefef; }
td, th { padding: 0.4em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Related
So I happen to work with an Array that looks like this
Array = [
{
product1,
orderedBy = [{customer1}, {customer2},.....,{customerN}]
},
{
product2,
orderedBy = [{customer3}, {customer4},.....,{customerN}]
},
.
.
.,
{
productN,
orderedBy = [{customer5}, {customer6},.....,{customerN}]
},
]
I want to render it on a table like shown below
I was hoping to achieve something like this, but it gives error
{orders.map((order, index) => (
{order.orderedBy.map((customer, index) => (
<tr>
<td>order.productId</td>
<td>customer.customerId</td>
</tr>
))}
))}
I'm aware that I can create a new array with elements that are easy to render using the map function. However, I'm curious to know if there is any cleaner way to achieve this result that I may not be aware of yet.
If you compile your data prior to mapping over it you'll have an easier time. This example creates a new object for each customer of product n, and adds it to a new array. You can call compileData from the JSX, and map over the returned array of objects instead.
Note: I shifted things around in the data to ensure products and customers have their own id which you should be doing anyway.
function compileData(data) {
const arr = [];
for (const { name: productName, orderedBy } of data) {
for (const { id, name: customerName } of orderedBy) {
arr.push({ id, productName, customerName });
}
}
return arr;
}
function Table({ data }) {
return (
<table>
<thead>
<td>#</td>
<td>Product name</td>
<td>Customer name</td>
</thead>
<tbody>
{compileData(data).map(row => {
return (
<tr key={row.id}>
<td>{row.id}</td>
<td>{row.productName}</td>
<td>{row.customerName}</td>
</tr>
);
})}
</tbody>
</table>
);
}
const data=[{id: 1, name:"product1",orderedBy:[{id: 1, name:"customer1"},{id: 2, name:"customer2"}]},{id: 2, name:"product2",orderedBy:[{id: 3, name:"customer3"},{id: 4, name:"customer4"}]}];
ReactDOM.render(
<Table data={data} />,
document.getElementById('react')
);
table { border-collapse: collapse; }
tr:nth-child(even) { background-color: powderblue; }
td { border: 1px solid #565656; padding: 0.3em; text-transform: capitalize; }
thead { background-color: lightgreen; font-weight: 600; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Wrap it in either any element such as div, but since you are filling a table, use <React.Fragment />, which lets you wrap several children in a faux-element. You can also make it repeat directly inside the <table> or <tbody>, if the case applies to you.
Either way, don't forget to add key to the mapped child:
{orders.map((order, index) => (
<React.Fragment key={'order-' + index}>
{order.orderedBy.map((customer, index) => (
<tr key={'customer-' + index}>
<td>order.productId</td>
<td>customer.customerId</td>
</tr>
))}
</React.Fragment>
))}
For other cases where you can wrap in a regular element this can also work (can work with any element that accepts and displays children)
{orders.map((order, index) => (
<div key={'order-' + index}>
{order.orderedBy.map((customer, index) => (
<tr key={'customer-' + index}>
<td>order.productId</td>
<td>customer.customerId</td>
</tr>
))}
</div>
))}
I have a body object with different types of elements: (strings, number, objects...).
I need to show the body in a table.
In order to do it, I need to print in one table the elements that aren't objects, and in another table the elements that are objects.
So I am calling the function to create an array with object elements (arrObj) and another array with the non object elements (arrSimple).
The problem is that when I go through the arrSimple array to print the elements in a table, this array is empty.
Could anyone guide me on how can I resolve this async problem?
const DetailResult = props => {
...
const arrSimple = []
const arrObj = []
function organizeArray() {
for (const prop in body) {
if (typeof (body[prop]) != 'object') {
arrSimple[prop] = (body[prop])
} else if (typeof (body[prop]) == 'object') {
arrObj[prop] = (body[prop])
}
}
}
function renderGeneralData() {
organizeArray()
arrSimple.map((key, i) => {
<tr key={i}>
<td width="25%">{key}</td>
<td>{(arrSimple[key])}</td>
</tr>
})
}
return (
<div>
<table className='table table-striped'>
<tbody>
<tr>
<th>General Data</th>
</tr>
<tr>
{renderGeneralData()}
</tr>
</tbody>
</table>
</div>
)
}
export default DetailResult;
The body object comes from the app component.
class App extends Component {
constructor() {
super()
this.state = {
dataTable: {
transactionID: '',
maxRows: 10,
currentPage: 0,
list: {
headerList: [],
body: []
}
}
}
this.search = this.search.bind(this)
}
search() {
axios.get(URL)
.then(resp => this.setState({
dataTable: Object.assign(this.state.dataTable, {
list: [
{headerList: ['App name', 'Date', 'Bio data', 'Is verified', 'Actions']},
{body: resp.data},
],
}),
}))
.catch(function (error) {
console.log(error);
})
}
I have a component that contains a search field to make a request
const SearchComponent = props => {
const renderDetailResult =
<DetailResult list={props.dtObject.list}
search={props.search}
/>
return (
<div role='form' className='searchID'>
<ContentHeader title={props.componentHeaderTitle} />
<Grid cols='12 9 10'>
<input id="cpf" className='w-25 form-control'
placeholder='Type the ID'
/>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='search'
onClick={props.search}>
</IconButton>
</Grid>
<Grid cols='12'>
{renderDetailResult}
</Grid>
</div>
)
}
export default SearchComponent
The reason why nothing appears is that you are calling a function that returns nothing, so there isn't anything to render.
You need to return the .map and return the elements you want.
function renderGeneralData() {
organizeArray()
// added return
return arrSimple.map((key, i) => (
<tr key={i}>
<td width="25%">{key}</td>
<td>{(arrSimple[key])}</td>
</tr>
))
}
Observation
You are rendering <tr> inside <tr>. I recommend removing the
return arrSimple.map((key, i) => (
//returning tr
<tr key={i}>
<td width="25%">{key}</td>
<td>{(arrSimple[key])}</td>
</tr>
))
<tr>
// returned tr inside another tr
{renderGeneralData()}
</tr>
I'm not sure how you want to display your data, but I recommend removing one of the tr tag.
Quick tip
If you want to remove the tr that is inside .map you should use React.Fragment
return arrSimple.map((key, i) => (
<React.Fragment key={i}>
<td width="25%">{key}</td>
<td>{(arrSimple[key])}</td>
</React.Fragment>
))
Edit:
I also noticed something weird in your code in this part
arrSimple.map((key, i) => (
<tr key={i}>
<td width="25%">{key}</td>
<td>{(arrSimple[key])}</td>
</tr>
))
In this part of the code, key will be an element of arrSimple. If you do arrSimple[key] it will probably return undefined. Here is an example
arr = ['hey', 'this', 'is', 'bad', '!']
console.log(arr.map((key, i) => arr[key]))
I have a local JSON file:
and I'm trying to display that data to look like a graph like this:
I have the graph laid out and I'm trying to display them like props, however I keep getting an error that says props is not defined.
Here is what the code looks like
<div>
<table>
<thead>
<tr>
<th>Athlete</th>
<th>Muscle-soreness</th>
<th>Sleep-quality</th>
</tr>
</thead>
<tbody>
{
props.data.map(row => (
<tr>
<td>{row.athlete}</td>
</tr>
))
}
<tr>
<td>Column1</td>
<td>Column2</td>
<td>Column3</td>
</tr>
</tbody>
</table>
</div>
I'm still pretty new to react so any help would be greatly appreciated!
Thank you
Ok, since you don't provide an answer to the comments I think you are still trying to figure it out. As far as I see your JSON data is an object. So somehow you need to map this object and pick the proper values. In the current shape of this data, it is a little bit tricky to do this.
First, we use Object.values to get the object values. Since all the key lengths are equal ( this is how all the data sync) we are just mapping the first value of the object. Actually the first or the second or any value does not matter here, we just need to map one value to track an iterator index.
Inside this mapped array we are again using another map to get the values. For each td we use our index coming from the upper map.
By the way, I've used indexes as keys but finding a unique key would be better.
const App = ( props ) => {
const { data } = props;
const values = Object.values( data );
return (
<div>
<table>
<thead>
<tr>
<th>Athlete</th>
<th>Muscle-soreness</th>
<th>Sleep-quality</th>
</tr>
</thead>
<tbody>
{values[ 0 ].map( ( _, i ) => (
<tr key={i}>{values.map( ( value, key ) => <td key={key}>{value[ i ]}</td> )}</tr>
) )}
</tbody>
</table>
</div>
);
};
const data = {
athlete: [ "Foo", "Bar", "Baz" ],
"muscle-soreness": [ 5, 6, 7 ],
"sleep-quality": [ 5, 6, 7 ],
};
ReactDOM.render( <App data={data} />, document.getElementById( "root" ) );
table, tr, td {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I am trying to paginate over an array
I am fairly new to react and don't know where to start
I am trying to paginate this list of events so it only shows 9 events on the page
and when there are more you can click on 2 or 3 etc to see more
and I have no idea how to implement pagination
class EventsList extends PureComponent {
componentWillMount() {
this.props.getAllEvents();
}
getEvent(eventId) {
this.props.getEvent(eventId);
}
addEvent = event => {
this.props.addEvent(event);
};
render() {
const now = Moment();
const { events, authenticated } = this.props;
const eventsList = events.sort((a, b) =>{
return a.id - b.id;
});
if (!authenticated) return <Redirect to="/login" />;
return <div>
<Paper className="styles" elevation={4}>
<h1>Coming Events</h1>
<table>
<thead>
<tr>
<th>Event Name</th>
<th>Starts</th>
<th>Ends</th>
<th>Short description</th>
</tr>
</thead>
<tbody>
{eventsList.map(event => <tr key={event.id}>
<td>
<Link className="link" to={`/events/${event.id}`} onClick={() => this.getEvent(event.id)}>
{event.name}
</Link>
</td>
{now && <td style={{ border: "2px solid black" }}>
{Moment(event.startDate).format("ll")}
</td>}
{now && <td style={{ border: "2px solid black" }}>
{Moment(event.endDate).format("ll")}
</td>}
<td />
<td style={{ border: "2px solid green" }}>
{event.description}
</td>
<td />
</tr>)}
</tbody>
</table>
<br />
<br />
<Link className="link" to={`/addEvent`}>
Add An Event
</Link>
</Paper>
</div>;
}
}
const mapStateToProps = function(state) {
return {
events: state.events,
event: state.event,
authenticated: state.currentUser !== null,
users: state.users === null ? null : state.users
};
};
export default connect(
mapStateToProps,
{
getAllEvents,
getEvent,
addEvent,
getUsers
}
)(EventsList);
I am looking at
http://pagination.js.org
and https://www.npmjs.com/package/react-js-pagination
but I don't get how I should implement it.
If someone can give me some pointers?
or a simple example ?
You are going to need to keep track of a few items. First the total number of events. Second the current page. Third the number of items per page (a constant you want to set).
Then you can do any number of things to your events array to only show those items.
For example, you could use an array slice https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice -- This will give you back a new array with only the items you want.
So to get a second page of items (10 - 19) you would calculate your start and end indexes based on the page number and the number of items per page and then end up with something like.
const numberPerPage = 9;
const startIndex = 10; // Computed based on your numbers
const endIndex = startIndex + numPerPage;
const pagedEvents = eventsList.slice(startIndex, endIndex);
That will give you a list with only the items for that page. Updating the page number will cause the numbers to change and the list to update.
I can't achieve to have two nested map:
render() {
return (
<table className="table">
<tbody>
{Object.keys(this.state.templates).map(function(template_name) {
return (
<tr key={template_name}><td><b>Template: {template_name}</b></td></tr>
{this.state.templates[template_name].items.map(function(item) {
return (
<tr key={item.id}><td>{item.id}</td></tr>
)
})}
)
})}
</tbody>
</table>
)
}
This gives a SyntaxError: unknown: Unexpected token.
How do you nest map calls in JSX?
You need to wrap it inside an element.
Something like this (I've added an extra tr due to the rules of tables elements):
render() {
return (
<table className="table">
<tbody>
{Object.keys(templates).map(function (template_name) {
return (
<tr key={template_name}>
<tr>
<td>
<b>Template: {template_name}</b>
</td>
</tr>
{templates[template_name].items.map(function (item) {
return (
<tr key={item.id}>
<td>{item}</td>
</tr>
);
})}
</tr>
);
})}
</tbody>
</table>
);
}
}
Running Example (without a table):
const templates = {
template1: {
items: [1, 2]
},
template2: {
items: [2, 3, 4]
},
};
const App = () => (
<div>
{
Object.keys(templates).map(template_name => {
return (
<div>
<div>{template_name}</div>
{
templates[template_name].items.map(item => {
return(<div>{item}</div>)
})
}
</div>
)
})
}
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I struggled for a while to get my nested map function to work only to discover that what you return is critical. Make sure you are returning the second map itself and not just the final expected output:
let { categories } = data;
categories = categories.map(category =>
category.subcategories.map((subcategory, i) => <h2 key={i}>{subcategory.name}</h2>)
);
return (
<div className="category-container">
<div>{categories}</div>
</div>
);
I'm not sure if it's correct technically, but as a mnemonic you can remember that: "Every returned JSX element must be only one JSX element".
So most of the times just wrapping what you have in a <></> pair (or any other arbitrary tag pair) will fix the issue. E.g., if you're returning two <div>s from the render method of a component, that will be incorrect, however, if you wrap these two in a <></> pair, most probably it will be fixed.
But notice that sometimes it can get a bit more vague, e.g., when nesting two ES6 maps in each other, for example:
<tbody>
{
this.categorizedData.map(
(catgGroup) => (
<tr>
<td>{catgGroup}</td>
</tr>
this.categorizedData[catgGroup].map(
(item) => (
<tr>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
)
)
)
)
}
</tbody>
Can be fixed like this:
<tbody>
{
this.categorizedData.map(
(catgGroup) => (
<> // <--- Notice this, it will wrap all JSX elements below in one single JSX element.
<tr>
<td>{catgGroup}</td>
</tr>
{this.categorizedData[catgGroup].map( // <--- Also notice here, we have wrapped it in curly braces, because it is an "expression" inside JSX.
(item) => (
<tr>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
)
)}
</>
)
)
}
</tbody>
P.S.: (From documentation): You can also return an array of elements from a React component:
render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :slight_smile:
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
I think the problem is that the return type should be an array but not an object in React16. You could try like this below:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
templates: {
foo: {
items: [
{id: 0},{id:1}
]
},
bar: {
items: [
{id: 2},{id:3}
]
}
}
}
}
renderTemplate = (template, name) => {
let data = []
data = template.items
data.unshift({ name: name })
return data.map((item, index) => <tr key={index}><td>{item.name ? item.name : item.id}</td></tr>)
}
render() {
return (
<table>
<tbody>
{Object.keys(this.state.templates).map(name => {
return this.renderTemplate(this.state.templates[name], name)
})}
</tbody>
</table>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
td {
color: white;
padding: 0 20px;
background: grey;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.1.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.1.1/umd/react-dom.production.min.js"></script>