Home > Blockchain >  Nested map in react and Material UI
Nested map in react and Material UI

Time:08-04

I have the following data that I want to iterate over:

const publications = [
  {
    id: '1',
    year: '2022-2023',
    period: 'First half',
    title: 'My title',
    authors: 'John Doe',
    type: 'Journal Article',
  },
  {
    id: '2',
    year: '2021-2022',
    period: 'Second half',
    title: 'My second title',
    authors: 'Jane Doe',
    type: 'Book',
  },
  {
    id: '3',
    year: '2022-2023',
    period: 'First half',
    title: 'My third title',
    authors: 'John Smith',
    type: 'Review',
  },
];

I'm trying to produce an MUI table like this:

<TableContainer component={Paper}>
        <TableHead>
          <TableRow>
            {publicationsData.map((item) => (
              <TableCell key={item.id}>{item.name}</TableCell>
            ))}
          </TableRow>
        </TableHead>
        <TableBody>
          {publications.map((publication) => (
            <TableRow key={publication.id}>
              <TableCell>{publication.year}</TableCell>
              <TableCell>{publication.period}</TableCell>
              <TableCell>{publication.title}</TableCell>
              <TableCell>{publication.authors}</TableCell>
              <TableCell>{publication.type}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </TableContainer>

I put the name of the columns in a separate json file (publicationsData.json)–I want to eliminate this step if possible.

[
  {
    "id": "1",
    "name": "Grant Year"
  },
  {
    "id": "2",
    "name": "Grant Period"
  },
  {
    "id": "3",
    "name": "Title"
  },
  {
    "id": "4",
    "name": "Author(s)"
  },
  {
    "id": "5",
    "name": "Type"
  }
]

I was able to eliminate code duplication in the header with the .map. How do I do the same with the table body? I don't want to manually copy each <TableCell> component.

What's the best way to go about this?

CodePudding user response:

You can do this using Object.keys.

Try replacing this:

{publications.map((publication) => (
   <TableRow key={publication.id}>
      <TableCell>{publication.year}</TableCell>
      <TableCell>{publication.period}</TableCell>
      <TableCell>{publication.title}</TableCell>
      <TableCell>{publication.authors}</TableCell>
      <TableCell>{publication.type}</TableCell>
   </TableRow>
))}

with this:

{publications.map((publication) => (
  <TableRow key={publication.id}>
    {Object.keys(publication).map((key, index) => (
      <TableCell>{publication[key]}</TableCell>
    ))}
  </TableRow>
))}

This may not be desirable though, since you'll end up with a TableCell for id.

You could prevent this by first filtering the attributes to not include id like this:

  {
    publications.map((publication) => (
      <TableRow key={publication.id}>
        {Object.keys(publication).filter(key => key !== 'id')
          .map((key) => (
            <TableCell>{publication[key]}</TableCell>
          ))}
      </TableRow>
    ));
  }

At this point though, the effort to write cleaner code may be adding more complexity than is worthwhile. There are tradeoffs to every decision, and in this case, writing <TableCell>{...}</TableCell> for several lines in a row (even 20 lines in a row) might not be the worst thing ever.

It all depends on your exact use-case of course.


Ordering Problem:

As Yone's comment pointed out, this leaves you without a guarantee of the order the keys will be iterated over.

As an alternative, you can create an array of the columns you want in order, like this:

const columns = ["year", "period", "title", "authors", "type"]

And then you can iterate to create your table cells based on that array:

  {
    publications.map((publication) => (
      <TableRow key={publication.id}>
        {columns.map((key) => (
            <TableCell>{publication[key]}</TableCell>
          ))}
      </TableRow>
    ));
  }

This reduces complexity and gives you better control over the columns.

  • Related