How to Create an Editable Table Component in React

How to Create an Editable Table Component in React

With the help of React hooks
Ferenc Almasi β€’ 2023 September 28 β€’ Read time 13 min read
Learn how you can build an editable table component in React with the help of React hooks to create dynamic tables.
  • twitter
  • facebook
React

Tables are often employed in web apps to represent data in a structured format. For applications where data can be updated, they're also accompanied by a dedicated form or a modal where users can edit entries.

However, making the table itself editable can enhance user experience by eliminating the need for a separate interface. In this tutorial, we'll see how you can create an editable dynamic table component in React from the ground up.


How to Use the Component

First, let's see how we want to use the component, so we can create the props and layout accordingly. We want to create the following functionality, where the table can be customized via props:

Copied to clipboard! Playground
import { EditableTable } from './EditableTable'
import { data } from './data'

const App = () => {
    const update = data => {
        console.log('Updated table data:', data)
    }

    return (
        <EditableTable
            headings={['ID', 'Name', 'Score', 'Age', 'Active']}
            rows={data}
            callback={update}
        />
    )
}
App.jsx
How to call the component

The component is called EditableTable, which accepts two mandatory and one optional prop:

  • headings: This is the only optional prop, which accepts an array of strings. These will be displayed as the table's header. If they're not present, no header will be displayed. The number of items in the array must match the number of properties of each object inside the rows prop.
  • rows: The data that we want to display. See the example below for the required data format. It must be an array of objects, with any type and number of properties.
  • callback: A callback function that will be executed each time a change is made in the table. This function will receive the updated data (that is passed to the rows prop) that can be passed forward to an API.
Copied to clipboard! Playground
export const data = [
    {
        id: '6512fb5737b7fd3482d41d90',
        name: 'Church Hamilton',
        score: 1232,
        age: 17,
        isActive: false
    }
]
data.js
The objects can take any shape and form

To generate random mock data for your application, you can use JSON Generator.


Displaying Tables

Now that we know how we want to call the component, let's create the layout. Create a new file called EditableTable.jsx and add the following lines of code:

Copied to clipboard! Playground
export const EditableTable = ({ headings, rows, callback }) => {
    return (
        <table>
            {!!headings?.length && (
                <thead>
                    <tr>
                        {headings.map((heading, index) => (
                            <th key={index}>
                                {heading}
                            </th>
                        ))}
                    </tr>
                </thead>
            )}
            <tbody>...</tbody>
        </table>
    )
}
EditableTable.jsx
Create the base for the component

Let's break the component down into two parts, as there are quite a few lines involved. First, we want to conditionally render a thead into our table element if the headings prop is present.

We can use a simple map to loop through the array and display each string inside a th. Don't forget to pass a key prop for React. Inside the tbody, we want to loop through the rows:

Copied to clipboard! Playground
<tbody>
    {rows.map((row, rowIndex) => (
        <tr key={rowIndex}>
            {Object.entries(row).map((entry, columnIndex) => (
                <td key={columnIndex}>
                    {columnIndex === 0
                        ? entry[1]
                        : <input
                            type={getInputType(entry[1])}
                            checked={getInputType(entry[1]) === 'checkbox' ? entry[1] : null}
                            value={rows[rowIndex][entry[0]]}
                            placeholder={headings?.[columnIndex]}
                        />
                    }
                </td>
            ))}
        </tr>
    ))}
</tbody>
EditableTable.jsx
Display table entries in the tbody

Here, we need to create a nested loop. First, we need to loop through the rows (each object inside the array), and then loop through the columns (each property inside each object).

To achieve this, we can use Object.entries inside rows.map. This returns a multidimensional array, where each array contains the key and value pairs of the object:

🚫 Refresh console

This way, we can reference both the key and the value, where entry[0] represents the key and entry[1] represents the value. Inside Object.entries, we have the following logic:

  • Lines 6-7: If we're at the 0th index, we know we're processing the ID. This should not be editable by users, so we can display them as is. This step is optional, and depending on your use case, you can omit this part. It assumes that the first element of the object is always the id.
  • Line 8-12: For each property inside the object, we create an input field with the following values.
    • type: To set the correct type for the input, we'll use a helper function that accepts the value of the property.
    • checked: Using the same helper function, we'll set the checked property if the value of the property is a boolean. It means that for true and false values, a checkbox will be created.
    • value: To set the value of the input, we can reference it using rows[rowIndex][entry[0]], which translates to an index and a key. For example: rows[0]['name'] will place the value of the name property of the first object in the array into the input.
    • placeholder: For the placeholder, we can use the headings prop combined with columnIndex. This means if the input is empty, the placeholder will show the same text as the column's heading.
