# Display Data in a Table with Inline Editing

To display Salesforce data in a table, use the `lightning-datatable` component. The component supports inline editing, which enables users to update a field value without navigating to the record.

:::note
This documentation uses the term *`lightning-datatable` component* and *datatable* interchangeably.
:::

To load a list of records, use the [`getListUi (Deprecated)`](/docs/platform/lwc/guide/reference-get-list-ui.md) wire adapter or use SOQL in an Apex method. See [Data Guidelines](/docs/platform/lwc/guide/data-guidelines.md).

This example uses SOQL in an Apex method to load contacts on an account record.

```apex
public with sharing class ContactController {

    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList(String accId) {
        return [
            SELECT AccountId, Id, FirstName, LastName, Title, Phone, Email
            FROM Contact
            WHERE AccountId = :accId
            WITH USER_MODE
        ];
    }
}
```

The `@AuraEnabled(cacheable=true)` annotation exposes the method to Lightning components and caches the list of contacts on the client. The `accId` parameter filters the contacts for the given account record.

![A toast notification displaying as a green banner in the LWC Recipes sample app.](https://a.sfdcstatic.com/developer-website/sfdocs/lwc/media/datatable_edit.png)

If the Apex method returns data, the HTML template displays the `lightning-datatable` component with editable first name and last name fields (1). When you edit a field, the **Save** button appears, and you can click it to update the contact record. Updating a contact record successfully also updates the record data in the [related lists](https://help.salesforce.com/articleView?id=lex_find_related_lists.htm) (2), and other components on the page that are managed by Lightning Data Service.

## Implement Inline Editing

:::tip
This example is based on the `datatableInlineEditWithUiApi` component in the [lwc-recipes](https://github.com/trailheadapps/lwc-recipes) repo. This version differs slightly to enable the datatable to be used on a record page.
:::

To enable inline editing, configure the `lightning-datatable` component as shown in this `apexContactsForAccount` template.

```html
<!-- apexContactsForAccount.html -->
<template>
  <lightning-card title="Datatable Example" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <template lwc:if={contacts.data}>
        <lightning-datatable
          key-field="Id"
          data={contacts.data}
          columns={columns}
          onsave={handleSave}
          draft-values={draftValues}
        >
        </lightning-datatable>
      </template>
      <template lwc:if={contacts.error}>
        <!-- Handle Apex error -->
      </template>
    </div>
  </lightning-card>
</template>
```

The required `key-field` attribute associates each row with a contact record. The `data` attribute holds the data retrieved through the wire service from either an Apex method or a wire adapter. The `columns` attribute assigns a record field to each column and customizes the behavior of the columns. When a user edits a cell, the updated value is stored in `draft-values`.

Clicking the **Save** button triggers the `save` event. Use the `onsave` event handler to persist changes in the datatable. In the example, the `onsave` event handler calls [`updateRecord(recordInput, clientOptions)`](/docs/platform/lwc/guide/reference-update-record.md) to save record changes.

:::note
For a list of attributes and supported features, see [`lightning-datatable`](https://developer.salesforce.com/docs/platform/lightning-component-reference/guide/lightning-datatable).
:::

## Enable Inline Editing in Columns

A row of data corresponds to a single record and each column displays the value of one of that record’s fields. To enable inline editing, specify which fields are editable by setting `editable: true` in the column definition. In this example, the First Name and Last Name fields are editable. Since the Name field on contacts is a compound field, you must use the First Name and Last Name fields separately.

```javascript
// apexContactsForAccount.js

import { LightningElement, wire, api } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";
import { refreshApex } from "@salesforce/apex";
import { updateRecord } from "lightning/uiRecordApi";

import { ShowToastEvent } from "lightning/platformShowToastEvent";
import FIRSTNAME_FIELD from "@salesforce/schema/Contact.FirstName";
import LASTNAME_FIELD from "@salesforce/schema/Contact.LastName";
import TITLE_FIELD from "@salesforce/schema/Contact.Title";
import PHONE_FIELD from "@salesforce/schema/Contact.Phone";
import EMAIL_FIELD from "@salesforce/schema/Contact.Email";
import ID_FIELD from "@salesforce/schema/Contact.Id";

const COLS = [
  {
    label: "First Name",
    fieldName: FIRSTNAME_FIELD.fieldApiName,
    editable: true
  },
  {
    label: "Last Name",
    fieldName: LASTNAME_FIELD.fieldApiName,
    editable: true
  },
  { label: "Title", fieldName: TITLE_FIELD.fieldApiName, editable: true },
  {
    label: "Phone",
    fieldName: PHONE_FIELD.fieldApiName,
    type: "phone",
    editable: true
  },
  {
    label: "Email",
    fieldName: EMAIL_FIELD.fieldApiName,
    type: "email",
    editable: true
  }
];
export default class DatatableInlineEditWithUiApi extends LightningElement {
  @api recordId;
  columns = COLS;
  draftValues = [];

  @wire(getContactList, { accId: "$recordId" })
  contacts;

  async handleSave(event) {
    // Convert datatable draft values into record objects
    const records = event.detail.draftValues.slice().map((draftValue) => {
      const fields = Object.assign({}, draftValue);
      return { fields };
    });

    // Clear all datatable draft values
    this.draftValues = [];

    try {
      // Update all records in parallel thanks to the UI API
      const recordUpdatePromises = records.map((record) => updateRecord(record));
      await Promise.all(recordUpdatePromises);

      // Report success with a toast
      this.dispatchEvent(
        new ShowToastEvent({
          title: "Success",
          message: "Contacts updated",
          variant: "success"
        })
      );

      // Display fresh data in the datatable
      await refreshApex(this.contacts);
    } catch (error) {
      this.dispatchEvent(
        new ShowToastEvent({
          title: "Error updating or reloading contacts",
          message: error.body.message,
          variant: "error"
        })
      );
    }
  }
}
```

`@wire(getContactList)` provisions data to `contacts.data` and the record Id to `accId`.

When you edit fields, `event.detail.draftValues` stores the edited field values and the record `Id` as an array of objects.

```javascript
[
  {
    FirstName: "Sean",
    Id: "003R0000002J2wHIAS"
  },
  {
    FirstName: "Jack",
    Id: "003R0000002J2wIIAS"
  }
];
```

When you press the Tab key or click outside the cell after making changes, the datatable footer appears with the **Cancel** and **Save** buttons. To hide the datatable footer, clear the `draftValues` property. The `onsave` event handler clears `draftValues` after copying the values into a record object.

Call [`updateRecord(recordInput, clientOptions)`](/docs/platform/lwc/guide/reference-update-record.md) to save the new field values to the records.

`updateRecord()` expects a single record only. Pass in a `recordInput` object for each record whose fields you’re updating. In this example, the `record` parameter contains an object with the updated fields for multiple records. To update multiple records simultaneously, pass the parameter to `updateRecord()` for processing.

After a successful save, the `onsave` event handler dispatches the `ShowToastEvent` event to display a success toast message.

After a record is updated, `refreshApex(this.contacts)` refreshes the list of contacts from the Apex method so that the datatable always presents the latest data.

:::note
For bulk record updates in a single transaction, we recommend using Apex. See the **Enabling Inline Editing Using Apex** section.
:::

## Enable Inline Editing Using Apex

For bulk record updates, pass your record changes to an Apex controller that calls an `update` Data Manipulation Language (DML) operation. In this example, the edited field values are passed to the `updateContacts` Apex controller. Since the records are updated by Apex, you must notify Lightning Data Service (LDS) using the [`notifyRecordUpdateAvailable(recordIds)`](/docs/platform/lwc/guide/reference-notify-record-update.md) function so that the Lightning Data Service cache and wires are refreshed.

:::note
This example is based on the `datatableInlineEditWithApex` component in the [github.com/trailheadapps/lwc-recipes](https://github.com/trailheadapps/lwc-recipes) repo. This version differs slightly by using Apex without LDS.
:::

In the `apexContactsForAccount.js` file you created in the previous example, import the Apex controller and the `notifyRecordUpdateAvailable` function.

```javascript
import updateContacts from "@salesforce/apex/ContactController.updateContacts";
import { notifyRecordUpdateAvailable } from "lightning/uiRecordApi";
```

Update the `handleSave()` function to handle the edited values. To ensure that `notifyRecordUpdateAvailable()` is called after the record is updated via Apex, use the `async`/`await` pattern or a Promise chain. This example uses `async`/`await`.

```javascript
async handleSave(event) {
    const updatedFields = event.detail.draftValues;

    // Prepare the record IDs for notifyRecordUpdateAvailable()
    const notifyChangeIds = updatedFields.map(row => { return { "recordId": row.Id } });

    try {
        // Pass edited fields to the updateContacts Apex controller
        const result = await updateContacts({data: updatedFields});
        console.log(JSON.stringify("Apex update result: "+ result));
        this.dispatchEvent(
            new ShowToastEvent({
                title: 'Success',
                message: 'Contact updated',
                variant: 'success'
            })
        );

        // Refresh LDS cache and wires
        notifyRecordUpdateAvailable(notifyChangeIds);

        // Display fresh data in the datatable
        await refreshApex(this.contacts);
            // Clear all draft values in the datatable
            this.draftValues = [];
        }
   } catch(error) {
           this.dispatchEvent(
               new ShowToastEvent({
                   title: 'Error updating or refreshing records',
                   message: error.body.message,
                   variant: 'error'
               })
         );
    };
}
```

The `updateContacts` Apex controller deserializes the JSON string containing the updated fields into a Contact object. It updates the changed records using the `update` DML operation.

```apex
@AuraEnabled
public static string updateContacts(Object data) {
    List<Contact> contactsForUpdate = (List<Contact>) JSON.deserialize(
         JSON.serialize(data),
         List<Contact>.class
    );
    try {
        update contactsForUpdate;
        return 'Success: contacts updated successfully';
    }
    catch (Exception e) {
        return 'The following exception has occurred: ' + e.getMessage();
    }
}
```

**See Also**

- [*Apex Developer Guide*: JSON Support](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_json_overview.htm)
- [*Apex Developer Guide*: How DML Works](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_examples.htm)
