Search code examples
reactjsmaterial-table

Edit custom column component while adding new row of Material Table


With the React Material Table library, is it possible to render a custom component while adding a new row? I'm using a custom component (a Material UI select box, actually), for the Expected Result column. When I add a new row, I only see a field for the Requirement column, not the Expected Result column. Is it possible to add an input for the Expected Result column of the new row as well?

Material Table screenshot

Another option is to not use custom components at all and instead use something like the Cell Editable Example of https://material-table.com/#/docs/features/editable. However, I'm not a fan of the extra clicks that it takes to edit the Expected Result, compared to directly using a Select field.

import MaterialTable from 'material-table'
import { MenuItem, Select } from '@material-ui/core'
import React, { useState } from 'react'
import update from 'immutability-helper'

type PassFailNA = 'Pass' | 'Fail' | 'N/A'

type RowData = {
    requirementId: number,
    requirementName: string,
    expectedResult: PassFailNA,
    expectedResultId?: number
}

export function ExpectedResultsTable(props: {
    scenarioId: number
}) {
    const [tableData, setTableData] = useState<RowData[]>([{ requirementId: 1, requirementName: 'hello', expectedResult: 'Pass' }])
    const { enqueueSnackbar } = useSnackbar()

    const handleSelect = (id: number) => (event: React.ChangeEvent<{ name?: string; value: any }>) => {
        setTableData((tableData: RowData[]) => {
            const rowNum = tableData.findIndex(x => x.requirementId === id)
            return update<RowData[]>(tableData, {
                [rowNum]: { expectedResult: { $set: event.target.value } }
            })
        })
    }

    return (
        <MaterialTable<RowData>
            title=""
            columns={[
                {
                    title: 'Requirement',
                    field: 'requirementName'
                },
                {
                    title: 'Expected Result',
                    field: 'expectedResult',
                    render: (rowData) => (
                        <Select value={rowData.expectedResult} onChange={handleSelect(rowData.requirementId)}>
                            <MenuItem value="Pass">Pass</MenuItem>
                            <MenuItem value="Fail">Fail</MenuItem>
                            <MenuItem value="N/A">N/A</MenuItem>
                        </Select>
                    )
                }
            ]}
            data={tableData}
            editable={{
                onRowAdd: newRow =>
                    new Promise((resolve, reject) => {
                        setTimeout(() => {
                            setTableData(tableData => update(tableData, { $push: [{ ...newRow, expectedResult: 'N/A'}] }))
                            resolve()
                        }, 1000)
                    })
            }}
        />
    )
}

Solution

  • To achieve what you are looking for, I think you should specify the editComponent property ( besides render ) when defining the column. That prop takes a function where you can define the component used during the edit or creation phase.

    Here is an example I made with a boolean input:

      const tableColumns = [
        { title: "Client", field: "id" },
        { title: "Name", field: "name" },
    
        {
          title: "booleanValue",
          field: "booleanValue",
          editComponent: (props) => {
            console.log(props);
            return (
              <input
                type="checkbox"
                checked={props.value}
                onChange={(e) => props.onChange(e.target.checked)}
              />
            );
          },
          render: (rowdata) => (
            <input type="checkbox" checked={rowdata.booleanValue} />
          )
        }
      ];
    

    Link to working sandbox. I hope that works for you!