LWC getRecords wire adapter: how to retrieve records dynamically
Tags: salesforce, lwc, wire, getrecords
Published: 2022-12-02 11:34:18

In this article, I would like to share use cases, syntax, workarounds and features of using the `getRecords` wire adapter in the Lightning Web Components (LWC) framework in Salesforce.

##Ways to retrieve record data in LWC
Development by using the LWC often includes some kind of operations with records in the database: creating, retrieving, updating, and deleting.

There are different ways to retrieve information about the record by its Id while working with LWC. 
The simplest and the most limited is by using Lightning Base Components such as “lightning-record-form” or “lightning-record-view-form”. You can retrieve field labels and values for the specific record id and that’s it. The most customizable way is, as always, calling custom Apex. You can retrieve all the needed record details. However, every call to the backend takes more time than operations on the client-side and Apex methods should be called imperatively.
In case you have ids of records (even of different objects!) and want to retrieve their field values (translated and original ones), record type, or last modified details, I recommend using the @wire `getRecord` or `getRecords` adapter.

## `getRecord` vs `getRecords` comparison

These two wire adapters are very similar - both of them belong to 'lightning/uiRecordApi', and accept record id(s) and fields to retrieve parameters.
The only thing is that `getRecords` operates over an array of record ids instead of only one id. For sure, it can accept an array that contains only one id.
Also, `getRecords` returns a collection of batch result records - each record for each provided id.
If you are unaware of how many record ids should be processed or if you expect more than one id, consider using the `getRecords` adapter.

## Syntax

```javascript
import { LightningElement, wire } from 'lwc';
import { getRecords } from 'lightning/uiRecordApi';

@wire(getRecords, { records: [ { recordIds: string[], fields: string[] } ] })
propertyOrFunction

@wire(getRecords, { records: [ { recordIds: string[], fields: string[], optionalFields?: string[] } ] })
propertyOrFunction
```

More details you may find [in the official documentation](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reference_wire_adapters_records).

Please note: usage of `fields` or `optionalFields` parameter is mandatory. The difference is that while using `fields` and the context user doesn’t have access to this field, an error occurs. If the user hasn’t access to the field from the `optionalFields` parameter - it doesn’t cause an error, just this field won’t be returned. Think about which approach is more convenient in your case and don’t forget that you can use both parameters at the same time.

## How to retrieve records for dynamic record Ids

There are various examples of retrieving record data using `getRecords` for predefined ids for one or for several different objects. Please take a look at the examples from the Salesforce documentation.

For one object:

```javascript
import { LightningElement, wire } from 'lwc';
import { getRecords } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/User.Name';
import EMAIL_FIELD from '@salesforce/schema/User.Email';

export default class GetRecordsExample extends LightningElement {
    @wire(getRecords, {
        records: [
            {
              recordIds: ['005XXXXXXXXXXXXXXX', '005XXXXXXXXXXXXXXX'],
              fields: [NAME_FIELD],
              optionalFields: [EMAIL_FIELD]
            }
        ]
    }) wiredRecords;
}
```

For multiple objects:

```javascript
import { LightningElement, wire } from 'lwc';
import { getRecords } from 'lightning/uiRecordApi';
import USER_NAME_FIELD from '@salesforce/schema/User.Name';
import USER_EMAIL_FIELD from '@salesforce/schema/User.Email';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';

export default class GetRecordsExample extends LightningElement {
    @wire(getRecords, {
        records: [
            {
              recordIds: ['005XXXXXXXXXXXXXXX'],
              fields: [USER_NAME_FIELD],
              optionalFields: [USER_EMAIL_FIELD]
            },
            {
              recordIds: ['001XXXXXXXXXXXXXXX'],
              fields: [ACCOUNT_NAME_FIELD]
            },
        ]
    }) wiredRecords;
}
```

As for now, the main difficulty is the inability (almost) to retrieve records when there are no predefined ids and objects. They are set only statically and there’s no way to write them down dynamically in the array of objects.

In such an unusual case I recommend creating an array `parameterObject` that will include all record parameters as a single property.
This object can be created in the scope of `connectedCallback`, `renderedCallback`, or at the level of the parent component that calls the current one.
The format will look something like this:

```javascript
this.recordIds.forEach(recordId =>{
   this.parameterObject.push({
       recordIds: [recordId],
       optionalFields: this.fieldsToRetrieve
   })
});
```

This is the structure that will retrieve records dynamically for different objects when there’s no prepared structure of `parameterObject` with predefined Ids. Let’s consider an example of using the `getRecords` adapter when records should be retrieved dynamically.

## Example of using `getRecords` for dynamic retrieving data for different objects

Let’s imagine a case when we need to retrieve contact records and the current user’s record and display them in the lightning datatable on Account’s record page.
As `getRecords` operates over record Ids, let’s create two components: `parentComponent` and `childComponent`. Method `getContacts` on the parent component will retrieve the Ids of contacts related to the current Account. **Yes, we could retrieve all the record details at this stage from Apex, but we have this logic just to demonstrate a case when we have only Id and no other record data.**

