Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Customize row className #1298

Closed
1 task done
erikian opened this issue Mar 25, 2021 · 12 comments · Fixed by #1448
Closed
1 task done

[DataGrid] Customize row className #1298

erikian opened this issue Mar 25, 2021 · 12 comments · Fixed by #1448
Labels
component: data grid This is the name of the generic UI component, not the React module! new feature New feature or request

Comments

@erikian
Copy link
Contributor

erikian commented Mar 25, 2021

  • I have searched the issues of this repository and believe that this is not a duplicate.

Summary 💡

I haven't found a way to customize the class of a row in the DataGrid component based on row data and I think this feature would be extremely useful.

Examples 🌈

My DataGrid is being used to display a set of devices. I want to add a specific className to each row so I can style it based on the properties of each device (eg: I want a row with red background for offline devices, green background for online devices, upside down for devices with names starting with A...). Code-wise, it would look something like this:

const devices = [
    {
        id: 1,
        status: 'online',
    },
    {
        id: 2,
        status: 'offline',
    },
    {
        id: 3,
        status: 'online',
        myVerySpecialBoolean: true,
    },
];

const rows = devices.map(device => ({
    ...device,
    className: `${device.status === 'offline' ? 'offline' : 'online'} ${device.myVerySpecialBoolean ? 'myVerySpecialClass' : ''}`,
}))

Motivation 🔦

Currently, in order to add an offline class to a row, for instance, I need to traverse the DOM whenever my devices change (which is quite often) and use each row's data-id attribute to check if the device with that ID is offline in order to domNode.classList.add('offline') or domNode.classList.remove('offline'), and we all know how bad, terrible, cumbersome, no-good, root-of-all-evil traversing the DOM is.

@erikian erikian added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Mar 25, 2021
@erikian erikian closed this as completed Mar 25, 2021
@erikian erikian reopened this Mar 25, 2021
@erikian erikian changed the title [DataGrid [DataGrid] Customize row className Mar 25, 2021
@oliviertassinari
Copy link
Member

@erikian There isn't enough information. Please provide a reproduction of what you have done so far, It would help a lot to understand the problem you are trying to solve

@oliviertassinari oliviertassinari added component: data grid This is the name of the generic UI component, not the React module! status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Mar 25, 2021
@erikian
Copy link
Contributor Author

erikian commented Mar 25, 2021

Sorry for the lack of information, let's try again 😃

Each table row represents a device. My goal is to add an offline class to a row if the device is offline, so I can style the row with a red background (take a look at the CSS in the final lines of my component).

Here's a better example:

import { DataGrid } from '@material-ui/data-grid'
import { useCallback, useEffect } from 'react'
import styled from 'styled-components'

interface Device {
  id: number
  status: 'online' | 'offline'
}

interface IProps {
  devices: Device[]
}

const MyCurrentTable = (props: IProps) => {
  const { devices } = props

  const columns = [
    {
      field: 'id',
      headerName: 'Device ID',
    },
    {
      field: 'status',
      headerName: 'Status',
    }
  ]

  // I'm storing the ids of all offline devices in this array
  const offlineDevices = useCallback(() => {
    return devices
      .filter(({ status }) => status === 'offline')
      .map(({ id }) => id)
  }, [devices])

  // whenever props.devices changes, I need to do this to add/remove the 'offline' class
  useEffect(() => {
    document
      .querySelectorAll('#dataGridWrapper .MuiDataGrid-row[data-id]')
      .forEach(row => {
        // the data-id attribute is provided by the DataGrid component itself (through the field `id` in the columns definition)
        const id = +row.getAttribute('data-id')

        // this device is now offline
        if (offlineDevices.includes(id)) {
          row.classList.add('offline')
        }

        // this device is no longer, or has never been, offline
        else {
          row.classList.remove('offline')
        }
      })
  }, [devices, offlineDevices])

  return (
    <div id="dataGridWrapper">
      <DataGrid rows={devices} columns={columns} />
    </div>
  )
}

export default styled(MyCurrentTable)`
  .MuiDataGrid-row {
    // here's what I want to do with my class
    &.offline {
      background: red;
    }
  }
`

Ideally, I'd be able to specify a className to the row in the rows prop, so my component would look something like this:

import { DataGrid } from '@material-ui/data-grid'
import styled from 'styled-components'

interface Device {
  id: number
  status: 'online' | 'offline'
}

interface IProps {
  devices: Device[]
}

const MyNewAndImprovedTable = (props: IProps) => {
  const { devices } = props

  const columns = [
    {
      field: 'id',
      headerName: 'Device ID',
    },
    {
      field: 'status',
      headerName: 'Status',
    }
  ]
  
  const rows = devices.map(device => ({
    ...device,
    
    // here's the magic
    className: device.status === 'offline' ? 'offline' : 'online',
  }))

  return (
    <div id="dataGridWrapper">
      <DataGrid
        rows={rows}
        columns={columns}
      />
    </div>
  )
}

export default styled(MyNewAndImprovedTable)`
  .MuiDataGrid-row {
    &.offline {
      background: red;
    }
  }
`

@oliviertassinari oliviertassinari added status: waiting for maintainer These issues haven't been looked at yet by a maintainer and removed status: waiting for author Issue with insufficient information labels Mar 25, 2021
@dtassone
Copy link
Member

You can add a css class to a cell using GridColDef.cellClassName and then apply your style? Would that work for now?

@dtassone dtassone added new feature New feature or request and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Mar 26, 2021
@erikian
Copy link
Contributor Author

erikian commented Mar 26, 2021

@dtassone You're right, it would work if I did something like this:

<DataGrid
  rows={rows}
  columns={columns.map(column => ({
    ...column,
    cellClassName: ({row: device}) => device.status,
  }))}
/>

While this works for my specific case, adding a class to every single cell still looks a bit hacky. Also, if I needed to style the entire row in some other way (like a border, linear-gradient background or something), this would not work, so I guess this feature still is a nice-to-have.

@oliviertassinari
Copy link
Member

oliviertassinari commented Mar 26, 2021

To add more context.

  • The data grid style its nested elements with a 1-level deep nested CSS selector in order to avoid the creating of extra React elements and styled-components. We haven't benchmarked the performance, we have assumed that it would be faster. This, effectively force developers to customize the data grid a nested CSS selector to win the specificity:
export default styled(DataGrid)`
  .MuiDataGrid-row {
    background: red;
  }
`);

which @erikian already does.

Maybe we should add the "waiting for more upvotes" label?

@dtassone
Copy link
Member

dtassone commented Mar 26, 2021

Here, maybe it makes sense to have a <DataGrid rowClassName?: (params) => string /> prop, to avoid having to define a cellClassName?: (params) => string property for each column. It's also worth noting that we didn't implement classes, a prop that a user in #1163 tried.

I think that we should add rowClassName. This point covers this specific use case. If we do it for rows, we should also need it for cells, and header cells (already done). #275 already allows computing classes at rendering using a function. So it would be refactoring.

"rowClassName", "headerClassName", "cellClassName" should have the same signature for consistency (params => string).

@cmocanu
Copy link

cmocanu commented Apr 17, 2021

This is definitely necessary. I'm working on a trading application and I need to show green rows for profit, red rows for loss, gray rows for inactive etc. I'm currently using cellClassName with a function parameter, but that gets run $nrOfColumns times more often than a row one would, contributing the bad performance I'm currently dealing with (scroll as well as resize).

@oliviertassinari

This comment has been minimized.

@cmocanu

This comment has been minimized.

@dtassone

This comment has been minimized.

@cmocanu

This comment has been minimized.

@oliviertassinari

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: data grid This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants