# Salesforce Tips — Beyond The Cloud > 100 short Salesforce tips covering Apex, Lightning Web Components, SOQL, testing, administration and the Salesforce CLI. > Each tip was published as a single square slide; the text below is the full transcript. > Code samples are included as fenced blocks; they were transcribed from the slide artwork. Catalog: https://blog.beyondthecloud.dev/tips Topics: Apex (27), LWC (25), DevOps & CLI (12), Admin & Setup (8), SOQL (7), Tooling (6), Async Apex (4), CSS & Styling (3), JavaScript (3), Testing (3), Integration (2) ## Apex ### Apex Comparator Interface URL: https://blog.beyondthecloud.dev/tips/apex-comparator-interface Tags: debugging Summary: Thankfully, in Winter ‘24 Apex got similar interface that we can use with Lists! ### How to compare custom classes easily? If you came from Java, you probably miss this useful interface: ```apex java.util Interface Comparator ``` Thankfully, in Winter ‘24 Apex got similar interface that we can use with Lists! ```apex Comparator ``` Okay, and how to use it? It's fairly simple, let's start with something we can compare, like student grades: ```apex public class Student { public String name; public Integer grade; public Student(String name, Integer grade) { this.name = name; this.grade = grade; } } ``` Now we need comparator implementation: ```apex public class GradesAscCompare implements Comparator { public Integer compare(Student s1, Student s2) { if (s1.grade == s2.grade) { return 0; } return s1.grade > s2.grade ? 1 : -1; } } ``` We can do it as a standalone class like above, or a collection of different comparators for our use case: ```apex public class StudentComparators { public class GradesAscCompare implements Comparator { public Integer compare(Student s1, Student s2) { if (s1.grade == s2.grade) { return 0; } return s1.grade > s2.grade ? 1 : -1; } } public class GradesDscCompare implements Comparator { public Integer compare(Student s1, Student s2) { if (s1.grade == s2.grade) { return 0; } return s1.grade < s2.grade ? 1 : -1; } } } ``` Okay, but what does this code mean? As you can see, the compare method returns three values: -1, 0, 1. Each of those values corresponds to one of the comparison results: -1 lesser than 0 equal to 1 greater than So when we try to compare four students with following grades [5, 2, 3, 5] we will get those comparison results: compare(5, 2) compare(5, 3) compare(5, 5) compare(2, 5) compare(2, 3) … and so on How it works in our example? Test code: ```apex List students = new List{ new Student('a', 5), new Student('b', 2), new Student('c', 3), new Student('d', 5) }; students.sort(new StudentComparators.GradesAscCompare()); System.debug(students); students.sort(new StudentComparators.GradesDscCompare()); System.debug(students); ``` Results: ```bash |DEBUG|Grades ascending: ({ Student b - Grade: 2}, { Student c - Grade: 3}, { Student a - Grade: 5}, { Student d - Grade: 5}) |DEBUG|Grades descending: ({ Student a - Grade: 5}, { Student d - Grade: 5}, { Student c - Grade: 3}, { Student b - Grade: 2}) ``` ### Apex sharing keywords URL: https://blog.beyondthecloud.dev/tips/apex-sharing-keywords Tags: testing, best-practices, security Summary: Apex code works based on the closest sharing keyword. This means that for every class you can define different behavior, and even run multiple scenarios from a single start point! ### Do you know how Apex sharing keywords work? Apex code works based on the closest sharing keyword. This means that for every class you can define different behavior, and even run multiple scenarios from a single start point! Let’s check an example: ```apex public with sharing class Parent { public static List getRecords(String sharingModel) { switch on sharingModel { when 'without' { return WithoutSharing.getRecords(); } when 'with' { return WithSharing.getRecords(); } when 'inherited' { return InheritedSharing.getRecords(); } when else { return DefaultSharing.getRecords(); } } } } ``` Now, we will test how it works: ```apex System.runAs(creator) { insert creatorRecords; } System.runAs(reader) { insert readerRecords(); } List readerList; Test.startTest(); System.runAs(reader) { readerList = Parent.getAccounts(SHARING_KEYWORD); } Test.stopTest(); Assert.areEqual(EXPECTED_SIZE, readerList.Size()); ``` Assumptions: Creator User: 2 records with Reader User: 1 record with private sharing private sharing Results: ```bash TEST NAME OUTCOME MESSAGE RUNTIME (MS) ParentTest.testDefaultSharing Pass Reader got 3 records 316 ParentTest.testInheritedSharing Pass Reader got 2 records 204 ParentTest.testWithSharing Pass Reader got 2 records 208 ParentTest.testWithoutSharing Pass Reader got 3 records 199 ``` Last question, what when a class doesn’t have an access keyword? ```apex public class DefaultSharing { public static List getRecords() { return [SELECT Id, Name FROM Record]; } } ``` By default, Apex runs in system mode, so the above code runs the same as the class with “without sharing” defined. It is a good practice to always define what sharing class should use! ```apex public without sharing class DefaultSharing { public static List getRecords() { return [SELECT Id, Name FROM Record]; } } ``` ### ApexLogs good practices URL: https://blog.beyondthecloud.dev/tips/apexlogs-good-practices Tags: best-practices, debugging Summary: Go to Developer Console, Query Editor and do the following: 1. Enter SELECT Id, LogLength FROM ApexLog ORDER BY LogLength DESC into input and click “Execute” 2. Select all the logs (logs… ### Do you struggle with too many debug logs? When opening the developer console, this message greets you? ![ApexLogs good practices — screenshot 1](https://blog.beyondthecloud.dev/tips/apexlogs-good-practices/fig-1.webp) There are a few things that can be done! First, clean up the existing logs: ![ApexLogs good practices — screenshot 2](https://blog.beyondthecloud.dev/tips/apexlogs-good-practices/fig-2.webp) Go to Developer Console, Query Editor and do the following: 1. Enter SELECT Id, LogLength FROM ApexLog ORDER BY LogLength DESC into input and click “Execute” 2. Select all the logs (logs are sorted from biggest to smallest) 3. Click “Delete Row” to remove no longer needed log Good practices, to prevent it in the future: 1. When setting user trace flags, select only the period you will be using the logs, smaller the better: ![ApexLogs good practices — screenshot 3](https://blog.beyondthecloud.dev/tips/apexlogs-good-practices/fig-3.webp) ![ApexLogs good practices — screenshot 4](https://blog.beyondthecloud.dev/tips/apexlogs-good-practices/fig-4.webp) 2. Try to never set logs for Integration users or Test Automation users 3. Remove unnecessary System.debug from the files ```apex HttpResponse response = http.send(request); System.debug(response.getBody()); System.debug(response.getStatusCode()) ``` 4. Use Logger framework or Debugger tools instead of System.debug ### Assign record values dynamically in Apex URL: https://blog.beyondthecloud.dev/tips/assign-record-values-dynamically-in-apex Tags: — Summary: Each standard and custom object inherits methods from the SObject class. ### Assign record values dynamically in Apex Each standard and custom object inherits methods from the SObject class. There are two methods worth considering in the context of our tip: ```apex put(String fieldName, Object value) put(Schema.SObjectField field, Object value) ``` It can be very useful in Test Data Factory frameworks where values need to be overridden. Dynamic assignment works perfectly with Maps, where the Field is a key. ```apex public void updateFields(SObject record, Map fieldToValue) { for (SObjectField field : fieldToValue.keySet()) { record.put(field, fieldToValue.get(field)); } } ``` so usage can look like this: ```apex updateFields(myAccount, new Map{ Account.Name => 'My Account', Account.Industry => 'IT' }); ``` ```apex updateFields(myAccount, new Map{ 'Name' => 'My Account', 'Industry' => 'IT' }); ``` ### Cache with getters and setters URL: https://blog.beyondthecloud.dev/tips/cache-with-getters-and-setters Tags: soql, security Summary: Static data such as object metadata or user profiles Sets and Maps Results of SOQL queries Results of API callouts ### How to cache data with getters and setters in Apex? Getters and Setters can be great tools for caching data in Apex. What can be cached? Static data such as object metadata or user profiles Sets and Maps Results of SOQL queries Results of API callouts While you can always use Platform Cache, for simple use cases getters and setters may be more suitable. Let’s check an example Sometimes the information provided by the UserInfo class is not sufficient. You can easily add more information and cache it so that only one SOQL query will be executed. ```apex public with sharing class CurrentUserInfo { public static User cachedUserDetails { get { if (cachedUserDetails == null) { cachedUserDetails = [ SELECT Id, Name, FederationIdentifier, ProfileId, UserType, AccountId FROM User WHERE Id = :UserInfo.getUserId() ]; } return cachedUserDetails; } private set; } public static String getFederationIdentifier() { return cachedUserDetails.FederationIdentifier; } public static String getAccountId() { return cachedUserDetails.AccountId; } } ``` ### Catch Multiple Exceptions URL: https://blog.beyondthecloud.dev/tips/catch-multiple-exceptions Tags: debugging Summary: Did you know that Exceptions can be chained? You can chain exception catch! Did you know that Exceptions can be chained? ```apex try { // do some stuff } catch (Exception a) { // logic after A exception } catch (Exception b) { // logic after B exception } ``` It can be useful if you want to implement different strategies depending on a thrown exception: ```apex try { Example.doSomethingRisky(); } catch (NullPointerException nullProblem) { Helper.callSystemAdmin(); } catch (DmlException insertProblem) { System.debug('It\'s not a big deal!'); } ``` It also works with custom exceptions: ```apex try { Example.doSomethingRisky(); } catch (MyQueueStackException customException) { System.enqueueJob(new EmergencyQueueable()); } catch (LastHopeException hopeException) { GodClass.sendForHelp(); } ``` ### Check Transaction Context URL: https://blog.beyondthecloud.dev/tips/check-transaction-context Tags: debugging, async Summary: The Request class is a viable solution to your problem. Check the next page for details. ### Check transaction context Have you ever needed your transaction to be context-aware? Have you wanted to call different logic depending on when it is invoked? Ever encountered an issue with calling a future method from another future method? ```apex Request.getCurrent().getQuiddity() ``` The Request class is a viable solution to your problem. Check the next page for details. getQuiddity method will return one of the possible transaction contexts: ANONYMOUS, AURA, BATCH_ACS, BATCH_APEX, BATCH_CHUNK_PARALLEL, BATCH_CHUNK_SERIAL, BULK_API, COMMERCE_INTEGRATION, DISCOVERABLE_LOGIN, EXTERNAL_SERVICE_CALLBACK, FUNCTION_CALLBACK, FUTURE, INBOUND_EMAIL_SERVICE, INVOCABLE_ACTION, PLATFORM_EVENT_PUBLISH_CALLBACK, POST_INSTALL_SCRIPT, QUEUEABLE, QUICK_ACTION, REMOTE_ACTION, REST, RUNTEST_ASYNC, RUNTEST_DEPLOY, RUNTEST_SYNC, SCHEDULED, SOAP, SYNCHRONOUS, TRANSACTION_FINALIZER_QUEUEABLE, VF Let's see an example: a class that executes its logic in either a future or synchronous context, depending on whether the current context is a future method. This approach helps prevent the dreaded 'System.AsyncException: Future method cannot be called from a future or batch method.’ ```apex public class MyFutureCall { public static void callMe() { if(Request.getCurrent().getQuiddity() == System.Quiddity.FUTURE) { mySyncMethod(); } else { myFutureMethod(); } } @future private static void myFutureMethod() { System.debug('Executing future method logic'); } private static void mySyncMethod() { System.debug('Execute synchronous logic'); } } ``` ### Current class name URL: https://blog.beyondthecloud.dev/tips/current-class-name Tags: debugging Summary: To get the names, simply create a new exception and check a stack trace! ### Current class name How to find the name of a class or method that is currently running? To get the names, simply create a new exception and check a stack trace! HandledException handledException = new HandledException(); System.debug(handledException.getStackTraceString()); ```bash Class.ExampleController.run: line 3, column 1 Class.AccountsController.runTest: line 14, column 1 ``` ### Now let's get only names of Class and Method as Strings: public class ExecutionUtils { public static String getRunningClass() { HandledException handledException = new HandledException(); return handledException.getStackTraceString() .substringAfterLast('Class.') .substringBefore('.'); } public static String getRunningMethod() { HandledException handledException = new HandledException(); return handledException.getStackTraceString() .substringAfterLast('Class.') .substringBefore(':') .substringAfter('.'); } ```bash 22:27:55.81 (98588124)|USER_DEBUG|[3]|DEBUG|ExampleController 22:27:55.81 (98702496)|USER_DEBUG|[4]|DEBUG|run ``` ### Database Operation Access Level URL: https://blog.beyondthecloud.dev/tips/database-operation-access-level Tags: best-practices, security Summary: Apex code, by default, operates in system mode, granting it significantly higher permissions compared to the user executing it. ### Database Operation Access Level Apex code, by default, operates in system mode, granting it significantly higher permissions compared to the user executing it. To enhance the security context of Apex, you can specify user-mode access for database operations. It is important to use proper context depending on the requirements to ensure the data is secure. Let’s check an example Remember, using USER_MODE enforces sharing access even in without sharing Apex class context! ```apex // Respecting security of the running user List acc = [SELECT Id, Name FROM Account WITH USER_MODE]; // Running in the system mode List acc = [SELECT Id, Name FROM Account WITH SYSTEM_MODE]; ``` It is recommended to use WITH USER_MODE instead of WITH SECURITY_ENFORCED because it has few advantages like accounting for polymorphic fields like Task.WhatId or finding all the errors of the query. ```apex List acc = [SELECT Id, Name FROM Account WITH SECURITY_ENFORCED]; ``` ```apex Account acc = new Account(Name = 'Acme Inc.'); // Respecting security of the running user insert as user acc; // Running in the system mode update as system acc; ``` User and System modes are available also from other overloaded Apex methods, and can be used via AccessLevel modes (USER_MODE, SYSTEM_MODE). ```apex // Some query methods with AccessLevel support Database.query(String queryString, System.AccessLevel accessLevel); Database.queryWithBinds(String queryString, Map bindMap, System.AccessLevel accessLevel); Database.getQueryLocator(sObject [] listofQueries, System.AccessLevel accessLevel); Database.countQuery(String query, System.AccessLevel accessLevel); Search.query(String query, System.AccessLevel accessLevel); // Some DML methods with AccessLevel support Database.insert(List records, Boolean allOrNone, System.AccessLevel accessLevel); Database.update(List records, Boolean allOrNone, System.AccessLevel accessLevel); Database.delete(List records, Boolean allOrNone, System.AccessLevel accessLevel); ``` Best practice dictates that the mode be explicitly set for each database operation, regardless of whether the default system mode is selected. This approach provides a clear understanding of the context in which your code operates, enhancing transparency. ```apex Account acc = new Account(Name = 'Acme Inc.'); // Should insert record in system mode insert acc; ``` ```apex Account acc = new Account(Name = 'Acme Inc.'); // Should insert record in system mode insert as system acc; ``` ### DataWeave in Apex URL: https://blog.beyondthecloud.dev/tips/dataweave-in-apex Tags: — Summary: DataWeave is a programming language, created by MuleSoft for accessing and transforming data. Salesforce allows to use DataWeave scripts in Apex, to simplify complex transformation… ### DataWeave in Apex Have you ever wonder if you can use simpler ways for transforming data in Apex? Do you know about DataWeave language? If not, let’s explore it! DataWeave is a programming language, created by MuleSoft for accessing and transforming data. Salesforce allows to use DataWeave scripts in Apex, to simplify complex transformation processing. To use DataWeave, you must first create the DataWeave Resource, with the transformation script. ```apex %dw 2.0 //Header, contains all definitions (vars, input, output) input payload application/xml output application/apex --- //Body, contains transformations payload.Accounts.*Account map(record) -> { Name: record.name, Phone: record.phone, BillingCountry: record.planet, BillingCity: record.region, } as Object {class: "Account"} ``` This script is getting XML Account data as input, and returning Salesforce Accounts. Once DataWeave Resource is created, you can use it directly in Apex to transform the data. ```apex String inputXML = '\n' + '\n' + '\n' + 'Jabba\'s Palace\n' + 'Desert of Tatooine\n' + 'Tatooine\n' + '(555) 555-1212\n' + '\n' + '\n' + 'Mos Eisley Cantina\n' + 'Mos Eisley\n' + 'Tatooine\n' + '(555) 555-1212\n' + '\n' + '\n' + 'Imperial Palace\n' + 'Coruscant\n' + 'Coruscant\n' + '(555) 555-1212\n' + '\n' + ''; DataWeave.Script script = new DataWeaveScriptResource.xmlToAccount(); DataWeave.Result result = script.execute(new Map{'payload' => inputXML}); List accounts = (List) result.getValue(); ``` This way, we can avoid complex transformations in Apex, in favor of the DataWeave language that is designed for that purpose. But should we always use it in case of any transformations? DataWeave execution is taking noticeable CPU time cost for initialization and transformation (but almost not affected by data volume that is transformed). ✅ When to use it? Any transformations that are complex or contains huge volume of data Error prone formats ❌ When to avoid it? Small transformations with a small volume of data Frequent transformations that need to be performed as quickly as possible Most UI-initiated synchronous process Other Considerations Available without any additional licenses Maximum 50 DataWeave Resources per org Importing modules/scripts not supported (but allows built in modules, i.e. dw::Core) Want to try it? You can use DataWeave Playground site! https://dataweave.mulesoft.com/learn/dataweave ### Declare static functions in anonymous Apex URL: https://blog.beyondthecloud.dev/tips/declare-static-functions-in-anonymous-apex Tags: debugging Summary: Declare static functions and classes in Anonymous Apex Declare static functions and classes in Anonymous Apex Did you know that the code written in Anonymous Apex is compiled in class Anon? This means that you can use static functions and classes in Anonymous Apex without even deploying them to the org: ```apex public class MyClass { public MyClass() { System.debug('My Class'); } } Anon.MyClass t = new Anon.MyClass(); ``` MyClass is an inner class so it can’t have static properties and methods: ```apex public class MyClass { public MyClass() { System.debug('My Class'); } static String myStaticMethod() { System.debug('This is not going to work!'); } } ``` If you need to use static methods you can declare them directly in Anonymous Apex: ```apex public static void myStaticMethod() { System.debug('My Static Method'); } Anon.myStaticMethod(); ``` ### Do not check if List is empty URL: https://blog.beyondthecloud.dev/tips/do-not-check-if-list-is-empty Tags: — Summary: DML operations on empty lists are NOT consuming the transaction DML limits? ### Do not check if List is empty Did you know that: DML operations on empty lists are NOT consuming the transaction DML limits? ```apex List accountsToInsert = new List(); if (!accountsToInsert.isEmpty()) { insert accountsToInsert; } ``` ```apex List accountsToInsert = new List(); insert accountsToInsert; ``` ### Don't name classes after System classes URL: https://blog.beyondthecloud.dev/tips/dont-name-classes-after-system-classes Tags: — Summary: Each defined object, custom or standard, is automatically represented by its own apex class. ### Do not create classes using standard object names! It might be tempting to create class like this: ![Don't name classes after System classes — screenshot 1](https://blog.beyondthecloud.dev/tips/dont-name-classes-after-system-classes/fig-1.webp) This will lead to unexpected results. Each defined object, custom or standard, is automatically represented by its own apex class. When we overwrite this default class, each existing or new reference is replaced by the new class, which is usually lacking a lot of implementation. In the best-case scenario, you will see something like this: ![Don't name classes after System classes — screenshot 2](https://blog.beyondthecloud.dev/tips/dont-name-classes-after-system-classes/fig-2.webp) In the worst-case scenario, your entire Salesforce instance might have problems operating! ### Effective variable debugging URL: https://blog.beyondthecloud.dev/tips/effective-variable-debugging Tags: debugging Summary: When you use System.debug() to display values of lists or big objects you usually see that the output is truncated: ### Effective variable debugging: avoiding truncation When you use System.debug() to display values of lists or big objects you usually see that the output is truncated: ```apex System.debug([SELECT Id, Name FROM Account LIMIT 100]); ``` ```bash 10:05:46:011 USER_DEBUG [1]|DEBUG|(Account:{Id=0010Y00001BmjXlQAJ, Name=JP Community Users}, Account:{Id=0010Y00001BqMUMQA3, Name=My Account #1}, Account:{Id=0010Y000016unWHQAY, Name=Customers}, Account:{Id=0010Y000016uofsQAA, Name=CommunityUsers}, Account:{Id=0010Y000016unWnQAI, Name=Partne ``` You can simply use JSON.serializePretty() method to see the full content of debugged variable: ```apex System.debug(JSON.serializePretty([SELECT Id, Name FROM Account LIMIT 100])); ``` ```json 09:05:46.1 (47092646)|USER_DEBUG|[3]|DEBUG|[ { "attributes" : { "type" : "Account", "url" : "/services/data/v59.0/sobjects/Account/0010Y00001BmjXlQAJ" }, "Id" : "0010Y00001BmjXlQAJ", "Name" : "JP Community Users" } ``` ### Fetch Metadata Records without using SOQL URL: https://blog.beyondthecloud.dev/tips/fetch-metadata-records-without-using-soql Tags: soql Summary: You can save on SOQL query limits when fetching Custom Metadata records. ### Fetch Custom Metadata records in Apex without SOQL You can save on SOQL query limits when fetching Custom Metadata records. ```apex List customApp = [ SELECT Id, Class__c FROM Custom_App__mdt WHERE DeveloperName = 'MyCustomApp' ]; ``` To retrieve a single record by name, simply use the following code: ```apex Custom_App__mdt mc = Custom_App__mdt.getInstance('MyCustomApp'); ``` Check the next page to see how to fetch all Custom Metadata records of a specific type. To retrieve all records for a specified metadata type, use the getAll() method: ```apex Map customApps = Custom_App__mdt.getAll(); ``` However, be cautious when using the getAll() method. If your metadata type contains a large amount of data and records, it can consume significant heap space, which may lead to unhandled exceptions. Always check the size of the retrieved data before using it in your code! ### Generate Unique Ids URL: https://blog.beyondthecloud.dev/tips/generate-unique-ids Tags: debugging Summary: Starting from the Spring ’24 release, you can generate UUIDs (Universal Unique Identifiers) in Apex. ### Generate Unique Id in Apex Starting from the Spring ’24 release, you can generate UUIDs (Universal Unique Identifiers) in Apex. Use the new UUID class for that ```apex UUID uniqueId = UUID.randomUUID(); System.debug(uniqueId); yourCrazyMethodAssigningIds(uniqueId.toString()); ``` ### Generate UUID in Apex URL: https://blog.beyondthecloud.dev/tips/generate-uuid-in-apex Tags: — Summary: UUID, or Universally Unique Identifier, is a 128-bit label with a probability of duplication considered close enough to zero to be negligible. ### Easily generate UUID in Apex Ever needed a UUID in Salesforce? UUID, or Universally Unique Identifier, is a 128-bit label with a probability of duplication considered close enough to zero to be negligible. ```bash 02c90c10-dc3f-4478-8ac5-fa869de4b061 ``` You don't need to build your own class to obtain the UUID in Apex. To retrieve UUID v4, all you have to do is use this one-liner: ```apex (String)((Map) JSON.deserializeUntyped(new Auth.JWT().toJSONString())).get('jti') ``` The code snippet is also pasted in the first comment, ready for you to copy! ### Get set of Ids from SObject List URL: https://blog.beyondthecloud.dev/tips/get-set-of-ids-from-sobject-list Tags: soql Summary: Get set of Ids from your SObject list without iteration ### Get set of Ids from your SObject list without iteration You don’t need to iterate through the list of SObjects to get the set of their Ids: ```apex Set leadsIds = new Set(); for (Lead l : leads) { leadsIds.add(l.Id); } ``` You can simply cast your List to Map and then use keyset: ```apex Set leadsIds = (new Map(leads)).keySet(); ``` Get set of Ids from your SObject list without iteration You can also do that directly on SOQL query result: ```apex Set leadsIds = new Map([ SELECT Id FROM Lead LIMIT 10 ]).keySet(); ``` ### Methods inherited from the Object class URL: https://blog.beyondthecloud.dev/tips/methods-inherited-from-the-object-class Tags: debugging Summary: The problem with the Object class is that it is not properly documented in Salesforce documentation and it’s hard to find information about inherited methods. We can only get more… ### Methods inherited from the Object class The Object is a supertype for all: standard objects custom objects primitive types collections classes The problem with the Object class is that it is not properly documented in Salesforce documentation and it’s hard to find information about inherited methods. We can only get more details while reading Java documentation and assuming it will work in the same way. The following methods are inherited from the Object class: toString() Returns a string representation of the object. You can override toString() method in your class. ```apex public class A {} System.debug(new A().toString()); // A:[] public class A { public override String toString() { return 'Hello toString()'; } } System.debug(new A().toString()); // 'Hello toString()' ``` ### equals() Indicates whether some other object is "equal to" this one. ```apex public class A {} public class B {} System.debug(new A().equals(new B())); // false System.debug(new A().equals(new A())); // false System.debug(new A().equals('Some String')); // false ``` You cannot override standard equals method like toString, but still, you can have one. It’s beneficial when you need to create Custom Types in Map Keys and Sets. ```apex public class A { public Boolean equals(Object objectToCompare) { return true; } } public class B {} System.debug(new A().equals(new B())); // true System.debug(new A().equals(new A())); // true System.debug(new A().equals('Some String')); // true ``` ### hashCode() Returns a hash code value for the object. ```apex public class A {} System.debug(new A().hashCode()); // 2069582068 System.debug('Some String'.hashCode()); // 2147069117 ``` You cannot override standard equals method like toString, but still, you can have one. It’s beneficial when you need to create Custom Types in Map Keys and Sets. ```apex public class A { public Integer hashCode() { return 1234; } } System.debug(new A().hashCode()); // 1234 ``` clone() Creates and returns a copy of this object. ```apex public class A { public override String toString() { return 'My A'; } public Boolean equals(Object objectToCompare) { return true; } } A oldA = new A(); A clonedA = oldA.clone(); System.debug(oldA); // My A System.debug(clonedA); // My A System.debug(clonedA.equals(oldA)); // true ``` ### Optimize Apex Triggers URL: https://blog.beyondthecloud.dev/tips/optimize-apex-triggers Tags: soql, performance, best-practices, async Summary: 1. Refactoring 2. Checking entry criteria 3. Moving to asynchronous tools 4. Using frameworks ### How to optimize Apex Triggers? Do you struggle with poor performance during Trigger Execution? Is your code hitting any of the platform limits? You can improve it by using one of the following strategies: 1. Refactoring 2. Checking entry criteria 3. Moving to asynchronous tools 4. Using frameworks ### Refactor The first step that we can take to optimize triggers is to check if our code can be improved. You should start by bulkifying all methods. Next, identify unnecessary code executions, duplicates or other simple mistakes. ```apex public class AccountTriggerHandler { public static void beforeInsert(List newAccounts) { for ( Account account: newAccounts ) { try { AccountUtils.checkForCompetition(account); } catch (Exception e) {} } } } public class AccountUtils { public static void checkForCompetition(Account account) { for(Lead lead: [SELECT Name FROM Lead WHERE Name LIKE account.Name]) { ... } } } ``` Delegating parts of the logic to external classes is generally considered good practice. However, it can also be risky. While the previous example is an exaggeration, such delegation can lead to issues like SOQL/DML operations inside loops or nested loops, which can negatively impact trigger performance. Refactor All trigger methods should process more than one record at the time. ```apex public class AccountTriggerHandler { public static void beforeInsert(List newAccounts) { List checkForCompetition = new List(); for ( Account account: newAccounts ) { if(account.RecordType.Name == 'Partner'){ checkForCompetition.add(account); } } AccountUtils.checkForCompetition(checkForCompetition); } } public class AccountUtils { public static void checkForCompetition(List accounts) { ... } } ``` ### Optimize entry criteria The next important step is adding or improving entry criteria. It is applicable for both, trigger records and all SOQL queries you make. ```apex public class AccountTriggerHandler { public static void beforeInsert(List newAccounts) { for ( Account account: newAccounts ) { if(account.RecordType = 'Partner') { AccountUtils.checkForCompetition(account); } } } } ``` It is important to ensure that your code only executes for the necessary set of records, instead of the entire data set. The same goes for queried records, you should add a filter to make sure only needed data is returned. Escape with asynchronous Another way is to move parts of the logic to asynchronous execution. Every action that is complicated, or when we don’t need instant results, can be moved out of the original trigger transaction. ```apex public class AccountTriggerHandler { public static void afterUpdate(List newAccounts, Map oldAccounts) { List emailChanged = new List(); for ( Account account: newAccounts ) { if(account.Email != oldAccounts.get(account.Id).Email){ updateStakeholder.add(account.Id); } } AccountUtils.updateStakeholder(emailChanged); } } public class AccountUtils { @Future public static void updateStakeholder(List accounts) { ... } } ``` Use Enterprise Patterns The last piece that you can use to improve the situation is to use some frameworks that implement enterprise patterns. In case of triggers, you can use Unit of Work. ```apex public class AccountTriggerHandler { public static void afterUpdate(List newAccounts, Map oldAccounts) { ... uow.registerDirty(contact); ... uow.registerDirty(case); ... uow.commitWork(); } } ``` ```apex public class AccountTriggerHandler { public static void afterUpdate(List newAccounts, Map oldAccounts) { ... uow.registerDirty(contact); ... uow.registerDirty(case); ... uow.commitWork(); } } ``` ### Prevent record insert in Apex URL: https://blog.beyondthecloud.dev/tips/prevent-record-insert-in-apex Tags: — Summary: You can create validations in the Apex trigger code to prevent the insertion of selected records? ### Prevent record save in Apex Did you know that: You can create validations in the Apex trigger code to prevent the insertion of selected records? ```apex for (SObject record : Trigger.new) { if (!isValid(record)) { throw new DmlException(); } } ``` Prevent saving only records that failed validation: ```apex for (SObject record : Trigger.new) { if (!isValid(record)) { record.addError('Validation failed!') } } ``` More details: SObject Class reference ### Remove System.debugs URL: https://blog.beyondthecloud.dev/tips/remove-system-debugs Tags: performance, debugging Summary: System.debugs statements are a straightforward way to debug our Apex code, but... ### Remove System.debugs System.debugs statements are a straightforward way to debug our Apex code, but... Did you know that System.debug can significantly affect Apex code performance? ```apex System.debug('Here'); System.debug('My Debug'); System.debug('Account ==> ' + acc); ``` Debugs that contain complex data types have the most significant impact on performance. Values need to be converted to a String to pass them as parameters to System.debug. If there is no specific reason to keep debugs in your code, you should always remove them! ### Running Reports in Apex URL: https://blog.beyondthecloud.dev/tips/running-reports-in-apex Tags: soql, debugging, admin Summary: You can utilize the ReportManager class to run reports directly in Apex! ### Running Reports in Apex Did you know that you can use Apex to retrieve data from your database without using SOQL queries? You can utilize the ReportManager class to run reports directly in Apex! ```apex Reports.reportResults res = Reports.ReportManager.runReport(reportId, true); ``` After running the report, you can retrieve the data from it. Check the next page for an example of how to get all values from a simple report without groupings ```apex Map rf = (Map)res.getFactMap(); for(Reports.ReportFactWithDetails rfwd : rf.values()) { // Retrieve all data rows from your report result List reportRows = rfwd.getRows(); for(Reports.ReportDetailRow rr : reportRows) { List rdCells = rr.getDataCells(); System.debug('Row data: '); Integer dataCellCounter = 0; // Do something with every single field of the data row: for(Reports.ReportDataCell rdc : rdCells) { System.debug( 'Column: ' + columns[dataCellCounter] + ', Label: ' + rdc.getLabel() + ', Value: ' + rdc.getValue() ); dataCellCounter++; } } } ``` This method is more complex than simply using SOQL queries, but in this case, no SOQL queries were used, and query record limits were not increased by even a single row. Check the Reports Namespace reference in official documentation to explore the full potential of running reports in Apex. ### Set Default Values for Invocable Action Parameters URL: https://blog.beyondthecloud.dev/tips/set-default-values-for-invocable-action-parameters Tags: — Summary: Starting with the Summer '24 release, you can now set default values for your invocable actions. These default values will be visible in Flow Builder. Let's see how easy this is! ### How to set default values to invocable action parameters? Starting with the Summer '24 release, you can now set default values for your invocable actions. These default values will be visible in Flow Builder. Let's see how easy this is! ```apex public class FlowParameters { @InvocableVariable( label = 'Parameter With Default Value' defaultValue = 'My Default Value' placeholderText = 'My Placeholder' ) public String myExampleParameter; } ``` First, add default value annotation to your invocable variable Now, default value is available in Flow Builder. You can also notice that it is possible to set the custom label for your variable: ![Set Default Values for Invocable Action Parameters — screenshot 1](https://blog.beyondthecloud.dev/tips/set-default-values-for-invocable-action-parameters/fig-1.webp) If you decide to not use default value, you just need to simply toggle the checkbox. Now, instead of the default value you will see the placeholder, that can be also specified on your variable: ![Set Default Values for Invocable Action Parameters — screenshot 2](https://blog.beyondthecloud.dev/tips/set-default-values-for-invocable-action-parameters/fig-2.webp) ### Throw Apex built-in exceptions URL: https://blog.beyondthecloud.dev/tips/throw-apex-built-in-exceptions Tags: debugging, integration Summary: I suppose you've used AuraHandledException many times, but you can throw also other exceptions. ### Throw Apex built-in exceptions Did you know that you can throw Apex's built-in exceptions? There's no need to create your own exceptions. I suppose you've used AuraHandledException many times, but you can throw also other exceptions. ```apex try { CalloutException exception = new CalloutException(); exception.setMessage('My built-in callout exception!'); throw exception; } catch (Exception e) { System.debug(e.getMessage()); } ``` Here is a list of standard exceptions you can throw: AsyncException CalloutException DmlException EmailException ExternalObjectException InvalidParameterValueException LimitException (though it still can't be caught) JSONException ListException MathException NoAccessException NoDataFoundException NoSuchElementException NullPointerException QueryException RequiredFeatureMissingException SearchException SecurityException SerializationException SObjectException StringException TypeException VisualforceException Should you throw built-in exceptions instead of creating your own? As we read in the documentation: An exception denotes an error that disrupts the normal flow of code execution. You can use Apex built-in exceptions or create custom exceptions. All exceptions have common methods. The perfect use case for built-in exceptions can be logic that wraps standard functionality: Unit of Work with DmlException Selector Layer with QueryException Callout Service with CalloutException ```apex public SObject toObject() { List records = toList(); if (records.size() > 1) { QueryException e = new QueryException(); e.setMessage('List has more than 1 row for assignment to SObject'); throw e; } if (records.size() == 0) { return null; // handle: List has no rows for assignment to SObject } return records[0]; } ``` ### Working with Salesforce limits URL: https://blog.beyondthecloud.dev/tips/working-with-salesforce-limits Tags: soql Summary: We can use Limits class to check how many resources we have left. Check example on the next page! ### How to work with Salesforce limits? You probably have seen a similar message before: ```bash 15:38:46.90 |EXCEPTION_THROWN|[101]|System.LimitException: Too many SOQL queries: 101 15:38:46.90 |FATAL_ERROR|System.LimitException: Too many SOQL queries: 101 ``` And you probably know that you cannot prevent it, how can we do something about it? We can use Limits class to check how many resources we have left. Check example on the next page! In the case of SOQL rows, we can use this code: ```apex if (Limits.getLimitQueries() - Limits.getQueries() == 0) { throw new CustomLimitException(); } ``` Now instead of uncatchable System.LimitException, code will throw a custom one which can be handled! When is it useful? You shouldn’t enclose all your code in the try/catch blocks from now on. This trick is useful in scenarios when you know that the operation you perform can hit a limit, and you want to account for that. A good example would be processing big amounts of data: ```apex if (Limits.getLimitHeapSize() - Limits.getHeapSize() < 500000) { System.enqueueJob(new RunNexJob(dataChunk)); } ``` This code will create new Job (new transaction) for the rest of data if we are close to HeapSize limit. See the link in the description for more information! ### You are doing validation in Apex wrong! URL: https://blog.beyondthecloud.dev/tips/you-are-doing-validation-in-apex-wrong Tags: security Summary: Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request… ### You are doing validation in Apex wrong! Use Chain of Responsibility design pattern, but what is that? Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. First create Validator abstract class: ```apex public abstract class Validator { protected Validator next; public virtual Validator setNext(List validations) { if (!validations.isEmpty()) { next = (Validator) Type.forName(validations.remove(0)).newInstance(); next.setNext(validations); } return this; } public abstract Boolean isValid(Account record); } ``` And then extend it with your custom isValid, validation method: ```apex public class AccountIndustryValidator extends Validator { private static final List SUPPORTED_INDUSTRIES = new List{ 'Electronics', 'Engineering' }; public override Boolean isValid(Account account) { return SUPPORTED_INDUSTRIES.contains(account?.Industry) && (next == null ? true : next.isValid(account)); } } ``` Finally, we can use new Validators, to check if the Account we got is a valid one: ```apex public with sharing class AccountController { public static Account createAccount(Account account) { Validator validator = new AccountIndustryValidator() .setNext(new List{ 'AccountTypeValidator' }); if (validator.isValid(account)) { insert account; } return account; } } ``` We create the first Validator manually, then all the logic is handled recursively thanks to implementation in an abstract class. In this implementation we are leveraging Dynamic Apex, it gives a lot of flexibility, you can change implementation to use, for example, Custom Metadata! ## LWC ### Boolean parameters in LWC URL: https://blog.beyondthecloud.dev/tips/boolean-parameters-in-lwc Tags: — Summary: In first component we’ve skipped boolean-parameter - in that way our component will use the default value, which is false. In second component simple adding boolean-parameter as… Boolean parameters in LWC In LWC you can pass parameters values from parent to child, but do you know how to pass a boolean parameter correctly? ```javascript import { LightningElement, api } from 'lwc'; export default class MyComponent extends LightningElement { @api booleanParameter = false; connectedCallback() { console.log(this.booleanParameter); } } ``` ```markup ``` If you’ll try to set boolean parameter in this way, you’ll actually set it as string, either “false” or “true”. You can cast it to boolean later, but why not doing it in right way from the beginning? ```markup ``` In first component we’ve skipped boolean-parameter - in that way our component will use the default value, which is false. In second component simple adding boolean-parameter as attribute (without value) will be parsed as boolean true value. ### Control tabs with WorkspaceAPI URL: https://blog.beyondthecloud.dev/tips/control-tabs-with-workspaceapi Tags: — Summary: With the Spring ‘24 release, Salesforce enabled the Workspace API in Lightning Web Components! ### Did you know you can use Workspace API in LWC? With the Spring ‘24 release, Salesforce enabled the Workspace API in Lightning Web Components! What does it mean? ![Control tabs with WorkspaceAPI — screenshot 1](https://blog.beyondthecloud.dev/tips/control-tabs-with-workspaceapi/fig-1.webp) Now you can control your application tabs - such as Home, shown above - within your LWC code. Learn more on the next page! By using the Workspace API, you can, for example, close one of the tabs. ```javascript import { LightningElement, wire } from 'lwc'; import { IsConsoleNavigation, getFocusedTabInfo, closeTab } from 'lightning/platformWorkspaceApi'; export default class TabController extends LightningElement { @wire(IsConsoleNavigation) isConsoleNavigation; closeHome() { if (this.isConsoleNavigation) { getFocusedTabInfo().then((tabInfo) => { closeTab(tabInfo.tabId); }); } } } ``` You can also focus, open new tab or refresh tab. Check link in post to learn more! ### Current User in LWC URL: https://blog.beyondthecloud.dev/tips/current-user-in-lwc Tags: apex Summary: There’s additional property which can be imported - isGuest. isGuest s a boolean value indicating whether the user is a guest user and it should be used in the Experience Builder sites. ### Current User ID in LWC Did you know that there’s a simple way to retrieve current user Id in LWC? Instead of passing it from APEX, you can simply import it in a way which is presented below: ```javascript import { LightningElement } from 'lwc'; import userId from '@salesforce/user/Id'; export default class CurrentUser extends LightningElement { // Expose to template userId = userId; } ``` There’s additional property which can be imported - isGuest. isGuest s a boolean value indicating whether the user is a guest user and it should be used in the Experience Builder sites. ```javascript import { LightningElement } from 'lwc'; import userId from '@salesforce/user/Id'; import isGuest from '@salesforce/user/isGuest'; export default class CurrentUser extends LightningElement { // Expose to template userId = userId; isGuest = isGuest; } ``` Those two properties might be especially useful while working on components for communities. ### Custom Labels in LWC URL: https://blog.beyondthecloud.dev/tips/custom-labels-in-lwc Tags: javascript Summary: To use labels in HTML, you need to create an additional component variable (labels). ### How to keep Custom Labels in LWC? There are several different ways to store Custom Labels in LWC. If you need to use a few labels we recommend the following approach: Create a separated file called customLabels.js. ![Custom Labels in LWC — screenshot 1](https://blog.beyondthecloud.dev/tips/custom-labels-in-lwc/fig-1.webp) What are the next steps? Import labels in the customLabels.js file. We are using ES6 module here, which allows us to share code. ```javascript import errorOccured from '@salesforce/label/c.ErrorOccured'; import firstName from '@salesforce/label/c.FirstName'; import lastName from '@salesforce/label/c.LastName'; import email from '@salesforce/label/c.Email'; const labels = { errorOccured, firstName, lastName, email }; export default labels; ``` Import labels from customLabels.js into your LWC. To use labels in HTML, you need to create an additional component variable (labels). ```javascript import { LightningElement } from 'lwc' import labels from './customLabels' export default class MyComponent extends LightningElement { labels = labels; // ... } ``` ```markup ``` Why a separate JS file for labels? Single Responsibility Principle - The sole responsibility of customLabel.js is to manage labels. Code Readability - The LWC component (myComponent.js) does not contain label imports, keeping the component's JS code clean. Lack of Dependencies - Each component has its own labels that can be changed independently. ### Debounce technique in LWC URL: https://blog.beyondthecloud.dev/tips/debounce-technique-in-lwc Tags: — Summary: Debouncing is a technique to make sure that the time consuming functions aren’t called too frequently. ### Debounce technique in LWC Debouncing is a technique to make sure that the time consuming functions aren’t called too frequently. The most common use case is when user is typing in search input and search method is being called. That call should be triggered only when user stops typing. But it can be used in many cases - like preventing user from double-clicking buttons. The usual timeout value is between 300 and 500 ms, anything around 1s might cause feeling of “delay”. Here’s how to prevent calling the search function on every change event. ```markup ``` ```javascript import { LightningElement } from 'lwc'; const DEBOUNCE = 300; export default class DebounceInput extends LightningElement { debounce; handleChange(event) { clearTimeout(this.debounce); // eslint-disable-next-line @lwc/lwc/no-async-operation this.debounce = setTimeout(() => { this.searchFunction(event.detail.value); }, DEBOUNCE); } searchFunction(search) { // call search function console.log(search); } } ``` ### Do not combine then and await in LWC URL: https://blog.beyondthecloud.dev/tips/do-not-combine-then-and-await-in-lwc Tags: javascript, best-practices Summary: The code is confusing. Devs who are not familiar with Promises very well, will not follow the code flow. It's hard to understand. Why do we need “await“ if there is “then“? What is the… ### Do Not Combine Then And Await in LWC JavaScript allows combining “then/catch“ and “async/await“. You can do something like this: ```javascript const myPromise = new Promise((resolve, reject) => setTimeout(() => resolve({ message: 'It works!' }), 200)); async function myCombinedFuntion() { await myPromise().then(result => { // ... }); } ``` But is it a good practice to use “then“ and “await“ together? ### You should NOT combine “then/catch“ and “async/await“ The code is confusing. Devs who are not familiar with Promises very well, will not follow the code flow. It's hard to understand. Why do we need “await“ if there is “then“? What is the idea behind it? Keep It Simple, Stupid (KISS) rule is broken here. You do NOT need constructions like that in your code. Do not mix “then“ and “await“ for the same Promise. Choose what is better for your code and stick to it. ```javascript const myPromise = new Promise((resolve, reject) => setTimeout(() => resolve({ message: 'It works!' }), 200)); async function myAwaitFunction() { try { const result = await myPromise(); // ... } catch(error) { console.error(error); } } function myThenFunction() { myPromise().then(result => { // ... }).catch(error => { console.error(error); }); } ``` ### Do not use JSON.stringify to pass params to Apex URL: https://blog.beyondthecloud.dev/tips/do-not-use-json-stringify-to-pass-params-to-apex Tags: apex, performance, best-practices Summary: Serializing parameters before passing them to Apex is considered bad practice because it leads to unclean code and negatively impacts performance. Do NOT use JSON.stringify to pass params to Apex Serializing parameters before passing them to Apex is considered bad practice because it leads to unclean code and negatively impacts performance. ```apex @AuraEnabled public static void call(String serializedParam) { MyWrapper wrapper = (MyWrapper) JSON.deserialize(serializedParam, MyWrapper.class); // ... } ``` ```javascript const params = { // ... }; await call({ serializedParam: JSON.stringify(params) }); ``` Apex data structures have corresponding equivalents in LWC that can be used directly. Please take a look at the table below for details. APEX LWC Primitive Types e.g Boolean, String, Integer Primitive Types List e.g List Array [ ] Custom Objects e.g Account, Custom__c Object { } Class e.g WrapperClass Object { } Map e.g Map Object { } Primitive Types ```apex @AuraEnabled public static void call(String stringParam, Integer integerParam) { } ``` ```javascript await call({ stringParam: 'Some String', integerParam: 42 }); ``` List ```apex @AuraEnabled public static void call(List myList) { // ... } ``` ```javascript const stringArray = [ 'Some String 1', 'Some String 2', 'Some String 3', ]; await call({ myList: stringArray }); ``` Custom Objects ```apex @AuraEnabled public static void call(Account account) { // ... } ``` ```javascript const newAccount = { Name: 'Account Name' }; await call({ account: newAccount }); ``` Wrapper Class ```apex public class MyWrapper { @AuraEnabled public String myString {get; set;} @AuraEnabled public Integer myInteger {get; set;} } ``` ```apex @AuraEnabled public static void call(MyWrapper myWrapper) { // ... } ``` ```javascript const myWrapper = { myString: 'Some String', myInteger: 43 }; await call({ myWrapper: myWrapper }); ``` Map ```apex @AuraEnabled public static void call(Map myMap) { // ... } ``` ```javascript const myMap = { param1: 'Some String 1', param2: 'Some String 2' }; await call({ myMap: myMap }); ``` ### Dynamic CSS classes in LWC URL: https://blog.beyondthecloud.dev/tips/dynamic-css-classes-in-lwc Tags: lwc, css, best-practices Summary: Nested ternaries in a class getter stop being readable the moment a second condition appears. A small classSet helper turns the same logic into a flat map of class name to condition. You've probably encountered code with dynamic CSS classes in LWC done in this way: ```javascript get wrapperClass() { return this.shouldHaveMarginTop ? 'slds-var-m-top_medium' : 'slds-m-top_none'; } ``` Which is not the best solution - once there's gonna be more classes, more conditions - it will become unreadable. ### Use a classSet helper instead Create a utils or helper component (if you don't have one) and create a classSet function. A basic implementation would look like this: ```javascript function classSet(config) { return Object.keys(config) .filter(key => config[key]) .join(' '); } export { classSet }; ``` Then import it into your component and start using it: ```javascript get wrapperClass() { return classSet({ 'slds-var-m-top_medium': this.shouldHaveMarginTop, 'slds-m-top_none': !this.shouldHaveMarginTop }); } ``` ```markup ``` You can use multiple classes and conditions in that way! ### Generic onChange event handler in LWC URL: https://blog.beyondthecloud.dev/tips/generic-onchange-event-handler-in-lwc Tags: — Summary: Some components have many input components or elements that we need to track in our variables. Creating handler methods for onChange events is the right way to update the variable in our… ### Generic onChange event handler in LWC Some components have many input components or elements that we need to track in our variables. Creating handler methods for onChange events is the right way to update the variable in our component. But how can we avoid having multiple handler methods that change only the variable value? For that, we can use a dynamic generic handler method. Let’s check an example ```markup ``` ```javascript handleOnChangeFirstName(event) { this.firstName = event.target.value; } handleOnChangeLastName(event) { this.lastName = event.target.value; } handleOnChangeIsCompany(event) { this.isCompany = event.target.checked; } // More handlers ``` Instead, use the data custom attribute with the same name as your component variable. ```markup ``` ```javascript handleOnChangeGeneric(event) { this[event.target.dataset.id] = event.target.value || event.target.checked; } ``` ### GET URL Params URL: https://blog.beyondthecloud.dev/tips/get-url-params Tags: — Summary: The easiest way to obtain URL parameters is by using CurrentPageReference. Below, you can find an example of currentPageReference. As you can see, all parameters are stored in the state… ### URL Params in LWC How to get URL Params in LWC? The easiest way to obtain URL parameters is by using CurrentPageReference. Below, you can find an example of currentPageReference. As you can see, all parameters are stored in the state property. ![GET URL Params — screenshot 1](https://blog.beyondthecloud.dev/tips/get-url-params/fig-1.webp) ```javascript // currentPageReference { attributes: { name: URL_Test_Page__c }, state: { lang: en_US, type: test-type, id: 000000000001 }, type: comm__namedPage } ``` Add the @wire(CurrentPageReference) method to your LWC component. The method will automatically fire every time URL parameters change. ```javascript import { LightningElement, wire } from 'lwc'; import { CurrentPageReference } from 'lightning/navigation'; export default class MyComponentName extends LightningElement { urlId = null; urlLanguage = null; urlType = null; @wire(CurrentPageReference) getStateParameters(currentPageReference) { if (currentPageReference) { this.urlId = currentPageReference.state?.id; this.urlLanguage = currentPageReference.state?.lang; this.urlType = currentPageReference.state?.type; } } } ``` ### How to deal with asynchronous code in LWC URL: https://blog.beyondthecloud.dev/tips/how-to-deal-with-asynchronous-code-in-lwc Tags: javascript, debugging, async Summary: This approach results in a callback nest that is difficult to understand and debug. On the next page, you will learn how to do it properly! ### Async in LWC You might have seen something like this before: ```javascript example(userId) { getAccount(userId).then(account => { return getPartner(account.Name).then(partner => { return getPartnerOrder(partner.OrderId).then(details => { this.partnerOrders = details; }); }); }); } ``` This approach results in a callback nest that is difficult to understand and debug. On the next page, you will learn how to do it properly! Use async/await when you want asynchronous code to behave like a synchronous one. JavaScript will pause function execution until the promise settles: ```javascript async example(userId) { const account = await getAccount(userId); const partner = await getPartner(account.Name); this.partnerOrders = await getPartnerOrder(partner.OrderId); } ``` If the data can be displayed later, or if you wish to perform another action only after the promise has been settled, utilize the then/catch block: ```javascript retrieveAccounts() { getAccounts({ country: this.country }) .then(accounts => { this.accounts = accounts; }) .catch(error => { console.error(error); }) .finally( () => { this.hideSpinner(); }) } ``` Do not combine then/catch and async/await It creates difficult to understand code, debugging is complicated and breaks the KISS principle ```javascript async function example() { return await myPromise().then(result => { console.log(result); }); } ``` ```javascript async example() { const data = await myPromise(); return data; } ``` Avoid nesting promises Instead of additional then/catch blocks, try to refactor the code into smaller blocks or use async/await instead. ```javascript async example(userId) { const account = await getAccount(userId); const partner = await getPartner(account.Name); this.partnerOrders = await getPartnerOrder(partner.OrderId); } ``` ```javascript example(userId) { getAccount(userId).then(account => { return getPartner(account.Name).then(partner => { return getPartnerOrder(partner.OrderId).then(details => { this.partnerOrders = details; }); }); }); } ``` Don’t create new promises In most cases, there is no reason to explicit create promises. And especially, don’t wrap promises within a promise. ```javascript example() { return new Promise((resolve, reject) => { asyncMethod .then(result => { resolve(result); }) .catch(error => { reject(error); }); }) } ``` ```javascript async example() { return await asyncMethod(); } ``` ```javascript example() { return asyncMethod().then(result => { console.log(result); }); } ``` ### How to format date in LWC URL: https://blog.beyondthecloud.dev/tips/how-to-format-date-in-lwc Tags: — Summary: In order to format date in LWC, you can use Intl.DateTimeFormat object which enables language-sensitive date and time formatting. ### How to format date in LWC In order to format date in LWC, you can use Intl.DateTimeFormat object which enables language-sensitive date and time formatting. The lightning-formatted-date-time component also uses Intl.DateTimeFormat in its codebase. ```javascript import { LightningElement } from 'lwc'; export default class FormatDate extends LightningElement { date = new Date(); formattedDate = new Intl.DateTimeFormat('en-GB').format(this.date); } ``` This example shows the shorthand version of DateTimeFormat function, with only one parameter passed - locale. Example value of formattedDate: '21/07/2024'. You can also pass an options argument with more advanced formatting rules: ```javascript import { LightningElement } from 'lwc'; export default class FormatDate extends LightningElement { date = new Date(); formattedDate = new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long', timeZone: 'Australia/Sydney' }).format(this.date); } ``` Example value of formattedDate: 'Monday 22 July 2024 at 02:00:00 GMT+10' You can find those options here. If you need to make your code aware of current users locale or timezone, you can simply import those properties from i18n module. ```javascript import { LightningElement } from 'lwc'; import LOCALE from '@salesforce/i18n/locale'; import TIMEZONE from '@salesforce/i18n/timeZone'; export default class FormatDate extends LightningElement { date = new Date(); formattedDate = new Intl.DateTimeFormat(LOCALE, { dateStyle: 'full', timeStyle: 'long', timeZone: TIMEZONE }).format(this.date); } ``` Remember: avoid overengineering and use the lightning-formatted-date-time when you can. ```markup ``` Links: @salesforce/i18n module lightning-formatted-date-time Intl.DateTimeFormat ### How to render Map in LWC URL: https://blog.beyondthecloud.dev/tips/how-to-render-map-in-lwc Tags: apex Summary: LWC for:each can't use Map to show elements, but it's easy to make it happen! ### Render Map in LWC How to render Map in LWC? LWC for:each can't use Map to show elements, but it's easy to make it happen! Get the data from the backend: ```apex @AuraEnabled(cacheable=true) public static Map getAccounts() { Map accounts = new Map(); for (Account account : [ SELECT Id, Name, NumberOfEmployees, (SELECT Id, Name, Email, Phone FROM Contacts) FROM Account ]) { accounts.put(account.Name, account); } return accounts; } ``` Then we simply transform the map into an array: ```javascript @wire(getAccounts, {}) wiredAccounts({ data, error }) { if(data) { this.data = Object.entries(data).map(([key, value]) => ({ name: key, employees: value.NumberOfEmployees, contacts: value.Contacts })); } else if(error){ console.error(error) } } ``` Why can it be useful? Simplified HTML looks like this: ```markup ``` The final solution can look like this: ![How to render Map in LWC — screenshot 3](https://blog.beyondthecloud.dev/tips/how-to-render-map-in-lwc/fig-3.webp) This can be useful for any kind of nested data, such as List of Lists, Maps, or Wrappers. The principle is to parse data into an Array, which then can be rendered by LWC for:each. ### How to share CSS styles between LWC components URL: https://blog.beyondthecloud.dev/tips/how-to-share-css-styles-between-lwc-components Tags: css, javascript Summary: Imagine that you want to build an internal CSS library or every component that you expose on the Experience Cloud site must reuse similar styling. ### How to share CSS styles between LWC components? You can reuse CSS styles between components. Imagine that you want to build an internal CSS library or every component that you expose on the Experience Cloud site must reuse similar styling. How to do that? Create an LWC Component with a .css style definition, without html template and javascript script. The structure should look like this: sharedStyles sharedStyles.css sharedStyles.js-meta.xml Define your reusable styles in the .css file ```css .reusable-card { display: flex; align-items: center; color: #747474; --slds-c-button-neutral-color-border: #99a9be; --slds-c-button-neutral-color-border-active: #5a9e3a; /*other styles, vars, design tokens overrides*/ } ``` You can import them in other LWC’s styling definition .css file ```css @import 'c/sharedStyles'; /* regular component styling */ .not-reusable__styling { color: #747474; } ``` ### How to use errorCallback URL: https://blog.beyondthecloud.dev/tips/how-to-use-errorcallback Tags: javascript, async Summary: LWC have dedicated errorCallback lifecycle hook, which can catch any uncaught errors from descendent components in its tree! How to use errorCallback LWC have dedicated errorCallback lifecycle hook, which can catch any uncaught errors from descendent components in its tree! ```javascript import { LightningElement } from 'lwc'; export default class ErrorCallback extends LightningElement { errorCallback(error) { } } ``` Let’s create two components: JavascriptError and ForcedError and simulate some errors. ```javascript import { LightningElement } from 'lwc'; export default class JavascriptError extends LightningElement { connectedCallback() { throw new Error('Javascript error!'); } } ``` ```javascript import { LightningElement } from 'lwc'; export default class ForcedError extends LightningElement { handleClick() { throw new Error('Forced error!'); } } ``` Both our components are inside of ErrorCallback component. We’re gonna print our errors with console.error. ```javascript import { LightningElement } from 'lwc'; export default class ErrorCallback extends LightningElement { errorCallback(error, stack) { console.error(error.message); console.error(stack); } } ``` Our first error came from connectedCallback in JavascriptError component - you can see its name in stack part. ![How to use errorCallback — screenshot 3](https://blog.beyondthecloud.dev/tips/how-to-use-errorcallback/fig-3.webp) Second error appeared after button click - which triggered handleClick function. It’s worth to mention that errorCallback won’t catch an error thrown during asynchronous operations like promises or timeouts - those types of errors you’ll have to handle on your own! ### How to use renderedCallback URL: https://blog.beyondthecloud.dev/tips/how-to-use-renderedcallback Tags: — Summary: LWC have dedicated renderedCallback lifecycle hook, which fires when component has finished rendering phase. It’s very useful, but can be dangerous when used incompetently. How to use renderedCallback LWC have dedicated renderedCallback lifecycle hook, which fires when component has finished rendering phase. It’s very useful, but can be dangerous when used incompetently. ```javascript import { LightningElement } from 'lwc'; export default class RenderedCallback extends LightningElement { renderedCallback() { } } ``` It’s crucial to understand phrase “when component has finished rendering phase”. There are multiple rendering cycles - every time when single variable changes on the template, the rendering cycle starts all over again - so renderedCallback will be called. ```markup ``` ```javascript import { LightningElement } from 'lwc'; export default class RenderedCallback extends LightningElement { number = 0; handleClick() { this.number++; } renderedCallback() { console.log('fired rendered callback because number variable got increased'); } } ``` So you must be prepared to avoid the infinite loops - especially, when you’re updating something in renderedCallback. ```markup ``` ```javascript import { LightningElement } from 'lwc'; export default class RenderedCallback extends LightningElement { number = 0; renderedCallback() { this.number++; } } ``` This example will cause the infinite loop. Always keep you code clean - functions should be named properly and when you’re using renderedCallback - remember about simple boolean flag to avoid execution of same code over and over again or stepping into infinite loop. ```javascript import { LightningElement } from 'lwc'; export default class RenderedCallback extends LightningElement { number = 0; hasRendered = false; handleClick() { this.number++; } renderedCallback() { if (this.hasRendered) { return; } // load external libraries // call functions // set the flag on the end this.hasRendered = true; } } ``` ### Instantiate LWC components dynamically URL: https://blog.beyondthecloud.dev/tips/instantiate-lwc-components-dynamically Tags: performance, security Summary: From the Winter’24 release, we can import and instantiate Lightning Web Components dynamically. LWC finally reached this feature, that Aura has from a long time. ### Instantiate LWC components dynamically From the Winter’24 release, we can import and instantiate Lightning Web Components dynamically. LWC finally reached this feature, that Aura has from a long time. This approach can help you prevent the unnecessary loading of extensive modules that may not be consistently required in your application, or when you're unsure of the specific component constructor until the application is running. To be able to instantiate an LWC component, lightning__dynamicComponent capability needs to be added to the component’s configuration file. ```markup 59.0 lightning__dynamicComponent ``` The minimum required api version is 55.0. To dynamically instantiate the component use element with lwc:is directive that passes the component contructor. ```markup 59.0 lightning__dynamicComponent ``` Component contractor can be obtain by using the import() syntax. ```javascript import { LightningElement } from "lwc"; export default class DynamicCmp extends LightningElement { componentConstructor; connectedCallback() { import("c/concreteComponent") .then(({ default: ctor }) => (this.componentConstructor = ctor)) .catch((err) => console.log("Error importing component")); } } ``` As long as concrete component expose the property with @api decorator, it can be passed via element. ```javascript import { LightningElement, api } from "lwc"; export default class ConcreteCmp extends LightningElement { @api text; } ``` ```markup ``` Considerations Lightning Web Security must be enabled. Dynamic components work either outside the packages or in Managed packages only - unlocked packages are unsupported. LWR Sites for Experience Cloud supports only statically analyzable dynamic imports. For this use case, import("c/analyzable") works, but import("c/" + "analyzable") doesn’t work because it isn’t statically analyzable. Performance As the name suggests, dynamic imports are loaded "on-the-fly", meaning they are not pre-loaded by the system. This can sometimes slow down user experience, as the system needs to fetch these modules when they're actually needed. That means - use it consciously! ```javascript import { LightningElement } from "lwc"; export default class DynamicCmp extends LightningElement { componentConstructor; connectedCallback() { import("c/concreteComponent") .then(({ default: ctor }) => (this.componentConstructor = ctor)) .catch((err) => console.log("Error importing component")); } } ``` ### Lightning Styling Hooks URL: https://blog.beyondthecloud.dev/tips/lightning-styling-hooks Tags: css Summary: You can use Lightning Styling Hooks to style standard LWC components provided by Salesforce that you wouldn’t necessarily want to write from scratch to make minor adjustments, such as… ### How to override standard components styling in LWC? You can use Lightning Styling Hooks to style standard LWC components provided by Salesforce that you wouldn’t necessarily want to write from scratch to make minor adjustments, such as background color or the border radius change. Which components can be styled this way? lightning-button lightning-card lightning-icon ...and many more - a full list of supported components can be found in the official documentation (link in the post). Let’s check an example Change the colors and border radius of every lightning-button (with neutral variant) inside the component ```css :host { --slds-c-button-neutral-color-border: #0064e1; --slds-c-button-neutral-color-border-active: #0064e1; --slds-c-button-neutral-color-border-hover: #92baec; --slds-c-button-radius-border: 20px; } ``` Change the colors and border radius of all neutral lightning-buttons that are children of the element with class=“container” ```css .container { --slds-c-button-neutral-color-border: #0064e1; --slds-c-button-neutral-color-border-active: #0064e1; --slds-c-button-neutral-color-border-hover: #92baec; --slds-c-button-radius-border: 20px; } ``` Change the colors and border radius of the specific neutral button with class=”custom-button” ```css .custom-button { --slds-c-button-neutral-color-border: #0064e1; --slds-c-button-neutral-color-border-active: #0064e1; --slds-c-button-neutral-color-border-hover: #92baec; --slds-c-button-radius-border: 20px; } ``` ### LWC Spread URL: https://blog.beyondthecloud.dev/tips/lwc-spread Tags: — Summary: Instead of passing multiple parameters to child component one by one, you can use lwc:spread directive. ### LWC spread directive lwc:spread directive is quite underrated, but very useful. Instead of passing multiple parameters to child component one by one, you can use lwc:spread directive. ```javascript import { LightningElement } from "lwc"; export default class Parent extends LightningElement { record = { name: "Thomas Anderson", recordId: "001" }; } ``` Decorate properties in child component with @api - as usual. ```javascript import { LightningElement, api } from "lwc"; export default class Child extends LightningElement { @api name; @api recordId; } ``` Pass properties from parent to child component: ```markup ``` ```markup ``` You can also pass standard HTML attributes and functions with callbacks: ```javascript import { LightningElement, track } from "lwc"; export default class Parent extends LightningElement { @track properties = { name: "Thomas Anderson", recordId: "001", id: "elementId", className: "elementClassName", onclick: this.childClick.bind(this) }; childClick() { this.properties.name = "Neo"; } } ``` Remember! lwc:spread can be added to component only once, so choose wisely! ### Refresh data from @wire URL: https://blog.beyondthecloud.dev/tips/refresh-data-from-wire Tags: apex, javascript Summary: Do you like fetching data for your component by @wire adapter? Are you struggling with cache and refreshing that data? Check this tip and enjoy @wire! Refresh data from @wire Do you like fetching data for your component by @wire adapter? Are you struggling with cache and refreshing that data? Check this tip and enjoy @wire! ```javascript import { LightningElement, wire } from 'lwc'; import getContacts from '@salesforce/apex/ContactController.getContacts'; export default class Contacts extends LightningElement { @wire(getContacts) contacts; } ``` There’s built-in refreshApex function which clears cache, but you have to know how to use it. Here are some working examples: ```javascript import { LightningElement, wire } from 'lwc'; import { refreshApex } from '@salesforce/apex'; import getContacts from '@salesforce/apex/ContactController.getContacts'; export default class Contacts extends LightningElement { @wire(getContacts) contacts; refreshRecords() { refreshApex(this.contacts); } } ``` You can use function notation in @wire, but you have to remember to store original response: ```javascript import { LightningElement, wire } from 'lwc'; import { refreshApex } from '@salesforce/apex'; import getContacts from '@salesforce/apex/ContactController.getContacts'; export default class Contacts extends LightningElement { _contacts; @wire(getContacts) contacts(value) { this._contacts = value; // rest of the logic } refreshRecords() { refreshApex(this._contacts); } } ``` All you have to do is to call refreshRecords function at the right moment. This example with destructuring assignment syntax won’t work: ```javascript import { LightningElement, wire } from 'lwc'; import { refreshApex } from '@salesforce/apex'; import getContacts from '@salesforce/apex/ContactController.getContacts'; export default class Contacts extends LightningElement { _contacts; @wire(getContacts) contacts({ data, error }) { this._contacts = data; // rest of the logic } refreshRecords() { refreshApex(this._contacts); } } ``` ### RefreshView API URL: https://blog.beyondthecloud.dev/tips/refreshview-api Tags: apex, security, admin Summary: From the Spring’23 release, we can use RefreshView API in LWC components, to refresh data in standard and custom components. ### Refresh LWC components using RefreshView API From the Spring’23 release, we can use RefreshView API in LWC components, to refresh data in standard and custom components. Are you working on Detail or Related List components in a Lightning Record Page? Or perhaps you're developing a custom component? The RefreshView API can now be your go-to tool to ensure your data remains up-to-date. For example, after creating child record in Apex, you can dispatch RefreshEvent and the related list will refresh and show newly created record without page reload! If you want to refresh your custom component, register refresh handler, that will be invoked any time RefreshEvent will be dispatched and reach the component. This way we can achieve refresh capabilities in consistent way of how Salesforce refresh standard components. ```javascript import { LightningElement } from "lwc"; import { registerRefreshHandler, unregisterRefreshHandler } from "lightning/refresh"; export default class RefreshHandler extends LightningElement { refreshHandlerId; connectedCallback() { this.refreshHandlerId = registerRefreshHandler(this, this.refreshHandler); } disconnectedCallback() { unregisterRefreshHandler(this.refreshHandlerId); } refreshHandler() { } } ``` This example works for orgs with Lightning Web Security enabled. For Lightning Locker follow the documentation. On the other hand, if your main concern is to update data in standard components on the Lightning Page, or in any other standard component (e.g. lightning-record-form), you can just dispatch the RefreshEvent. ```javascript import { LightningElement } from "lwc"; import { RefreshEvent } from "lightning/refresh"; export default class RefreshButton extends LightningElement { beginRefresh() { this.dispatchEvent(new RefreshEvent()); } } ``` How typical refresh works? 1. RefreshEvent is dispatched on other event or button click 2. The nearest level container component, which is registered with the RefreshView API, receives the RefreshEvent, stopping its propagation. 3. The components’ refresh handlers initiate the refresh process on the appropriate components. They can display spinners, perform instrumentation, and do other things to prepare the UI to be refreshed. 4. Descendant components of the handler participate in the refresh process through exposed API hooks. They can fetch data from a Salesforce org or perform other tasks to synchronize displayed data with the external data source. 5. The refresh for the component hierarchy completes when all data is synchronized and updated onscreen. Considerations Custom component must initiate the actual data refresh. For example, call refreshApex() to refresh Apex data provisioned via the wire service. Or call refreshGraphQL() to update the data provisioned by the GraphQL wire adapter. If the record is updated via a server action such as an Apex call, call notifyRecordUpdateAvailable to update the @wire. These calls are done outside the context of RefreshView API. RefreshView API can work in orgs that have enabled Lightning Web Security or Lightning Locker. The protocol for registering containers and handlers is different for each security architecture. The base Lightning Aura components currently don’t support RefreshView API but force:refreshView can be used there. ### SVG in LWC URL: https://blog.beyondthecloud.dev/tips/svg-in-lwc Tags: css Summary: You have received an SVG icon from a UX/UI designer and need to use it in an LWC. ### SVG IN LWC How to add SVG Icon to LWC? You have received an SVG icon from a UX/UI designer and need to use it in an LWC. You have a few options: Add it directly to the HTML, which can make your code a bit messy since SVG code is usually quite long. Add the icon as a static resource, which can make it even more challenging. Alternatively, you can convert the SVG icon to a CSS background. How to do it? 1. Go to https://www.svgbackgrounds.com/tools/svg-to-css/ or just google “svg to css”. 2. Insert your SVG icon. 3. Copy the generated CSS’s code. 4. Add the code to your LWC’s CSS as a background-image. ```markup ``` ![SVG in LWC — screenshot 2](https://blog.beyondthecloud.dev/tips/svg-in-lwc/fig-2.webp) ```css .salesforce-logo { width: 273px; height: 191px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:cc='http://creativecommons.org/ns%23' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns%23' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 273 191'%3E%3Ctitle%3ESalesforce.com logo%3C/title%3E%3Cde....."); } ``` ### Toast Module URL: https://blog.beyondthecloud.dev/tips/toast-module Tags: — Summary: Do you know that there is another way to show toast besides using platformShowToastEvent module? You can use a new toast module. ### Enhanced way to show toasts Do you know that there is another way to show toast besides using platformShowToastEvent module? You can use a new toast module. Here is a code sample ```javascript import Toast from 'lightning/toast'; export default class ToastDemo extends LightningElement { showError() { Toast.show( { label: 'Error Response - {httpErrorDocs}', labelLinks: { httpErrorDocs: { url: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404', label: 'HTTP 404' } }, message: 'Received error when trying to update order data. Check the details {errorDetails}.', messageLinks: { errorDetails: { url: 'https://someurl.example.com', label: 'here' } }, mode: 'dismissable', onclose: this.handleErrorToastClosed, variant: 'error' }, this ); } handleErrorToastClosed() { // Do whatever you wish } } ``` What are the benefits? Custom links support for the toast title (not only the content). Ability to specify links both in the index-based ({0}, {1} ... {n}) and name-based form. On close action support. Cleaner invocation? ### Use null coalescing operator URL: https://blog.beyondthecloud.dev/tips/use-null-coalescing-operator Tags: apex Summary: You can even chain! The code skips the left side until it is null/undefined. ### Use Null Coalescing (??) Null Coalescing Operator (??) covers cases where the left-hand side is: null (LWC ; Apex) undefined (LWC) ```javascript function getUserLanguage() { return currentUser.preferredLanguage ?? 'en_US'; } ``` Why is it useful? Replace IF-ELSE structures with one line of code. ```javascript function getUserLanguage() { if (currentUser.preferredLanguage) { return currentUser.preferredLanguage; } return 'en_US'; } ``` ```javascript function getUserLanguage() { return currentUser.preferredLanguage ?? 'en_US'; } ``` You can even chain! The code skips the left side until it is null/undefined. ```javascript function getUserLanguage() { return currentUser.preferredLanguage ?? organization.language ?? 'en_US'; } ``` Apex The ?? operator returns the left-hand argument if the left-hand argument isn’t null. Otherwise, it returns the right-hand argument. Similar to the safe navigation operator (?.), the null coalescing operator (??) replaces verbose and explicit checks for null references in code. ```apex public static String getUserLanguage() { if (currentUser.preferredLanguage == null) { return 'en_US'; } return currentUser.preferredLanguage; } ``` ```apex public static String getUserLanguage() { return currentUser.preferredLanguage ?? 'en_US'; } ``` Lightning Web Components The nullish coalescing (??) operator is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand ```javascript function getUserLanguage() { if (currentUser.preferredLanguage) { return currentUser.preferredLanguage; } return 'en_US'; } ``` ```javascript function getUserLanguage() { return currentUser.preferredLanguage ?? 'en_US'; } ``` ### Use OR instead of Ternary Operator URL: https://blog.beyondthecloud.dev/tips/use-or-instead-of-ternary-operator Tags: — Summary: You probably used the logical JS operator OR (||) in your Lightning Web Components. The most common use cases involve conditional statements. ### Use OR (||) instead of IFs and Ternary Operators You probably used the logical JS operator OR (||) in your Lightning Web Components. The most common use cases involve conditional statements. ```javascript const a = 3; const b = -2; if (a > 0 || b > 0) { // ... } ``` But did you know that || is a great operator to replace IF and Ternary Operator statements? As we can read in the documentation: [OR] It is typically used with boolean (logical) values. When it is, it returns a Boolean value. However, the || operator actually returns the value of one of the specified operands, so if this operator is used with non- Boolean values, it will return a non-Boolean value. ```javascript import { LightningElement, api } from 'lwc'; export default class MyComponentName extends LightningElement { @api error; get errorMessage() { return this.error || 'Unexpected Error. Please contact your administrator!' } } ``` OR (||) operator will return the first value that is NOT null, NaN, 0, empty (‘’), or undefined. Check out more examples... ```javascript get language() { if (user.language) { return user.language; } return 'en-US'; } ``` ```javascript get language() { return user.language ? user.language : 'en-US'; } ``` ```javascript get language() { return user.language || 'en-US'; } ``` ```javascript handleChange(event) { this.tabset = event?.target?.dataset?.tabset || DEFAULT_TABSET; } ``` ```javascript this.showToast({ title: 'Unexpected error', message: error?.message || error?.body?.message || labels.contactAdministrator, variant: 'error' }); ``` ## DevOps & CLI ### ANT Migration Tool End of Life URL: https://blog.beyondthecloud.dev/tips/ant-migration-tool-end-of-life Tags: sfdx, git, best-practices Summary: The Ant Migration Tool's last release was Winter '24 (v59.0). It still runs, but it gets no new functionality and no support — Salesforce CLI is the officially maintained client for the Metadata API. ### ANT Migration Tool The last released version of the tool is Winter '24 (v59.0). The tool continues to function for future API versions but isn't updated with new functionality and isn't supported. If you continue to use it, you do run the risk of not being able to access any new functionality that was added to the API or any new metadata types released after API v59.0. Salesforce CLI is the officially recommended and maintained Salesforce client to access Metadata API services. ### SF CLI advantages You can use MDAPI or Source format - up to you. You do not need to use Packages or Scratch Orgs, it is optional. Whatever CI/CD you have built with ANT you can achieve the same with SF CLI and some scripts like Bash, Python or PowerShell. SF CLI is supported and updated weekly. It is a free and open-source solution where the community has built multiple plugins that can fully cover simple CI/CD. Working with SF CLI and plugins in an IDE like Visual Studio Code is simply better and easier for developers than working with ANT. ### Your next steps Get to know Salesforce CLI in a test environment like a scratch org, developer org or Trailhead org. Evaluate what it will take for your project to migrate from ANT to SF CLI. If migration to SF CLI goes wrong, you should be able to use ANT still until all fixes are applied. Make sure all developers are familiar with SF CLI. If you are still using the MDAPI format it is a great opportunity to migrate into (DX) source format. SF CLI is way better documented, so be sure to read the docs first before migration to understand all the new possibilities it gives you. ### Contribute to open source URL: https://blog.beyondthecloud.dev/tips/contribute-to-open-source Tags: git, best-practices Summary: The contribution loop on GitHub, end to end: identify the repository, fork it, branch, commit, push to your fork, and open a pull request against the upstream project. Here is a quick tip showing how to contribute to an open-source project. Recently, we wanted to change how one of the features of an open-source plugin (sfdx-hardis) works. For this example, we will use a general GitHub repository as the target project. See what steps have to be performed to include your changes. ### 1. Identify the repository Locate the repository of the project you want to contribute to, typically on GitHub. Read the repository's README and CONTRIBUTING.md files for guidelines on how to contribute. ### 2. Fork the repository Create a personal copy of the repository by clicking the "Fork" button on the repository page, then clone the forked repository to your local machine. ```bash # Clone your forked repository git clone https://github.com/your-username/project-name.git cd project-name ``` ### 3. Create a branch Create a new branch for your changes to keep your work separate from the main branch. ```bash git checkout -b feature/your-feature-name ``` ### 4. Make, commit and push your changes Implement the changes required for your feature or fix. Use the tools and development environment recommended in the project documentation. ```bash # Make your changes and test them if possible git add . git commit -m "Description of the changes you made" # Push changes to your fork git push origin feature/your-feature-name ``` ### 5. Create a pull request Navigate to the original repository and create a pull request (PR) from your branch — yes, it will be visible there. ### 6. Respond to feedback Maintain communication with the project maintainers. Address any feedback or requested changes to your PR promptly. ### 7. Finalize the contribution Once approved, your changes will be merged into the main branch. Celebrate your contribution to the open-source community! ### Deploy before Merge URL: https://blog.beyondthecloud.dev/tips/deploy-before-merge Tags: git, best-practices Summary: "Deploy before merge" is a CI/CD technique: the pipeline deploys — and optionally runs tests — while the pull request is still open, and only merges if that deployment succeeds, so a broken main branch never blocks the team. When designing the pipeline that delivers your changes through sandbox environments up to production, it's good practice to expect that people using it will sometimes fail. Failure is normal - we're all human - we just need a process that's prepared for it. That's why we often use what's called "Deploy before Merge." It's a CI/CD technique when working with Pull/Merge Requests: before you click the "Merge" button, you run a pipeline that deploys the code (and maybe runs unit tests) and only if that deployment succeeds will it automatically merge the code. ### Why it is worth it When deploying before merging the code, you keep your branch in good shape - maybe even deployable all the time. If something goes wrong and even peer code review didn't catch it, validation passed, but deployment fails - you don't have to panic, do git reverts, or make fast changes just to unblock everyone else. This may not be so harmful in small projects, but when working with several teams of dozens of developers and you just merged something that's blocking everyone else, the goosebumps and heat feeling just maybe isn't worth it. Adrenaline is fine, but maybe look for some other source than playing with git. ### "But why should I care when validation will catch everything?" Well, not really. Validation catches most stuff, but there are changes validation can't predict - for example: background jobs that appeared; manual changes in the background when the package was on quick deploy; Apex tests using real org data; post-destructive changes; and Master-Detail to/from Lookup field type changes. And so on - every day we discover new "just Salesforce things." So prepare your processes for failure and keep up the good work! ### Deploy to production URL: https://blog.beyondthecloud.dev/tips/deploy-to-production Tags: apex, testing, sfdx Summary: By default when deploying items to Production you would run local tests (all tests except for Unlocked/Managed Packages) ### Deploy to Salesforce Production without Tests By default when deploying items to Production you would run local tests (all tests except for Unlocked/Managed Packages) You cannot use the NoTestRun flag as it will result in an error “testLevel of NoTestRun cannot be used in production organizations” Use any of the below commands to deploy to Prod without running Unit Tests! This will work only if you do not have Apex Classes/Triggers to be deployed ```bash sfdx force:source:deploy [...] --testlevel RunSpecifiedTests --runtests "someRandomString" sf project deploy start [...] --test-level RunSpecifiedTests --tests "someRandomString" ``` ### Handy Git commands URL: https://blog.beyondthecloud.dev/tips/handy-git-commands Tags: git, best-practices Summary: Three commands for the everyday awkward moments: git stash to switch branches with dirty files, git checkout to pull one file from another branch, and git reset --hard to throw a local branch away. Git is a must-have for Salesforce developers nowadays. Here are 3 simple but useful commands which help in navigating through common scenarios: switching contexts, reverting changes, and managing local modifications efficiently. ### Save local changes and change branch If you are working on something, but suddenly you need to switch to some other branch, the struggle is real when you don't know the below command. ```bash git stash git checkout bugfix/urgent-fix # do some work # ... # here you are done and want to switch back git checkout feature/my-first-feature git stash pop ``` "git stash" will temporarily save tracked files without committing them as they are still work-in-progress. After some time you can re-apply stashed changes. If you are working on new files be sure to stage them first or use "git stash -u" as it will also support untracked files. ### Copy a file from a different branch Fetch a specific file or folder from another branch into your current working branch. Use it when you need to incorporate changes from file(s) developed in a different branch without merging the entire branch into your current one. ```bash # git checkout -- path/to/your/folder git checkout bugfix/login-fix -- path/to/login/bugfix ``` ### Reset all local changes Sometimes you end up with a completely devastated local branch and you only want to reset its status to a remote version. Fortunately, some git commands will help you with that. With the below commands, you will discard local changes in your working directory and reset your branch's state to a remote version of the specified branch (usually the same branch, but on a remote server). ```bash # git reset --hard origin/your-branch git reset --hard origin/master git clean -df ``` ### Manage Dev Sandboxes URL: https://blog.beyondthecloud.dev/tips/manage-dev-sandboxes Tags: security, admin Summary: From the Summer’24 release, we can assign new Manage Dev Sandboxes user permission. ### Manage Dev Sandboxes Permission From the Summer’24 release, we can assign new Manage Dev Sandboxes user permission. This permission allows to create, clone, refresh, and delete only Developer/Pro sandboxes. This way Partial Copy and Full Copy sandboxes can be still maintained only by specific group of users, but in the same time Developers can maintain their sandboxes without any additional interaction. ![Manage Dev Sandboxes — screenshot 1](https://blog.beyondthecloud.dev/tips/manage-dev-sandboxes/fig-1.webp) This user permission can be assigned on Production Orgs via Permission Set or Profile. ![Manage Dev Sandboxes — screenshot 2](https://blog.beyondthecloud.dev/tips/manage-dev-sandboxes/fig-2.webp) Manage Sandboxes user permission is still giving the same access, i.e. access to manage all sandboxes. ![Manage Dev Sandboxes — screenshot 3](https://blog.beyondthecloud.dev/tips/manage-dev-sandboxes/fig-3.webp) Even though this permission looks like a way to give similar development freedom as in creating scratch orgs by Developers with Salesforce Limited Access - Free license, the new Manage Dev Sandboxes user permission cannot be assigned to this free license, it requires full license. Bummer. ### Mass Delete Scratch Org URL: https://blog.beyondthecloud.dev/tips/mass-delete-scratch-org Tags: sfdx, admin Summary: Scratch orgs are ActiveScratchOrg records in the Dev Hub, so you can query them all in the Developer Console and use "Delete Row" to remove them in one pass instead of one at a time. How to delete multiple scratch orgs at once? To delete a single scratch org, use the App Launcher and search for "Active Scratch Orgs". But how do you delete all of them in one click? ### Delete them from the Developer Console Open the Developer Console. Use the Query Editor to view records from the ActiveScratchOrg object. ```sql SELECT Owner.Name, CreatedDate, Id, Name, SignupUsername, SignupInstance, ScratchOrgInfo.LoginUrl, OrgName FROM ActiveScratchOrg ORDER BY CreatedDate ASC ``` Select which rows you would like to delete and click "Delete Row". Deletion is an asynchronous process, it should take less than 30 seconds. Click "Refresh Grid" to see the current status. ### Monitor your API Limits URL: https://blog.beyondthecloud.dev/tips/monitor-your-api-limits Tags: sfdx, performance, admin Summary: One Salesforce CLI command prints over 50 org limits — daily API requests, async Apex executions, bulk API batches, storage — with the maximum and remaining allocation for each. ```bash sf org list limits --json ``` Simple, yet powerful command for displaying limits in your org. It returns over 50 different limits like: Daily Api Requests, Daily Async Apex Executions, Daily Bulk Api Batches, Daily Scratch Orgs, Single Email, Package 2 Version Creates, File Storage MB and Data Storage MB. ### This is how sample output looks like ```json { "status": 0, "result": [ { "name": "ActiveOrgSnapshots", "max": 80, "remaining": 79 }, { "name": "ActiveScratchOrgs", "max": 80, "remaining": 36 }, { "name": "AnalyticsExternalDataSizeMB", "max": 40960, "remaining": 40960 } ], "warnings": [] } ``` ### What can you do with it? Because the output is JSON, it is easy to script. A quick win is a scheduled job that formats the result as a usage report and posts it to a team channel: Resource Usage Report — ActiveOrgSnapshots 79 / 80 (1%), ActiveScratchOrgs 36 / 80 (55%), AnalyticsExternalDataSizeMB 40960 / 40960 (0%), and so on for every limit the org reports. Run it against production regularly — hitting the daily API request limit is one of the more disruptive ways to find out you were close to it. ### Retrieve All Salesforce Metadata URL: https://blog.beyondthecloud.dev/tips/retrieve-all-salesforce-metadata Tags: sfdx, admin Summary: Two Salesforce CLI commands pull an entire org's metadata: generate a manifest from the org, including managed and unlocked packages, then retrieve against that manifest. You can retrieve all Salesforce metadata using just 2 Salesforce CLI commands. ```bash sf project generate manifest \ --from-org {ORG-ALIAS} \ --name package.xml \ --output-dir ./manifest \ --include-packages managed,unlocked sf project retrieve start \ --manifest ./manifest/package.xml \ --target-org {ORG-ALIAS} \ --wait 120 ``` If your retrieve is failing due to hitting limits, split your package.xml into smaller ones that do not exceed 10,000 components per file. ### Run Apex code in command line URL: https://blog.beyondthecloud.dev/tips/run-apex-code-in-command-line Tags: apex, sfdx Summary: Did you know that you don’t have to open developer console to run apex scripts via Anonymous Apex? You don’t even have to open any IDE or create a text file, simply put your code in… ### Run Apex Anonymous code in command line Did you know that you don’t have to open developer console to run apex scripts via Anonymous Apex? You don’t even have to open any IDE or create a text file, simply put your code in command line interface. All you have to do is to use the sf command: ```bash sf apex run ``` This will start interactive shell. You can use enter to go to next line, when you are done with writing your code, use Ctrl+D to execute code ### Scratch Org Snapshots URL: https://blog.beyondthecloud.dev/tips/scratch-org-snapshots Tags: sfdx Summary: Did you know you can create scratch orgs based on already existing scratch org? This is possible thanks to the Scratch Org Snapshots feature. ### Scratch Org Snapshots Did you know you can create scratch orgs based on already existing scratch org? This is possible thanks to the Scratch Org Snapshots feature. This is very useful when: 1. You have some steps that need to be performed on the scratch org every time you create it - e.g. managed package installation, test data population, manual config in the setup. 2. You are in the middle of the development and your scratch org is about to expire - just create a snapshot from it and your work will not be lost. Let’s check how to create snapshots Complete the prerequisites: Enable Scratch Org Snapshots on your DevHub org (Setup -> Scratch Orgs -> Enable Scratch Org Snapshots) Make sure your user has CRUD object permissions on the Org Snapshot object Create an unnamespaced source scratch org and apply all the changes and configuration you wish to be included in the snapshot Create the scratch org snapshot ```bash sf org create snapshot --name snapshot --source-org your-snapshot-source-scratch ``` Check the creation status ```bash sf org get snapshot --snapshot snapshot ``` Add snapshot reference to the scratch org config file ```json { "orgName": "BTC Snapshot Demo", "snapshot": "snapshot" } ``` Create a new scratch org based on the snapshot ```bash sf org create scratch -a your-great-scratch -f .\config\project-scratch-def.json --wait 30 ``` Limitations to keep in mind 1. Snapshots expire after 90 days 2. You can only create snapshots based on scratch orgs without a namespace (but you can create a namespaced scratch org from the snapshot!) 3. You can create up to 5 snapshots daily per Devhub org 4. You can have up to 5 active snapshots per Devhub org ### Share access to Salesforce Environment URL: https://blog.beyondthecloud.dev/tips/share-access-to-salesforce-environment Tags: sfdx, security, admin Summary: sf org open --url-only prints a login URL backed by your access token, so a teammate can get into the environment for troubleshooting without you handing over credentials. ```bash sf org open --url-only ``` Easily share access to your Salesforce environment using the above command. This generates a login URL using an access token, allowing others to access the environment without sharing credentials directly. It's a great tool for collaborative work and troubleshooting. Treat the generated URL as a credential — anyone holding it is logged in as you until the session expires. Share it over a private channel, and only for sandboxes and scratch orgs. ## Admin & Setup ### Build Dynamic Report for Record Page URL: https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page Tags: admin Summary: This example contains sum of amount for all opportunities related to the account. Check next pages to see how to do that in 6 simple steps! ### Build Dynamic Reports on Record Pages You can easily add dynamic reports like this one to your record pages: ![Build Dynamic Report for Record Page — screenshot 1](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-1.webp) This example contains sum of amount for all opportunities related to the account. Check next pages to see how to do that in 6 simple steps! 1. Create your report. (Mote that no filter on Account Id is added at this stage). Add grouping by the stage so that we can create a chart ![Build Dynamic Report for Record Page — screenshot 2](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-2.webp) ![Build Dynamic Report for Record Page — screenshot 3](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-3.webp) Check next page to see how to create a chart and add it to record page ![Build Dynamic Report for Record Page — screenshot 4](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-4.webp) 2. Click on and select the chart type that you prefer. Set the values for X and Y axis as shown below ![Build Dynamic Report for Record Page — screenshot 5](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-5.webp) 3. Save your report. It is important to save it in public folder! 4. Last step is to add report to the record page. Go Lightning App Builder to edit your page, and select the standard component: ![Build Dynamic Report for Record Page — screenshot 6](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-6.webp) 5. In component properties, find your report. Set Filter By to Account Id. It will dynamically get the current page Account Id. ![Build Dynamic Report for Record Page — screenshot 7](https://blog.beyondthecloud.dev/tips/build-dynamic-report-for-record-page/fig-7.webp) 6. Save changes and refresh the page. Voila! Your shiny new report is added to the page! ### Easily navigate to any record URL: https://blog.beyondthecloud.dev/tips/easily-navigate-to-any-record Tags: admin Summary: Simply paste the record ID after your domain, and you will be redirected to your record page ### Easily navigate to any record You don’t need the full URL of a record page to display it. All you need is the record ID: ```bash https://beyondtheclouddev-dev-ed.lightning.force.com/0017Q00000MhvxcQAB ``` Simply paste the record ID after your domain, and you will be redirected to your record page ```bash https://beyondtheclouddev-dev-ed.lightning.force.com/lightning/r/Account/0017Q00000MhvxcQAB/view ``` This works for all standard and custom objects! ### Get notified when SF is down URL: https://blog.beyondthecloud.dev/tips/get-notified-when-sf-is-down Tags: admin, best-practices Summary: status.salesforce.com lets you subscribe to a specific instance and pushes an email or SMS the moment a service disruption, performance degradation or maintenance window is posted for it. This is an example message from status.salesforce.com when there is an ongoing Service Disruption. "Service Disruption Impacting Multiple Services in APAC. Issue: On December 16, 2023 at 14:12 UTC, the Salesforce Technology team became aware of an issue impacting multiple services. Impact: A network issue is affecting customers across multiple services. During this time customers won't be able to access their services." ### How to subscribe If you are interested in these types of messages, follow the simple steps below to get an ASAP notification. Go to status.salesforce.com. Click the LOG IN button in the top right corner. Enter your email address to log in. You will get an email with the login URL. Once logged in you can optionally enter your phone number. Select the Instance you are interested in. Your instance name is visible in Setup under Company Information. Select the Services of your interest and click Submit. You can subscribe to Service Disruption, Performance Degradation and Maintenance separately, over email or SMS. From then on, every incident and status update for that instance reaches you without anyone having to check the site. ### List View Intelligence URL: https://blog.beyondthecloud.dev/tips/list-view-intelligence Tags: admin Summary: In Winter 24 Release, Salesforce released Intelligence View for leads and contacts List Views. This view brings in some activity metrics related to the displayed records activities, e.g… ### List View Intelligence In Winter 24 Release, Salesforce released Intelligence View for leads and contacts List Views. This view brings in some activity metrics related to the displayed records activities, e.g. number of records with activities due today ![List View Intelligence — screenshot 1](https://blog.beyondthecloud.dev/tips/list-view-intelligence/fig-1.webp) Check the next slide to see how to enable and open Intelligence View Go to Setup and find “Lead Intelligence View Setup” . Turn on Lead Intelligence View, and also add the intelligence view button: ![List View Intelligence — screenshot 2](https://blog.beyondthecloud.dev/tips/list-view-intelligence/fig-2.webp) Now you can switch between standard list view and Intelligence View by using an action button: ![List View Intelligence — screenshot 3](https://blog.beyondthecloud.dev/tips/list-view-intelligence/fig-3.webp) The same steps could be followed to enable Intelligence View for contacts. ### Login as an API-only user URL: https://blog.beyondthecloud.dev/tips/login-as-an-api-only-user Tags: sfdx Summary: 1. Open Visual Studio Code 2. Press Ctrl + Shift + P > Select SFDX: Authorize an Org 3. Select the appropriate login URL and Alias, when a browser opens a new tab, then enter credentials… ### LOGIN AS API USER How can you normally use Salesforce but with Salesforce API-only user? 1. Open Visual Studio Code 2. Press Ctrl + Shift + P > Select SFDX: Authorize an Org 3. Select the appropriate login URL and Alias, when a browser opens a new tab, then enter credentials for API Only User 4. Once completed click on the “Open Org” button or type one of the below commands ![Login as an API-only user — screenshot 1](https://blog.beyondthecloud.dev/tips/login-as-an-api-only-user/fig-1.webp) ```bash sfdx force:org:open sf org open ``` 5. Voilà la - you are logged in to Salesforce UI as an API-only user ### Set password in Salesforce URL: https://blog.beyondthecloud.dev/tips/set-password-in-salesforce Tags: apex, sfdx Summary: Well, not really, this is about setting up a password via Apex and SF CLI. ### Set password in Salesforce Really? You are now creating a tip on how to reset a password? What's next? How to log in to Salesforce? Well, not really, this is about setting up a password via Apex and SF CLI. You might want to do this when you are already authenticated (i.e., via OAuth) but have forgotten the password, and resetting the password via UI in the classic way is too simple for such a pro, or you just have issues receiving the email with the one-time reset password link. Set password via Apex script If you are authenticated in IDE, use your terminal to get your user ID. ![Set password in Salesforce — screenshot 1](https://blog.beyondthecloud.dev/tips/set-password-in-salesforce/fig-1.webp) Then you have to execute anonymous Apex code entered on the command line or from a local file. The apex script will be: ```apex System.setPassword(USERID, PASSWORD); ``` ![Set password in Salesforce — screenshot 2](https://blog.beyondthecloud.dev/tips/set-password-in-salesforce/fig-2.webp) That's how you can set a password, but there is one more way. The documentation says it is just for scratch orgs, but as of now, it also works for normal orgs. Generate password via SF CLI ```bash sf org generate password ``` There are additional parameters to generate strong passwords, such as: --length X (default 13; valid values between 8 and 100) --complexity X (default 5; valid values between 0 and 5) ### Show all records URL: https://blog.beyondthecloud.dev/tips/show-all-records Tags: admin Summary: Setup list pages keep their page size in the URL. Click "more" once, then change rowsperpage from 35 to a large number and reload — the whole list renders on a single page. How many times were you frustrated by clicking "Show me more records" multiple times until you saw the full list or found the right record? There is a simple trick to show all records on the page. ### A little URL "hacking" When you open a new page (for example Apex Classes) you see a URL similar to the one below: ```bash https://beyondtheclouddev.lightning.force.com/lightning/setup/ApexClasses/home ``` Scroll down and click on "more" one time. The URL will change to something much longer, now carrying the page state. If you are doing it for the first time, the best would be to use a text editor. Copy the whole URL and paste it into the text editor. Search for "rows". Change the number next to "rowsperpage" from 35 to 350000. ```bash # before ...%3Arowsperpage%3D35%26retURL%3D%252Fsetup%252Fhome # after ...%3Arowsperpage%3D350000%26retURL%3D%252Fsetup%252Fhome ``` Replace the old URL with the modified one and see all the records with this simple trick! This works with Salesforce Classic as well — it is even easier there, since the parameter is not URL-encoded. ```bash ...setupid=ApexClasses&all_classes_page%3AtheTemplate%3AclassList%3Arowsperpage=35000 ``` ### User Access Policies URL: https://blog.beyondthecloud.dev/tips/user-access-policies Tags: apex, security, admin Summary: From the Summer’24 release, we can use User Access Policies to automate assignments (and removal of assignments) to permission set licenses, permission sets, permission set groups… Automate Access Management using User Access Policies From the Summer’24 release, we can use User Access Policies to automate assignments (and removal of assignments) to permission set licenses, permission sets, permission set groups, package licenses, queues, and public groups. To enable this feature, go to Setup > User Management Settings, and toggle User Access Policies. ![User Access Policies — screenshot 1](https://blog.beyondthecloud.dev/tips/user-access-policies/fig-1.webp) By default, it will also enable an enhanced interface for user access policies, giving you a better user interface to manage the policies. ![User Access Policies — screenshot 2](https://blog.beyondthecloud.dev/tips/user-access-policies/fig-2.webp) Enahnced User Interface (based on Lightning look and feel). ![User Access Policies — screenshot 3](https://blog.beyondthecloud.dev/tips/user-access-policies/fig-3.webp) Standard User Interface (based on Visualforce Page look and feel). You can define the User criteria by which users will be matched for the rule. These criteria can be matched by: Public Group, Queue, Permission Set, Permission Set Group, Package License, Profile and Role. Additionally to that, you need to filter by user-specific information like User Type, Email, Active status, and more. ![User Access Policies — screenshot 4](https://blog.beyondthecloud.dev/tips/user-access-policies/fig-4.webp) Created User Access Policy can be triggered manually, or automated to run every time user is created, updated or on both events. ![User Access Policies — screenshot 5](https://blog.beyondthecloud.dev/tips/user-access-policies/fig-5.webp) ![User Access Policies — screenshot 6](https://blog.beyondthecloud.dev/tips/user-access-policies/fig-6.webp) Considerations Automated policies cannot be deployed before deactivation on the target Salesforce org, which means manual intervention is required during deployments. An action performed by a user access policy can’t trigger another user access policy. You can have up to 200 active user access policies at a time. Multiple policies have Order field which determine the order in which they will run. An active policy is applied to existing users only when their records are updated to match the policy’s criteria. ## SOQL ### Get list of one column SOQL URL: https://blog.beyondthecloud.dev/tips/get-list-of-one-column-soql Tags: apex Summary: The simplest way to retrieve record IDs without a for loop is by using a Map and the keySet() method. ### Get field values in SOQL How to Obtain Unique Field Values Without Using a For Loop? The simplest way to retrieve record IDs without a for loop is by using a Map and the keySet() method. ```apex Set accountIds = new Map( [SELECT Id FROM Account] ).keySet(); ``` But how can you obtain values for other fields? You can accomplish this using field aliasing. As shown on previous slide, a Map will automatically use the Id as a Map key. ```apex Set industries = new Set(); for (Account acc : [SELECT Industry FROM Account]) { industries.add(acc.Industry); } ``` ```apex Set industries = new Map([ SELECT Industry Id FROM Account WHERE Industry != NULL GROUP BY Industry ]).keySet(); ``` To avoid Row with null Id at index: 0 always add null check (!= NULL) ### SOQL All Rows URL: https://blog.beyondthecloud.dev/tips/soql-all-rows Tags: apex, debugging Summary: You can use the SOQL ALL_ROWS clause for that. The resulting query includes the soft-deleted records (the ones in the recycle bin) with the results. ### SOQL ALL_ROWS Clause Do you need to query deleted records? You can use the SOQL ALL_ROWS clause for that. The resulting query includes the soft-deleted records (the ones in the recycle bin) with the results. Here’s an example that will help you understand and test this feature: ```apex Account activeAccount = new Account(Name = 'Active Acc'); Account accountToDelete = new Account(Name = 'To Delete'); List accounts = new List{ activeAccount, accountToDelete }; insert accounts; delete accountToDelete; Integer accountsNumber = [SELECT COUNT() FROM Account WHERE Id IN :accounts ALL ROWS]; Assert.areEqual(2, accountsNumber, 'Both accounts should be returned when using ALL ROWS clause.'); ``` It can be useful for use cases like auditing, accidental removal mitigation, integrity checks, or debugging. Note: you can’t use the ALL_ROWS clause via API (only Apex, my friend). ### SOQL for loops URL: https://blog.beyondthecloud.dev/tips/soql-for-loops Tags: — Summary: SOQL for loops retrieve all sObjects, using efficient chunking with calls to the query and queryMore methods of SOAP API. Developers can avoid the limit on heap size by using a SOQL for… ### SOQL For Loops As we can read in the documentation: SOQL for loops retrieve all sObjects, using efficient chunking with calls to the query and queryMore methods of SOAP API. Developers can avoid the limit on heap size by using a SOQL for loop to process query results that return multiple records. It’s worth mentioning that the approach can increase the total heap size. ```apex List accounts = [SELECT Id, Name FROM Account]; for (Account acc : accounts) { // ... } ``` ```apex for (Account acc : [SELECT Id, Name FROM Account]) { // ... } ``` ### SOQL for Polymorphic relationships URL: https://blog.beyondthecloud.dev/tips/soql-for-polymorphic-relationships Tags: — Summary: You can leverage TYPEOF keyword in your SOQL queries! Do you use polymorphic relationships? You can leverage TYPEOF keyword in your SOQL queries! ```sql SELECT Id, WhoId, TYPEOF Who WHEN Contact THEN Name, Account.Name WHEN Lead THEN Id, Company END FROM Task ``` Now instead: ```apex for(Lead lead: [SELECT Id, OwnerId, Owner.Name, Owner.Type, Owner.Email, Owner.Phone FROM Lead]) { if(lead.OwnerId.startsWith('005')){ ... } else { ... } } ``` Do the following: ```apex for(Lead lead: [ SELECT Id, OwnerId, TYPEOF Owner WHEN Group THEN Name, Type WHEN User THEN Name, Email, Phone END FROM Lead ]) { if(lead.Owner instanceof Group) { ... } else { ... } } ``` ### SOQL Semi-Join URL: https://blog.beyondthecloud.dev/tips/soql-semi-join Tags: — Summary: Consideration: Semi-joins do NOT count against the query limit. Semi-joins COUNT against the aggregated query limit. ### SOQL Semi-Join Have you ever tried to retrieve parent records based on a child's condition? Probably you did something like this: You can accomplish this more easily using a Semi-Join: ```apex List opportunities = [ SELECT Id, AccountId FROM Opportunity WHERE StageName = 'Closed Lost' ]; Set accountIds = new Set(); for (Opportunity opp : opportunities) { accountIds.add(opp.AccountId); } List accounts = [ SELECT Id, Name FROM Account WHERE Id IN :accountIds ]; ``` ```sql SELECT Id, Name FROM Account WHERE Id IN ( SELECT AccountId FROM Opportunity WHERE StageName = 'Closed Lost' ) ``` Consideration: Semi-joins do NOT count against the query limit. Semi-joins COUNT against the aggregated query limit. ### Use RecordType.DeveloperName instead of RecordTypeId URL: https://blog.beyondthecloud.dev/tips/use-recordtype-developername-instead-of-recordtypeid Tags: — Summary: Use RecordType.DeveloperName! Each object has a reference to its RecordType. You can utilize that reference to query records by RecordType. Use RecordType.DeveloperName instead of RecordTypeId Avoid using recordTypeId to query records of a specific RecordType. ```apex Id recordTypeId = [ SELECT Id FROM RecordType WHERE DeveloperName = 'Partner' ].Id; // OR Id recordTypeId = Account.SObjectType .getDescribe(SObjectDescribeOptions.DEFERRED) .getRecordTypeInfosByDeveloperName() .get('Partner') .getRecordTypeId(); List accounts = [ SELECT Id, Name FROM Account WHERE RecordTypeId = :recordTypeId ]; ``` Use RecordType.DeveloperName! Each object has a reference to its RecordType. You can utilize that reference to query records by RecordType. ![Use RecordType.DeveloperName instead of RecordTypeId — screenshot 2](https://blog.beyondthecloud.dev/tips/use-recordtype-developername-instead-of-recordtypeid/fig-2.webp) ```apex List accounts = [ SELECT Id, Name FROM Account WHERE RecordType.DeveloperName = 'Partner' ]; ``` ### Use the RecentlyViewed table URL: https://blog.beyondthecloud.dev/tips/use-the-recentlyviewed-table Tags: — Summary: By running this simple query, you can get records that user recently viewed: ### Did you know you can query recently viewed records? By running this simple query, you can get records that user recently viewed: ```sql SELECT Id, Name FROM RecentlyViewed WHERE Type IN ('Account', 'Contact', 'User') ORDER BY LastViewedDate DESC ``` Why it is useful? Using this simple query, you can build custom Recently Viewed lists: ![Use the RecentlyViewed table — screenshot 2](https://blog.beyondthecloud.dev/tips/use-the-recentlyviewed-table/fig-2.webp) Or use it as a search proposition in input components: ![Use the RecentlyViewed table — screenshot 3](https://blog.beyondthecloud.dev/tips/use-the-recentlyviewed-table/fig-3.webp) ## Tooling ### Document Design Mode URL: https://blog.beyondthecloud.dev/tips/document-design-mode Tags: — Summary: Do you know that there is a special HTML feature that allows you to modify the content of the web page without touching the underlying code? It is called document design mode ### Document design mode for demo and testing Do you know that there is a special HTML feature that allows you to modify the content of the web page without touching the underlying code? It is called document design mode How to enable it? Just type this command in the browser developer tools. ```javascript document.designMode = 'on'; ``` Let’s check an example Let’s edit the Salesforce Sales home page using the design mode. It looks like that before the adjustments: ![Document Design Mode — screenshot 2](https://blog.beyondthecloud.dev/tips/document-design-mode/fig-2.webp) After enabling the design mode, its content becomes editable and we can change every label on the page ![Document Design Mode — screenshot 3](https://blog.beyondthecloud.dev/tips/document-design-mode/fig-3.webp) When is it useful? Simple demo - you don’t have the perfect code yet or you haven’t prepared ideal demo data, but would like to send a screenshot to receive feedback from the customer Fast prototyping Quick UI tests - to check how your component would look like with certain content Making a prank ;) ### Generate code with Einstein URL: https://blog.beyondthecloud.dev/tips/generate-code-with-einstein Tags: apex, testing Summary: Fear no more, check how to use Einstein for Developers! ### Generate code with Einstein Are you feeling that you are missing all the fun while working with Apex code? ![Generate code with Einstein — screenshot 1](https://blog.beyondthecloud.dev/tips/generate-code-with-einstein/fig-1.webp) Fear no more, check how to use Einstein for Developers! First, enable Einstein for Developers in the setup. ![Generate code with Einstein — screenshot 2](https://blog.beyondthecloud.dev/tips/generate-code-with-einstein/fig-2.webp) And install VS Code extension ![Generate code with Einstein — screenshot 3](https://blog.beyondthecloud.dev/tips/generate-code-with-einstein/fig-3.webp) You can now start writing prompts, for example: ```apex @isTest public class HttpBuilderTest { @isTest static void testHttpBuilder() { Integrations__mdt metadata = new Integrations__mdt(); metadata.Credentials__c = 'test'; metadata.Endpoint__c = 'test'; metadata.Method__c = 'test'; metadata.Content_Type__c = 'test'; HttpBuilder builder = new HttpBuilder(metadata); System.assertEquals(metadata, builder.metadata); builder.buildHeader('test', 'test'); builder.buildParam('test', 'test'); builder.buildBody('test'); builder.buildTimeout(120000); HttpRequest request = builder.build(); System.assertEquals('test', request.getHeader('test')); System.assertEquals('test', request.getBody()); System.assertEquals('test', request.getEndpoint()); System.assertEquals('test', request.getMethod()); System.assertEquals(120000, request.getTimeout()); } } ``` ### github.dev web-based editor URL: https://blog.beyondthecloud.dev/tips/github-dev-web-based-editor Tags: git, best-practices Summary: Swap github.com for github.dev in any repository URL and a browser-based VS Code opens on that repo — search files, edit and commit, without cloning anything. Did you know you can open any GitHub repo within the browser as if you were in an IDE, in one click? Just change the URL from github.com to github.dev and a web-based editor will open. ```bash # from https://github.com/beyond-the-cloud-dev/soql-lib # to https://github.dev/beyond-the-cloud-dev/soql-lib ``` ### What you get Open and search through the files, as well as commit your changes. Features from Visual Studio Code, like syntax highlighting. A subset of web-optimized VS Code extensions. Please note that this is very lightweight and something different than GitHub Codespaces or Salesforce Code Builder. Check the documentation and limitations before relying on it. ### Metadata Coverage Page URL: https://blog.beyondthecloud.dev/tips/metadata-coverage-page Tags: — Summary: You can use Metadata Coverage, to find the exact API name of components you are trying to work with (link also in description): https://developer.salesforce.com/docs/metadata-coverage/60 ### Do you struggle to memorize all the metadata names? If so, you probably see a similar message: ```bash js@os ~/project (main)> sf project retrieve start --metadata AuraComponentBundle:SuperSecretComponent Preparing retrieve request... Error Error (1): The specified metadata type is unsupported: [AuraComponentBundle] ``` Don’t worry, there is a page that can help you! You can use Metadata Coverage, to find the exact API name of components you are trying to work with (link also in description): https://developer.salesforce.com/docs/metadata-coverage/60 ![Metadata Coverage Page — screenshot 1](https://blog.beyondthecloud.dev/tips/metadata-coverage-page/fig-1.webp) Now I know that the correct name is “AuraDefinitionBundle”: ```bash js@os ~/project (main) > sf project retrieve start --metadata AuraDefinitionBundle:SuperSecretComponent Retrieving v59.0 metadata from jan@beyondthecloud.sandbox using the v60.0 SOAP API Preparing retrieve request... Succeeded Retrieved Source ================================================================================================= | State Name Type Path |------------------------------------------------------------------------------------------------ | Changed SuperSecretComponent AuraDefinitionBundle force-app/main/default/aura/SuperSecretComponent/SuperSecretComponent.cmp | Changed SuperSecretComponent AuraDefinitionBundle force-app/main/default/aura/SuperSecretComponent/SuperSecretComponent.cmp-meta.xml | Changed SuperSecretComponent AuraDefinitionBundle force-app/main/default/aura/SuperSecretComponent/SuperSecretComponentController.js ``` ### npm script shortcuts URL: https://blog.beyondthecloud.dev/tips/npm-script-shortcuts Tags: apex, sfdx Summary: You can use the npm scripts to run bash scripts, and unleash the unlimited power of scripting. ### Use npm scripts for frequently used commands You don’t have to memorize all your commands and their parameters. If you frequently use certain commands, consider adding them to your package.json file as a script. For instance, if you want to use sfdx command to deploy the content of your force-app folder, you can do this simply: ```json "scripts" : { "deploy:all": "sf project deploy start --source-dir force-app -w" } ``` Now to execute your sfdx command you can type the following command in terminal: ```bash npm run deploy:all ``` Check the next page to see another examples You can use the npm scripts to run bash scripts, and unleash the unlimited power of scripting. ```json "scripts": { "hello": "bash hello.sh" } ``` Create a file ‘hello.sh’ with the script (in this case it should belong to the root folder of your project): ```bash #!/bin/bash echo "Hello World" ``` Now to execute your bash script you can type the following command in terminal: ```bash npm run hello ``` Another useful example is to run the Anonymous Apex. Let me show you how this could be used to enable debug mode without using UI. ```json "scripts": { "debug-mode": "sfdx apex:execute -f \"debugMode.apex\"" } ``` Create an apex script in file ‘debugMode.apex’ ```apex update new User(Id = UserInfo.getUserId(), UserPreferencesUserDebugModePref = true); ``` Now to execute your sfdx command you can type the following command in terminal: ```bash npm run debug-mode ``` Don’t forget, also to check what the npm scripts that are already existing in your package.json file. There are probably some useful commands already created there. ![npm script shortcuts — screenshot 5](https://blog.beyondthecloud.dev/tips/npm-script-shortcuts/fig-5.webp) I would also recommend checking a great article about npm scripts by Philippe Ozil (link in the comment) Do you have any favourite npm scripts that boost your productivity? Share them in the comments! Also, don’t forget to follow our page to get other examples and tips! ### Quickly delete Debug Logs URL: https://blog.beyondthecloud.dev/tips/quickly-delete-debug-logs Tags: apex, sfdx, debugging Summary: Have you ever encountered an issue with the debug log limit? You've probably seen the message at least once: '...Before you can edit trace flags, delete some debug logs.' ### Quickly delete Debug Logs Have you ever encountered an issue with the debug log limit? You've probably seen the message at least once: '...Before you can edit trace flags, delete some debug logs.' You can easily delete all logs with a small plugin: osiecki-sfdx- plugins (link to the repository in the comment) All you need to do is install the plugin using the command: ```bash sfdx plugins:install osiecki-sfdx-plugins ``` Now, you can use the command ```bash sfdx oa:apex:log:delete -a ``` And it’s done! More information about the parameters could be found in repository in Readme file. ## Async Apex ### Avoid too many Queueable jobs URL: https://blog.beyondthecloud.dev/tips/avoid-too-many-queueable-jobs Tags: apex, async Summary: That means that in the sync context, you can enqueue up to 50 queueables, 50 futures, and 100 batches, but in a queueable context, only 1 queueable and 50 futures. ### Avoid “Too many queueable jobs...” error with Async Lib Ever hit the “Too many queueable jobs” error due to multiple async entry points? Or tried using @future methods, only to run into the 50-per-transaction limit? Maybe you used if (System.isFuture() && System.isBatch() && ...) checks to skip logic in async contexts—just to avoid the limits? Let’s be honest: that’s a workaround, not a solution. If any of this sounds familiar, this tip is for you. ### Know platform limits ![Avoid too many Queueable jobs — screenshot 1](https://blog.beyondthecloud.dev/tips/avoid-too-many-queueable-jobs/fig-1.webp) That means that in the sync context, you can enqueue up to 50 queueables, 50 futures, and 100 batches, but in a queueable context, only 1 queueable and 50 futures. Total Apex async jobs (future, queueable, batch, scheduled) are capped at 250,000 per 24 hours. Check if you are within the limits ```apex if (Limits.getQueueableJobs() < Limits.getLimitQueueableJobs()) { System.enqueueJob(new MyQueueableJob()); } ``` This way you can prevent the limit errors in synchronous and asynchronous context, where you have only 1 job that can be enqueued! Avoid enqueueing queueable jobs in Triggers or loops ```apex public class MyTriggerHandler { public static void execute() { // some conditions System.enqueueJob(new MyQueueableJob()); } } ``` Unless you are sure you know what you are doing, it is not recommended to enqueue queueable jobs in Triggers, due to the number of them that can be created. But what if you need to enqueue more than the limit allows (especially in Queueable context)? You can: 1.Specify logic to chain the queues when above the limit, in tandem with AsyncOptions. 2.Use Async Lib to manage the queueable jobs, and automatically enqueue the Queueable chain when needed. ```apex // QueueableJob class example public class MyQueueableJob extends QueueableJob { public override void work() { // To access the current job context Async.QueueableJobContext ctx = Async.getQueueableJobContext(); // Your logic here } } //Trigger Handler public class MyTriggerHandler { public static void execute() { // some conditions Async.queueable(new MyQueueableJob()) .enqueue(); } } ``` Considerations Even though the Async Lib framework allows to safely enqueue Queueable Jobs above the Apex Salesforce Limits, it can still lead to unexpected issues or slow down asynchronous execution in Flex Queue due to the number of running jobs. Total Apex async jobs (future, queueable, batch, scheduled) limit is still valid. ### Handle Uncatchable Exceptions in Batch URL: https://blog.beyondthecloud.dev/tips/handle-uncatchable-exceptions-in-batch Tags: apex, debugging, security, async Summary: However, there is another way to achieve this in Apex Batches. Check the next page to see how. ### Handle Uncatchable Exceptions in Batch Some types of exceptions cannot be handled using standard try - catch blocks. Those types are: Limit Exception and Assert Exception: ```apex try { throw new System.LimitException(); } catch(System.LimitException ex) { System.debug('This code will never be executed'); } finally { System.debug('Neither will this one'); } ``` However, there is another way to achieve this in Apex Batches. Check the next page to see how. All changes that needs to be implemented in batch is the implementation of the Database.RaisesPlatformEvents interface: ```apex public with sharing class BatchExample implements Database.Batchable, Database.RaisesPlatformEvents { //Code of your batch } ``` Now you can subscribe to this event stream using Apex Trigger or Flow: ```apex trigger MarkDirtyIfFail on BatchApexErrorEvent (after insert) { // Trigger Logic } ``` BatchApexErrorEvent contains information about Async Apex Job Id, Exception Type, JobScope (all records Ids), Message, Phase of the batch (Start, Execute, Finish) and Stack Trace. Developer can use that information for e.g. error logging, retry mechanisms, user notifications ### Manage Scheduled Jobs URL: https://blog.beyondthecloud.dev/tips/manage-scheduled-jobs Tags: — Summary: It will allow you to use Schedule Builder or Cron Expression to configure the job execution schedule ### Manage Scheduled Jobs Did you know that starting with the Summer '24 release, you are able to view and alter the scheduling of your jobs without having to delete a job and create a new one ? ![Manage Scheduled Jobs — screenshot 1](https://blog.beyondthecloud.dev/tips/manage-scheduled-jobs/fig-1.webp) In All Scheduled Jobs you can now click on “Manage” action It will allow you to use Schedule Builder or Cron Expression to configure the job execution schedule ![Manage Scheduled Jobs — screenshot 2](https://blog.beyondthecloud.dev/tips/manage-scheduled-jobs/fig-2.webp) You can also temporarily pause the job instead of deleting it if you plan to use it later; however, paused jobs still count against the Scheduled Jobs limit. ### Run Finalizer after Queueable URL: https://blog.beyondthecloud.dev/tips/run-finalizer-after-queueable Tags: debugging, security, async Summary: There are few benefits: reusable post-Queueable actions controls what happens when Queueable succeeds or fails easy way of logging results running summarizing jobs enqueue other jobs Did you know that you can execute code after Queueable finishes? It’s easier than you think! Let's add Finalizer to an example Queueable class: ```apex public with sharing class QueueableExample implements Queueable { public void execute(QueueableContext context) { System.debug('An example of Queueable execution!'); } } ``` The first step is Finalizer implementation: ```apex public with sharing class FinalizerExample implements Finalizer { public void execute(FinalizerContext context) { System.debug('Job done!'); } } ``` Next, attach Finalizer to Queueable execution: ```apex public with sharing class QueueableExample implements Queueable { public void execute(QueueableContext context) { FinalizerExample myFinalizer = new FinalizerExample(); System.attachFinalizer(myFinalizer); System.debug('An example of Queueable execution!'); } } ``` Okay, cool! But why would I want to do it? There are few benefits: reusable post-Queueable actions controls what happens when Queueable succeeds or fails easy way of logging results running summarizing jobs enqueue other jobs And a lot more! Check the links in the post to learn more. ## CSS & Styling ### Access HTML attributes in CSS URL: https://blog.beyondthecloud.dev/tips/access-html-attributes-in-css Tags: css Summary: You can use the attr() function to retrieve the value of an attribute of the selected element and use it in the stylesheet. Access HTML attributes in CSS You can use the attr() function to retrieve the value of an attribute of the selected element and use it in the stylesheet. ```markup
``` ```css .my-class::after { content: attr(data-value); } ``` Why it can be useful? You can add text to the lightning-spinner to inform the user about the processing stage. ```markup ``` ```css .loading-spinner::after { position: absolute; content: attr(data-text); width: 100%; text-align: center; font-weight: bold; top: calc(50% + 3em); } ``` ![Access HTML attributes in CSS — screenshot 3](https://blog.beyondthecloud.dev/tips/access-html-attributes-in-css/fig-3.webp) ### CSS calc() URL: https://blog.beyondthecloud.dev/tips/css-calc Tags: css Summary: You can use the CSS calc(expression) function to dynamically calculate the CSS props of the elements. You can mix the units inside. This feature is especially useful when combined with… Dynamic CSS properties with calc() function You can use the CSS calc(expression) function to dynamically calculate the CSS props of the elements. You can mix the units inside. This feature is especially useful when combined with CSS variables. ```css /* Dynamic dimensions of the page content based on footer and sidebar*/ :host { --sidebar-width: 200px; --footer-height: 300px; } .content { height: calc(100% - var(--footer-height)); width: calc(100% - var(--sidebar-width)); } .footer { height: var(--footer-height); } .sidebar { width: var(--sidebar-width); } ``` Here are a few more examples ```css /* Dynamic element width */ .element { width: calc(100% - var(--gap-width) / 2); } /* Static size ratio */ .container { width: 50%; height: calc(50% * 0.75); /* Maintain 4:3 aspect ratio */ } /* Dynamic size calculation based on different units */ .some-section { height: calc(100% - 2em); } /* Complex calc for page elements */ .complex { height: calc(100% - var(--header-height) - var(--footer-height) - var(--gap-height) * 2); } /* ... whatever else you need */ ``` ### CSS max min functions URL: https://blog.beyondthecloud.dev/tips/css-max-min-functions Tags: css Summary: You can use the CSS min() and max() functions to dynamically calculate CSS props of the elements and put min/max constraints on them. Dynamic CSS properties with min() and max() functions You can use the CSS min() and max() functions to dynamically calculate CSS props of the elements and put min/max constraints on them. ```css /* Set width dynamically depending on the available space */ .element { width: min(300px, 100%); } /* Ensure that the relative font size doesn't exceed certain limits */ .text { font-size: min(1rem, 20px); } /* Dynamic margin/padding */ .content--max-margin { margin: max(20px, 5%); } .content--min-padding { padding: min(20px, 5%); } /* Ensure images occupy the maximum available space without stretching beyond a certain size. */ .img { width: max(200px, 50%); height: auto; } /* Specify the minimum width of columns while allowing them to grow as per content or available space. */ .column { min-width: min(200px, 20%); } ``` ## JavaScript ### Destructuring assignment URL: https://blog.beyondthecloud.dev/tips/destructuring-assignment Tags: lwc Summary: The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays or properties from objects into distinct variables. ### Destructuring assignment in LWC The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays or properties from objects into distinct variables. The most popular use of destructuring assignment can be found in wire. ```javascript @wire(getRecord, { recordId: "$recordId", fields: ["Account.Name"] }) wiredAccount({ error, data }) { if (data) { this.record = data; } else if (error) { console.error(error); } } ``` Transformation from an object to an array. ```javascript Object.entries(person).map(row => ({ field: row[0], value: row[1] })); ``` ```javascript Object.keys(person).map(key => ({ field: key, value: person[key] })); ``` ```javascript Object.entries(person).map(([property, value]) => ({ field: property, value })); ``` Destructuring in Function Parameters. ```javascript import { LightningElement} from 'lwc'; export default class EventHandling extends LightningElement { handleEvent(event) { console.log(`Value of ${event.target.name} changed to ${event.target.value}`); } } ``` ```javascript import { LightningElement } from 'lwc'; export default class EventHandling extends LightningElement { handleEvent({ target: { value, name } }) { console.log(`Value of ${name} changed to ${value}`); } } ``` Destructuring with Default Values. ```javascript import { LightningElement } from 'lwc'; export default class DefaultValueExample extends LightningElement { config = { theme: 'light' }; getConfig() { const theme = this.config.theme; const layout = this.config.layout ?? 'standard'; return `Theme: ${theme}, Layout: ${layout}`; } } ``` ```javascript import { LightningElement } from 'lwc'; export default class DefaultValueExample extends LightningElement { config = { theme: 'light' }; getConfig() { const { theme, layout = 'standard' } = this.config; return `Theme: ${theme}, Layout: ${layout}`; } } ``` ### JavaScript snippets URL: https://blog.beyondthecloud.dev/tips/javascript-snippets Tags: — Summary: Navigate to the Sources tab and select Snippets from the left toolbar. ### Chrome Dev Tools Javascript Snippets Do you need to experiment with javascript quickly or just want to run a script in your browser a few times? Checkout the Snippets feature of Chrome Developer Tools ![JavaScript snippets — screenshot 1](https://blog.beyondthecloud.dev/tips/javascript-snippets/fig-1.webp) Here’s what you need to do step by step Open the Chrome Console by pressing F12 on Windows or Fn + F12 on MAC. Navigate to the Sources tab and select Snippets from the left toolbar. Click New snippet and name it. Write your script, save, and execute as many times as you wish. ### Object property shorthand URL: https://blog.beyondthecloud.dev/tips/object-property-shorthand Tags: — Summary: It applies only when the property name is the same as the name of the variable that stores a value. ### JavaScript Property Definitions Shorthand To put variables into an object you can do something like that: ```javascript import edit from '@salesforce/label/c.Edit'; import save from '@salesforce/label/c.Save'; const labels = { edit: edit, save: save }; export default labels; ``` There is a shorter notation available to achieve the same: ```javascript import edit from '@salesforce/label/c.Edit'; import save from '@salesforce/label/c.Save'; const labels = { edit, save }; export default labels; ``` It applies only when the property name is the same as the name of the variable that stores a value. ```javascript showToast(title, message, variant) { this.dispatchEvent( new ShowToastEvent({ title: title, message: message, variant: variant }) ); } ``` ```javascript showToast(title, message, variant) { this.dispatchEvent( new ShowToastEvent({ title, message, variant }) ); } ``` ## Testing ### Debug with Assert class URL: https://blog.beyondthecloud.dev/tips/debug-with-assert-class Tags: apex, debugging Summary: However, debugging with the Assert class is a straightforward and efficient way to debug both Apex and Apex Unit Tests. ### Debug with Assert class How to debug code with the Assert class? There are numerous Apex debugging methods. However, debugging with the Assert class is a straightforward and efficient way to debug both Apex and Apex Unit Tests. ```apex @IsTest static void myTestMethod() { Account acc = [SELECT Id, Name FROM Account WHERE Name = 'TestAcc']; Assert.areEqual(new Account(), acc); // This will fail, and you will see the value List contacts = MyController.getAccountContacts(acc.Id); ... } ``` ```bash System.AssertException: Assertion Failed: Expected: Account:{}, Actual: Account:{Id=0013M00001HrGndQAF, Name=Beyond The Cloud} ``` Why can it be useful? Debug Unit Test - You can view System.debug statements in Apex, but not in Apex Unit Tests. Adding an Assert statement allows you to inspect the variable values. Debug in CICD - Encounter issues in your CI/CD process? Tests work in the development environment but fail during deployment? Adding an Assert statement can help you debug these issues. ### Set Record Created Date In Test URL: https://blog.beyondthecloud.dev/tips/set-record-created-date-in-test Tags: apex, async Summary: You can use Test.setCreatedDate to set the CreatedDate of the inserted record inside Unit Test. This is helpful when the logic you’re testing depends on the records’ creation date - for… ### How to set record created date in Apex Unit Test? You can use Test.setCreatedDate to set the CreatedDate of the inserted record inside Unit Test. This is helpful when the logic you’re testing depends on the records’ creation date - for example, a batch job that clears all Log__c records older than 5 days. ```apex @IsTest static void yourBatchTestExample() { Log__c log = new Log__c( Body__c = 'Sample log body', Category__c = LogCategory.INFO ); insert log; Test.setCreatedDate(log.Id, DateTime.now().addDays(-10)); Test.startTest(); Database.executeBatch(new LogsClearBatch()); Test.stopTest(); Integer logsNumber = [SELECT COUNT() FROM Log__c]; Assert.areEqual(0, logsNumber, 'Log created 10 days ago should be deleted.'); } ``` Let’s check an example ### Use Map and JSON.serialize to mock HTTP Response URL: https://blog.beyondthecloud.dev/tips/use-map-and-json-serialize-to-mock-http-response Tags: — Summary: Since the HTTP Body is a JSON, in this way, the code is as much similar/formatted as standard JSON as possible. ### Use Map and JSON.serialize to mock HTTP Response Did you ever try to mock an HTTP response? Perhaps you did something like this: ```apex HttpResponse response = new HttpResponse(); response.setHeader('Content-Type', 'application/json'); response.setBody('{"name":"my_username", "first-name": "My", "email": "user" + UserInfo.getUserId() + "@example.test", "attributes": { "rel": "edit" }}'); response.setStatusCode(200); return response; ``` This is the most common approach, but... You can make it cleaner with Map and JSON.serialize. ```apex HttpResponse response = new HttpResponse(); response.setHeader('Content-Type', 'application/json'); response.setBody(JSON.serialize( new Map{ 'name' => 'my_username', 'first-name' => 'My', 'email' => 'user' + UserInfo.getUserId() + '@example.test', 'attributes' => new Map{ 'rel' => 'edit' } } )); response.setStatusCode(200); return response; ``` Since the HTTP Body is a JSON, in this way, the code is as much similar/formatted as standard JSON as possible. There is no need to create wrappers. Just a simple Map, JSON.serialize, and voilà! ## Integration ### HTTP status codes URL: https://blog.beyondthecloud.dev/tips/http-status-codes Tags: apex, best-practices, security Summary: The value returned when an external ID exists in more than one record. The response body contains the list of matching records. ### Do you know the meaning of HTTP response status codes? Probably, you are used to the following: ![HTTP status codes — screenshot 1](https://blog.beyondthecloud.dev/tips/http-status-codes/fig-1.webp) But do you know there are many more codes, and all have a meaning to them? ### You can check the below list of some status codes for standard Salesforce APIs: Description HTTP response code “OK” success code, for GET, HEAD, and some PATCH requests. The value returned when an external ID exists in more than one record. The response body contains the list of matching records. The request content hasn’t changed since a specified date and time. The date and time is provided in a If- Modified-Since header. See Get Object Metatdata Changes for an example. The request couldn’t be understood, usually because the JSON or XML body contains an error. The session ID or OAuth token used has expired or is invalid. The response body contains the message and errorCode. The request has been refused. Verify that the logged-in user has appropriate permissions. If the error code is REQUEST_LIMIT_EXCEEDED, you’ve exceeded API request limits in your org. The requested resource couldn’t be found. Check the URI for errors, and verify that there are no sharing issues. The length of the URI exceeds the 16,384-byte limit. Salesforce Edge doesn’t have routing information available for this request host. Contact Salesforce Customer Support. The combined length of the URI and headers exceeds the 16,384-byte limit. Salesforce Edge wasn’t able to communicate successfully with the Salesforce instance. Knowing what Salesforce means by status code is useful, but sometimes we need to create our own endpoints, what in that situation? ### In those cases, we should follow industry standards, for example RFC 9110, some example codes and meanings below: Description HTTP response code Indicates that the request has succeeded. The content sent in a 200 response depends on the request method. For GET method, it would mean that the resource has been fetched and transmitted in the message body. Server has successfully fulfilled the request and that there is no additional content to send in the response content. The target resource has been assigned a new permanent URI and any future references to this resource ought to use one of the enclosed URIs. Access to the target resource is no longer available at the origin server and that this condition is likely to be permanent. The expectation given in the request's Expect header field could not be met by at least one of the inbound servers. ### It is a good practice for our APIs to return proper codes as a response! ### How can you do it with Rest Apex? Status Code Description CREATED Use RestContext.response.statusCode to assign one of the status codes from the list on right (unfortunately, Salesforce allows only those codes as a response). ACCEPTED NO_CONTENT PARTIAL_CONTENT MULTIPLE_CHOICES MOVED_PERMANENTLY FOUND NOT_MODIFIED BAD_REQUEST UNAUTHORIZED FORBIDDEN ```apex @RestResource(urlMapping='/ImportantEndpoint/*') global with sharing class ImportantEndpointCallback { @HttpPost global static ResponseWrapper importantEndpoint() { try { ...some code... } catch (Exception e) { RestContext.response.statusCode = 302; return new ErrorWrapper(e); } return new SuccessWrapper(); } } ``` NOT_FOUND METHOD_NOT_ALLOWED NOT_ACCEPTABLE CONFLICT GONE PRECONDITION_FAILED REQUEST_ENTITY_TOO_LARGE REQUEST_URI_TOO_LARGE UNSUPPORTED_MEDIA_TYPE EXPECTATION_FAILED ### Check post description for links to resource material! INTERNAL_SERVER_ERROR SERVER_UNAVAILABLE ### Use Builder Pattern for Integrations URL: https://blog.beyondthecloud.dev/tips/use-builder-pattern-for-integrations Tags: apex Summary: In the metadata, we are saving Endpoint-specific information that won’t change in any condition. Note the “Credentials” column, there we can store Named Credentials name to use in Apex… ### Organize your Integrations Are you working in an environment that is heavily relying on integration? Do you struggle with a lot of Apex class integrations? Introduce those easy changes to make your life easier! Firstly, create simple metadata with all the endpoints: ![Use Builder Pattern for Integrations — screenshot 1](https://blog.beyondthecloud.dev/tips/use-builder-pattern-for-integrations/fig-1.webp) In the metadata, we are saving Endpoint-specific information that won’t change in any condition. Note the “Credentials” column, there we can store Named Credentials name to use in Apex class. Now let's add some abstraction on top of the standard HTTP class: ```apex public class HttpBuilder { private Integrations__mdt metadata; private Map headers = new Map(); private Map params = new Map(); private String body = null; private Integer timeout = 120000; public HttpBuilder(String metadata) { this.metadata = Integrations__mdt.getInstance(metadata); } public HttpBuilder buildHeader(String key, String value) { this.headers.put(key, value); return this; } public HttpBuilder buildParam(String key, String value) { this.params.put(key, value); return this; } public HttpBuilder buildBody(String body) { this.body = body; return this; } public HttpBuilder buildTimeout(Integer timeout) { this.timeout = timeout; return this; } } ``` And finally, build the request: ```apex public HttpRequest build() { HttpRequest request = new HttpRequest(); request.setEndpoint(getAddress(metadata.Credentials__c, metadata.Endpoint__c, params)); request.setMethod(metadata.Method__c); request.setHeader('Content-Type', metadata.Content_Type__c); for (String key : headers.keySet()) { request.setHeader(key, headers.get(key)); } if (String.isNotBlank(body)) { request.setBody(body); } request.setTimeout(timeout); return request; } private String getAddress(String credentials, String endpoint, Map params) { for (String key : params.keySet()) { endpoint += '&' + key + '=' + params.get(key); } endpoint = endpoint.replaceFirst('&', '?'); return 'callout:' + credentials + '/' + endpoint; } ``` Final result: ```apex public static HttpResponse getStandardAccounts() { HttpRequest request = new HttpRequest(); request.setEndpoint('callout:MULESOFT/get_accounts'); request.setMethod('GET'); request.setHeader('Content-Type', 'application/json'); request.setTimeout(60000); return new Http().send(request); } ``` ```apex public static HttpResponse getBuilderAccounts() { HttpRequest request = new HttpBuilder('Get_Accounts').buildTimeout(60000).build(); return new Http().send(request); } ``` By using Custom Metadata, we can create a Builder class which provides an abstraction over the standard HttpRequest class. In the builder we are covering often duplicated code, such as setting method, endpoint etc. Thanks to that, we can create HttpRequest in only one line!