How to send two values/Id from Aura component to Javascript Controller in Salesforce!

Passing a record Id from an Aura component into its JavaScript controller looks like it should be trivial, and then you hit the wall: inside an iteration, every rendered element fires the same handler, and the handler has no idea which row it came from.

The fix is HTML data-* attributes. You hang the values you need on the element itself, then read them back off the event. Here is the version that works, including the one mistake that catches nearly everyone.

Step 1: put the values on the element

Inside your iteration, add a data- attribute for each value you want to carry through. The names are yours, they just have to start with data-:

<aura:iteration items="{!v.records}" var="iterator">
    <a data-category="{!iterator.value1}"
       data-id="{!iterator.value2}"
       onclick="{!c.action}">
        {!iterator.label}
    </a>
</aura:iteration>

Both values now travel with the element. No attributes on the component, no events, no helper round trip.

Step 2: read them in the controller

({
    action: function (component, event, helper) {
        var el  = event.currentTarget;
        var cat = el.dataset.category;
        var id  = el.dataset.id;

        console.log('category: ' + cat + ', id: ' + id);
    }
})

dataset converts each data-* attribute into a camelCase property, so data-category becomes dataset.category. If you prefer being explicit, el.getAttribute('data-category') does the same job.

The mistake almost everyone makes

Use event.currentTarget, not event.target.

They look interchangeable and behave identically right up until they do not:

  • event.target is the element that was actually clicked, which may be a child. If your anchor wraps an icon, a span, or any text node, clicking it returns that child, and your data- attributes come back undefined.
  • event.currentTarget is the element the handler is attached to, which is the one carrying your attributes. It is correct every time.

This is why the technique works fine in a bare test component and then breaks the moment someone puts an icon inside the link. If your values are mysteriously undefined only sometimes, this is why.

Passing more than two values

There is no limit. Add as many attributes as you need:

<a data-id="{!iterator.recordId}"
   data-stage="{!iterator.stage}"
   data-index="{!iterator.index}"
   onclick="{!c.handleRowClick}"> ... </a>

One caveat worth knowing: every value arrives as a string. A number comes back as "42", and a boolean comes back as "false", which is truthy. Convert before you compare:

var index  = parseInt(el.dataset.index, 10);
var isOpen = el.dataset.isOpen === 'true';

The same thing in Lightning Web Components

If you are migrating this pattern to LWC, it carries over almost unchanged:

<!-- template -->
<template for:each={records} for:item="rec">
    <a key={rec.Id}
       data-id={rec.Id}
       data-category={rec.Category__c}
       onclick={handleClick}>{rec.Name}</a>
</template>

// JavaScript
handleClick(event) {
    const id  = event.currentTarget.dataset.id;
    const cat = event.currentTarget.dataset.category;
}

Same attributes, same currentTarget rule, same string conversion caveat.

Quick troubleshooting

  • Values are undefined: you are using event.target. Switch to currentTarget.
  • Attribute name not found: dataset is camelCase. data-record-id becomes dataset.recordId, not dataset.record-id.
  • Handler never fires: check the binding is {!c.action} and that a controller method of that exact name exists.
  • Comparison always true: remember every value is a string.

That is the whole pattern. It is a small amount of markup, it avoids component events entirely for this kind of row-level interaction, and once you know the currentTarget rule it is reliable.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *