How to have nested loops with map in JSX? - javascript

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>

Related

How can you use .map (or any other alternative) in React for nested Arrays

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

Render JSON in a HTML table in react js

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>

How can I integrate searching functionality on table?

At the moment, all the available flights that was received from API are successfully loaded on the page. However, I would like to enable the end user to search specific flight, let's say, by flight number and departure date. How can I integrate this searching functionality in the existing codes?
FlightPage.js
render() {
return (
<>
<h2>Flights</h2>
{this.props.loading ? (
<div>Loading...</div>
) : (
<FlightList flights={this.props.flights} />
)}
</>
);
}
}
As you can see the bellow code, I have used table to present the results.I would like to show only one result or blank table when searching is applied. Can you help me to achieve this?
FlightList.js
const FlightList = ({ flights }) => (
<table className="table">
<thead>
<tr>
<th />
<th>Date</th>
<th>Provider</th>
<th>Dest</th>
</tr>
</thead>
<tbody>
{flights.map((f, i) => {
return (
<tr key={i}>
<td>
<input type="checkbox" name="flightListCheckbox" />
</td>
<td>{f.date}</td>
<td>{f.pnr}</td>
<td>{f.flightNumber}</td>
</tr>
);
})}
</tbody>
</table>
);
You could use filter to create a searching functionality like
I would at first add an input where I can insert my filter values
FlightPage.js
handleInput: (event) => {
const { name, value } = event.target
this.setState({ [name]: value })
}
render () {
const { filter } = this.state
return (
<>
<input onChange=(this.handleInput) value={filter} name='filter' />
<FlightList flights={this.props.flights} filterValues={filter} />
</>
)
}
Then I would use my state to filter my Object like
FlightList.js
const FlightList = ({ flights, filterValue }) => {
const filterdFlights = flights.filter(flight => Object.values(flight).includes(filterValue))
return (
<table className="table">
<thead>
<tr>
<th />
<th>Date</th>
<th>Provider</th>
<th>Dest</th>
</tr>
</thead>
<tbody>
{filterdFlights.map((f, i) => {
return (
<tr key={i}>
<td>
<input type="checkbox" name="flightListCheckbox" />
</td>
<td>{f.date}</td>
<td>{f.pnr}</td>
<td>{f.flightNumber}</td>
</tr>
);
})}
</tbody>
</table>
)};
You need an input for search and filter flights by value of input. Try this
class FlightPage extends React.Component {
state = {
keyword: '',
}
...
getFlights = () => {
const { keyword } = this.state
const { flights } = this.props
return flights.filter(flight => flight.name.includes(keyword)) // name or something else
}
onInputChange = e => {
this.setState({ keyword: e.target.value })
}
render () {
return (
<>
<input onChange=(this.onInputChange) value={this.state.keyword} />
<FlightList flights={this.getFlights()} />
</>
)
}
}
You can filter your flights array using flights.filter or sort it using flights.sort.
You could try to use jquery datatable. It adds a lot of funcionality to tables easy to implement.
DataTable doc

How to deal with async function in react?

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

Create a table from JSON completely dynamically

I want to create entire table from a JSON array include dynamic ths, so a decoupled the head part to:
import React from 'react';
import TableDataTR from './TableTRView';
const TableView = ({ thds, tableData }) => {
if (!thds) {
return <table></table>;
}
return (
<div>
<table>
<thead>
<tr>
{thds.map((data, i) => <th key={i}>{data.th}</th>)}
</tr>
</thead>
<TableDataTR tableData={tableData}/>
</table>
</div>
)
}
export default TableView;
And the table prints the head part, but I am having trouble printing my tbody:
import React, { Component } from 'react';
class TableDataTR extends Component {
render() {
let rows = this.props.tableData.map((currElement, index) =>{
return(
<tr key={index}>
<td>currElement[index]</td>
</tr>
)
});
return (
<tbody>{rows}</tbody>
);
}
}
export default TableDataTR;
Example data, table head
thds : [ { th: 'th1' },
{ th: 'th2' }]
tableData:[ { tdData1: 'somedata', tdData2: 'somedata2' },
{ tdData1: 'somedata', tdData2: 'somedata2' },]
The table head works fine, but the data inside tbody is not displaying at all.
The final goal is to get any 2 arrays and display the table accordingly.
You need to access the table data using key name as
let rows = this.props.tableData.map((currElement, index) => {
return (
<tr>
{ Object.keys(currElement).map((item) =>
(
<td>{currElement[item]}</td>
)
)}
</tr>
)
});
There is a typo again in your <td>..</td> part, missing {} for your expression. For the dynamic solution after first map you need to map your Objects as well.
<tr key={index}>
<td>
{
Object.keys(currElement).map(key => currElement[key])
}
</td>
</tr>

Categories

Resources