DEV Community

Sabir Sheikh
Sabir Sheikh

Posted on

πŸ”§ Add, Edit, and Delete Questions Dynamically in Salesforce Using LWC

In many audit-related Salesforce applications, users often need to maintain a dynamic list of questions β€” especially in sectors like manufacturing where compliance and quality assurance require periodic updates. In this blog post, we’ll walk through building a Lightning Web Component (LWC) that allows users to add, edit, and delete questions and store them in a custom field as JSON.

🧩 Use Case
We want to allow auditors to manage a list of questions (add, edit, delete) directly from a record page, and save them in a field (QuestionSet_c) of a custom object (Auditor_Question_c).

πŸ“¦ Salesforce Schema Setup
Object: Auditor_Question__c

Fields Used:
Name (Standard)
QuestionSet__c (Long Text Area / Rich Text – stores JSON array of questions)

🧠 Key Functionalities

  • Fetch record data using @wire(getRecord)
  • Display a list of questions (parsed from JSON)
  • Add new questions
  • Edit existing questions
  • Delete unwanted questions
  • Update QuestionSet__c field with modified JSON using updateRecord

βœ… AuditorQuestionController.cls

public with sharing class AuditorQuestionController {

    @AuraEnabled(cacheable=true)
    public static Auditor_Question__c findRecordByName(String name) {
        // Validate input
        if (String.isBlank(name)) {
            return null;
        }

        // Query the record by Name
        List<Auditor_Question__c> results = [
            SELECT Id, Name, QuestionSet__c
            FROM Auditor_Question__c
            WHERE Name = :name
            LIMIT 1
        ];

        // Return the first result or null if not found
        return results.isEmpty() ? null : results[0];
    }
}

Enter fullscreen mode Exit fullscreen mode

πŸ” Notes:
The @AuraEnabled(cacheable=true) annotation makes it usable in LWC wire adapters for better performance (read-only).

with sharing ensures field-level security and record-level access are respected.

You can add fields like QuestionSet__c as required for your use case.

You can also expand this controller with additional methods like:

  • updateQuestions(String recordId, String questionsJson)

  • createNewAuditorQuestion(...)

πŸ’» LWC Template (HTML)

<template>
    <lightning-card title="Add New Question New">
        <div class="slds-p-around_medium">
            <p><strong>Record Name:</strong> {recordName}</p>

            <lightning-input
                label="Add New Question"
                value={inputValue}
                onchange={handleInputChange}>
            </lightning-input>

            <lightning-button
                label="Add Question"
                class="slds-m-top_medium"
                onclick={handleAdd}>
            </lightning-button>

            <template if:true={questionArray}>
                <template for:each={questionArray} for:item="q">
                    <div key={q.id} class="slds-box slds-m-top_small">
                        <template if:false={isEdit}>
                            <p>{q.Question}</p>
                        </template>
                        <template if:true={isEdit}>
                            <lightning-input
                                value={q.Question}
                                data-id={q.id}
                                onchange={handleChangeUpdateQuestion}
                                label="Edit Question">
                            </lightning-input>
                        </template>

                        <template if:false={isEdit}>
                            <lightning-button label="Edit" onclick={handleEdit}></lightning-button>
                        </template>
                        <template if:true={isEdit}>
                            <lightning-button label="Update" onclick={handleUpdate}></lightning-button>
                            <lightning-button label="Cancel" onclick={handleEdit}></lightning-button>
                        </template>

                        <template if:false={isEdit}>
                            <lightning-button
                                variant="destructive"
                                label="Delete"
                                data-id={q.id}
                                onclick={handleDelete}>
                            </lightning-button>
                        </template>
                    </div>
                </template>
            </template>
        </div>
    </lightning-card>
</template>

Enter fullscreen mode Exit fullscreen mode

🧠 LWC JavaScript (JS)

import { LightningElement, api, track, wire } from 'lwc';
import { getRecord, updateRecord } from 'lightning/uiRecordApi';
import QUESTION_SET_FIELD from '@salesforce/schema/Auditor_Question__c.QuestionSet__c';
import NAME_FIELD from '@salesforce/schema/Auditor_Question__c.Name';

const FIELDS = [NAME_FIELD, QUESTION_SET_FIELD];

export default class AddMoreQuestionsNew extends LightningElement {
    @api recordId;
    @track recordName = '';
    @track inputValue = '';
    @track questionArray = [];
    @track isEdit = false;

    @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
    wiredRecord({ error, data }) {
        if (data) {
            this.recordName = data.fields.Name.value;
            try {
                const decoded = data.fields.QuestionSet__c.value.replace(/&quot;/g, '"');
                this.questionArray = JSON.parse(decoded);
            } catch (e) {
                console.error("Failed to parse JSON:", e);
            }
        } else if (error) {
            console.error('Error fetching record:', error);
        }
    }

    handleInputChange(event) {
        this.inputValue = event.target.value;
    }

    handleAdd() {
        const trimmed = this.inputValue.trim();
        if (trimmed) {
            const newQuestion = {
                id: Date.now(),
                Question: this.inputValue
            };
            this.questionArray = [...this.questionArray, newQuestion];
            this.inputValue = '';
            this.updateQuestionSet(JSON.stringify(this.questionArray));
        }
    }

    handleEdit() {
        this.isEdit = !this.isEdit;
    }

    handleChangeUpdateQuestion(event) {
        const id = parseInt(event.target.dataset.id, 10);
        const updatedValue = event.target.value;
        this.questionArray = this.questionArray.map(item =>
            item.id === id ? { ...item, Question: updatedValue } : item
        );
    }

    handleUpdate() {
        this.updateQuestionSet(JSON.stringify(this.questionArray));
        this.handleEdit();
    }

    handleDelete(event) {
        const idToDelete = parseInt(event.target.dataset.id, 10);
        this.questionArray = this.questionArray.filter(q => q.id !== idToDelete);
        this.updateQuestionSet(JSON.stringify(this.questionArray));
    }

    updateQuestionSet(questionJsonString) {
        const fields = {
            Id: this.recordId,
            [QUESTION_SET_FIELD.fieldApiName]: questionJsonString
        };

        updateRecord({ fields })
            .then(() => {
                // Success message or toast can be shown here
            })
            .catch(error => {
                console.error('Update error:', error);
                alert('Failed to update: ' + (error.body?.message || error.message));
            });
    }
}

Enter fullscreen mode Exit fullscreen mode

βœ… AddMoreQuestionsNew.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>63.0</apiVersion>
      <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)