parentComponent.html. Here we transfer `contactIds` to the child component.

```html
<template>
    <c-child-component record-ids={contactIds}></c-child-component> 
</template>
```

parentComponent.js

```javascript
import { LightningElement, wire, api } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContactsByAccount';

export default class ParentComponent extends LightningElement {

    @api recordId; //current Account's Id
    contactIds = [];

    @wire(getContacts, {accountId: '$recordId'})
    retrievedContacts({error, data}){
        if(data){
            this.contactIds = data;
        } else if(error){
            console.log('error: ', error);
        }
    }

}
```

And Apex controller ContactController that retrieves contact Ids:

```java
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactsByAccount(Id accountId) {
        return [
                SELECT
                    Id
                FROM
                    Contact
                WHERE
                    AccountId = :accountId
        ];
    }
}
```

The next one is childComponent.html. Here we display all the data in the form of a lightning-datatable Lightning Base Component.

```html
<template>
    <template if:true={dataIsRetrieved}>
        <lightning-datatable
                data={dataToDisplay}
                columns={columnsToDisplay}
                key-field="id"
                hide-checkbox-column="true">
        </lightning-datatable>
    </template>
</template>
```

And the last but not least part - childComponent.js.

```javascript
import { LightningElement, wire, api, track } from 'lwc';
import { getRecords } from 'lightning/uiRecordApi';
import CONTACT_NAME_FIELD from '@salesforce/schema/Contact.Name';
import CONTACT_EMAIL_FIELD from '@salesforce/schema/Contact.Email';
import CONTACT_PHONE_FIELD from '@salesforce/schema/Contact.Phone';
import USER_NAME_FIELD from '@salesforce/schema/User.Name';
import USER_EMAIL_FIELD from '@salesforce/schema/User.Email';
import USER_PHONE_FIELD from '@salesforce/schema/User.Phone';

import userId from '@salesforce/user/Id';

export default class ChildComponent extends LightningElement {

    dataIsRetrieved = false;
    contactIds = [];
    parameterObject;
    dataToDisplay = [];
    columnsToDisplay = [
        { label: 'Object', fieldName: 'object'},
        { label: 'Id', fieldName: 'id'},
        { label: 'Name', fieldName: 'name' },
        { label: 'Email', fieldName: 'email', type: 'email'},
        { label: 'Phone', fieldName: 'phone', type: 'phone'},
        { label: 'Last Modified Date', fieldName: 'lastModified', type: 'date'}
    ];

    @api get recordIds(){
        return this.contactIds;
    }
    set recordIds(contacts){
        this.contactIds = contacts;
        this.parameterObject = [];

        if(this.contactIds.length > 0){
            this.contactIds.forEach(contact => {
                this.parameterObject.push({
                    recordIds: [contact.Id],
                    optionalFields: [CONTACT_NAME_FIELD, CONTACT_EMAIL_FIELD, CONTACT_PHONE_FIELD]
                });
            });
            this.parameterObject.push({
                recordIds: [userId],
                optionalFields: [USER_NAME_FIELD,USER_EMAIL_FIELD, USER_PHONE_FIELD]
            });
        }
    }

    @wire(getRecords, { records: '$parameterObject'})
    wiredRecords({ err, data }) {
        if (err) {
            console.log('error: ',err);
        } else if (data) {
            this.dataIsRetrieved = true;
            console.log('getRecords results: ',data.results);
            data.results.forEach(record => {
                this.dataToDisplay.push({
                    name: record.result.fields.Name.value,
                    email: record.result.fields.Email.value,
                    id: record.result.id,
                    phone: record.result.fields.Phone.value,
                    object: record.result.apiName,
                    lastModified: record.result.lastModifiedDate
                });
            });
        }
    }

}
```

As you may see, `getRecords` uses `‘$parameterObject’` with all the parameters inside for retrieving records. A dollar sign $ means using a dynamic variable. The method will be called every time when the dynamic variable is updated. However, dynamic update works for `getRecord` and other methods that accept primitive types as a parameter, not a collection. That is why I recommend using `parameterObject` for `getRecords`. This `parameterObject` is an array that is filled dynamically when ids of contacts are received from the parentComponent. If the number of Ids or retrieved objects may vary, this is the best workaround for dynamic retrieval records using `getRecords`.

The final result is the following:

![getRecords result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/avc0nps4njyk0lkpk977.png)

## Summary

To sum up, getRecords is a very useful solution for some specific cases when record data should be retrieved by Id, especially from different objects - to minimize the amount of SOQL queries and calls to the backend.

If you found this article helpful, I invite you to connect with me on [LinkedIn](https://www.linkedin.com/in/tdrnk/). I am always looking to expand my network and connect with like-minded individuals in the Salesforce area. Additionally, you can also reach out to me for any questions or feedback on the article. I'd be more than happy to engage in a conversation and help out in any way I can. So don’t hesitate to contact me, and let’s connect and learn together!