πŸ” Login to get access to the full source code in one piece. With TypeScript types included.

The getInputType function

To get the proper input type based on the value of the property, add the following helper function in front of the return statement:

Copied to clipboard! Playground
const getInputType = value => {
    const map = {
        number: 'number',
        string: 'text',
        boolean: 'checkbox'
    }

    return map[typeof value]
}
EditableTable.jsx
Add the getInputType function

Inside the function, we can define a map where we map the type of the passed value to the input type. This means that the function will return a string based on the passed value. For example:

Copied to clipboard!
getInputType('string') -> 'text'
getInputType(123)      -> 'number'
getInputType(true)     -> 'checkbox'
The return values of getInputType
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master Reactinfo Remove ads

Updating State

There's only one thing left for us to do, and that is to update the state of the table and call the callback prop so we can handle the updated data. For this, introduce a new useState hook and pass the rows prop as a default value:

Copied to clipboard! Playground
import { useState } from 'react'

export const EditableTable = ({ headings, rows, callback }) => {
    const [state, setState] = useState(rows)

    const getInputType = value => { ... }

    return (
        <table>
            ...
            <tbody>
                {state.map((row, rowIndex) => (
                    <tr key={rowIndex}>
                        {Object.entries(row).map((entry, columnIndex) => (
                            <td key={columnIndex}>
                                {columnIndex === 0
                                    ? entry[1]
                                    : <input
                                        type={getInputType(entry[1])}
                                        checked={getInputType(entry[1]) === 'checkbox' ? entry[1] : null}
                                        value={state[rowIndex][entry[0]]}
                                        placeholder={headings?.[columnIndex]}
                                    />
                                }
                            </td>
                        ))}
                    </tr>
                ))}
            </tbody>
        </table>
    )
}
EditableTable.jsx
Add a new useState hook for the rows

Replace rows.map and the value attribute on line:21 with state.map. At this stage, we've injected the static rows data into the state of the component. To also update the state whenever the input changes, add the following update function to the component and call it in the onChange prop of the input:

Copied to clipboard! Playground
const update = (event, index, key) => {
    const parseValue = {
        'checkbox': e => e.target.checked,
        'number': e => Number(e.target.value),
        'text': e => e.target.value
    }

    rows[index][key] = parseValue[event.target.type](event)

    setState([ ...rows ])
    callback(state)
}

// Inside the return, add an onChange prop to the input:
<input
    ...
   onChange={(e) => update(e, rowIndex, entry[0])}
/>
EditableTable.jsx
Add the update function

This function excepts three parameters: the event object, rowIndex, and the key of the property to update. With these three parameters, we can update the correct property inside the state (which is an array of objects):

Copied to clipboard! Playground
const rows = [
    {
        id: '6512fb5737b7fd3482d41d90',
        name: 'Church Hamilton',
        score: 1232,
        age: 17,
        isActive": false
    }
]

rows[0]['name'] -> 'Church Hamilton'
How index and key can reference the correct property

To grab the correct value from the event object, we need a map called parseValue. Each of its properties represents a different input type. Based on the input type, it returns its value using the correct reference:

  • checkbox: Checkboxes values can be referenced through e.target.checked.
  • number: For numbers, we can typecast them from strings to numbers using Number(e.target.value).
  • text: For text-based inputs, we can use e.target.value.

Notice that each property returns a function that we need to call with the event passed to the update function: parseValue[event.target.type](event)

To get the type of input, we can use event.target.type, which will be either checkbox, number or text. Last but not least, we need to call the updater function, as well as the callback prop and pass it the state.

Copied to clipboard!
setState([ ...rows ])
callback(state)
Updateing state at the end of the update function

As objects are passed by reference in JavaScript, we need to use the spread operator to create a new array when calling setState; otherwise, the state will not be updated.


Summary

In summary, creating editable dynamic tables in React can be achieved in less than 100 lines of code with a single useState hook. If you would like to expand upon this solution, check out how you can also export your HTML tables without any extra libraries.

πŸ” Login to get access to the full source code in one piece. With TypeScript types included.

If you would like to learn more about working in React, check out our React projects roadmap, a comprehensive guide that offers inspiration on what to build next and how to approach it. Thank you for reading through, happy coding!

React Projects Roadmap
  • twitter
  • facebook
React
Did you find this page helpful?
πŸ“š More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.