Issue T22475
Visible to All Users

Error when switched to JSON Editor tab or click Save button after modifying custom `queryParams` property in specialized dropdown component: "Cannot set property elements of # which has only a getter"

created 8 days ago

Hi SurveyJS Team and Community

Your form library is awesome. I am considering to buy a license for surveyjs creator to use it in my real case. But now I am facing a problem that I don't know how to fix.

I've created a specialized dropdown component (master-data-dropdown) that uses dynamic choicesByUrl data based on a custom masterApiType dropdown property. The component also allows users to add custom additional query parameters through a custom queryParams itemvalues property.

Current Behavior

When a user changes the masterApiType value, I update the available parameters for the queryParams property using question.setPropertyValue in the onPropertyChanged event handler. This works fine for certain masterApiType values (continent, region, province), but fails for others (country, district, subdistrict, and switch default cases).

Specifically, when the queryParams array is not empty and the user attempts to:

  1. Switch to the JSON Editor tab, OR
  2. Save the survey

Everything works perfectly in the "Design" and "Preview" tabs until the user attempts to change to the "JSON Editor" tab or clicks the "Save" button. At these points, the following error occurs (only when the queryParams array is not empty). The following error occurs:

Code
survey-creator-core.js:29119 Uncaught TypeError: Cannot set property elements of #<QuestionCustomModelBase> which has only a getter at SurveyCreator.moveElementsToTheEnd (survey-creator-core.js:29119:38) at eval (survey-creator-core.js:29121:55) at Array.forEach (<anonymous>) at SurveyCreator.moveElementsToTheEnd (survey-creator-core.js:29121:35) at eval (survey-creator-core.js:29112:37) at Array.forEach (<anonymous>) at SurveyCreator.moveElementsToTheEnd (survey-creator-core.js:29112:18) at eval (survey-creator-core.js:29121:55) at Array.forEach (<anonymous>) at SurveyCreator.moveElementsToTheEnd (survey-creator-core.js:29121:35) at eval (survey-creator-core.js:29112:37) at Array.forEach (<anonymous>) at SurveyCreator.moveElementsToTheEnd (survey-creator-core.js:29112:18) ... at SurveyCreator.getSurveyTextFromDesigner (survey-creator-core.js:29101:14) at get text (survey-creator-core.js:29166:21) at TextareaJsonEditorModel.onPluginActivate (survey-creator-core.js:6184:34) at new TextareaJsonEditorModel (survey-creator-core.js:6513:14) at TabJsonEditorTextareaPlugin.createModel (survey-creator-core.js:6592:16) at TabJsonEditorTextareaPlugin.activate (survey-creator-core.js:6370:27) at SurveyCreator.activatePlugin (survey-creator-core.js:27566:20) at SurveyCreator.switchViewType (survey-creator-core.js:27558:29) at SurveyCreator.switchTab (survey-creator-core.js:27535:21) at TabbedMenuItem.action (survey-creator-core.js:26459:42) at onClick (survey-creator-react.js:180:190) at executeDispatch (react-dom-client.development.js:16352:9) at runWithFiberInDEV (react-dom-client.development.js:1511:30) at processDispatchQueue (react-dom-client.development.js:16402:19) at eval (react-dom-client.development.js:17000:9) at batchedUpdates$1 (react-dom-client.development.js:3254:40) at dispatchEventForPluginEventSystem (react-dom-client.development.js:16556:7) at dispatchEvent (react-dom-client.development.js:20639:11) at dispatchDiscreteEvent (react-dom-client.development.js:20607:11)

Steps to Reproduce

  1. Create a custom dropdown component using choicesByUrl and a custom masterApiType property
  2. Add functionality to populate a custom queryParams property based on the selected masterApiType
  3. Change masterApiType to 'country', 'district', or 'subdistrict'
  4. Set some values in the queryParams itemvalues property
  5. Try to switch to JSON Editor tab or save the survey

Reproduce

I've created a simplified reproduction of the issue in this CodeSandbox: https://codesandbox.io/p/devbox/fpypkm?embed=1

  1. Go to /survey page
  2. add Master Data Dropdown question
  3. Go to question setting > Master Data > Master Type = Comments (Query Parameters will append item from my script)
  4. Go to JSON Editor tab and got the error

Technical Details

  • SurveyJS: 2.0.1 (survey-core, survey-creator-core, survey-creator-react, survey-react-ui)
  • Next.js: 15.2.0 (App router)
  • React: 19.0.0
  • Browser: Google Chrome 134

Questions

  1. Is there a proper way to update the queryParams property without causing this error?
  2. Could there be differences in object structure when certain masterApiType values are selected?
  3. Is there an alternative approach to achieve this functionality?

I would appreciate any guidance on resolving this issue or a workaround to implement this functionality correctly.

My Custom Specialized Dropdown Code

TypeScript
'use client'; import { ComponentCollection, Question, Serializer, SvgRegistry } from 'survey-core'; import { editorLocalization } from 'survey-creator-core'; import { SurveyCreator } from 'survey-creator-react'; const CUSTOM_TYPE = 'master-data-dropdown'; const translation = editorLocalization.getLocale(''); translation.pe.tabs.master = 'Master Data'; translation.qt[CUSTOM_TYPE] = 'Master Data Dropdown'; export const initializeMasterDataDropdownComponent = ({ creator, apiKey, userKey, }: { creator?: SurveyCreator; apiKey: string; userKey: string; }) => { const changeApiType = (question: Question) => { question.contentQuestion.choicesByUrl.clear(); const masterApiType = question.masterApiType; let apiUrl = ''; switch (masterApiType) { case 'department': apiUrl = '/api/proxy/department/dropdownitem'; break; case 'departmentext': apiUrl = '/api/proxy/departmentext/dropdownitem'; break; case 'continent': apiUrl = '/api/proxy/geo/continent/dropdownitem'; break; case 'region': apiUrl = '/api/proxy/geo/region/dropdownitem'; break; case 'country': apiUrl = '/api/proxy/geo/country/dropdownitem'; break; case 'province': apiUrl = '/api/proxy/geo/province/dropdownitem'; break; case 'district': apiUrl = '/api/proxy/geo/district/dropdownitem'; break; case 'subdistrict': apiUrl = '/api/proxy/geo/subdistrict/dropdownitem'; break; default: apiUrl = '/api/proxy/master/data/dropdownitem?master_type_id=' + masterApiType; break; } apiUrl += apiUrl.indexOf('?') === -1 ? '?' : '&'; apiUrl += 'page=1&pageSize=9999'; if (question.queryEnable) { const queryParams = question.queryParams; if (queryParams && queryParams.length > 0) { apiUrl += '&' + queryParams.map(({ value, text }: { value: string; text: string }) => `${text}=${value}`).join('&'); } } question.contentQuestion.choicesByUrl.url = apiUrl; question.contentQuestion.choicesByUrl.path = 'data'; question.contentQuestion.choicesByUrl.valueName = 'value'; question.contentQuestion.choicesByUrl.titleName = 'text'; question.contentQuestion.choicesByUrl.run(); }; ComponentCollection.Instance.add({ inheritBaseProps: ['placeholder'], name: CUSTOM_TYPE, defaultQuestionTitle: 'Select Data', questionJSON: { type: 'dropdown', name: 'masterDataDropdown', }, onInit() { Serializer.addProperty(CUSTOM_TYPE, { name: 'masterApiType', displayName: 'Master Data Type', default: 'province', type: 'dropdown', category: 'master', visibleIndex: 0, choices: (obj: any, choicesCallback: any) => { const predefinedChoices = [ { value: 'department', text: 'หน่วยงานภายใน' }, { value: 'departmentext', text: 'หน่วยงานภายนอก' }, { value: 'continent', text: 'ทวีป' }, { value: 'country', text: 'ประเทศ' }, { value: 'region', text: 'ภาค' }, { value: 'province', text: 'จังหวัด' }, { value: 'district', text: 'อำเภอ/เขต' }, { value: 'subdistrict', text: 'ตำบล/แขวง' }, // { value: '02107503-b05b-4c04-8127-e9369728e417', text: 'สินค้า' }, get from api below ]; const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/proxy/master/type/dropdownitem'); xhr.setRequestHeader('API-Key', apiKey); xhr.setRequestHeader('User-Key', userKey); xhr.onload = () => { if (xhr.status === 200) { const response = JSON.parse(xhr.response); const result = []; // Make the property nullable result.push({ value: null }); // Web service returns objects that are converted to the `{ value, text }` format // If your web service returns an array of strings, pass this array to `choicesCallback` response.data.forEach((item: { value: string; text: string }) => { result.push({ value: item.value, text: item.text }); }); choicesCallback([...predefinedChoices, ...result]); } else { choicesCallback(predefinedChoices); } }; xhr.send(); }, }); Serializer.addProperty(CUSTOM_TYPE, { name: 'queryEnable', displayName: 'Enable Query Parameters', default: false, type: 'boolean', category: 'master', visibleIndex: 1, }); Serializer.addProperty(CUSTOM_TYPE, { name: 'queryParams', displayName: 'Query Parameters', type: 'itemvalues', category: 'master', visibleIndex: 2, dependsOn: ['queryEnable'], enableIf: (obj) => obj.queryEnable, onSetValue: (surveyElement, value, jsonConv) => { console.log('onSetValue', surveyElement, value, jsonConv); surveyElement.setPropertyValue('queryParams', value); }, }); if (creator) { creator.onPropertyEditorCreated.add((sender, options) => { if (options.property.name === 'queryParams' && options.element.getType() === CUSTOM_TYPE) { if (options.editor && options.editor.columns) { // Swap the first two columns [options.editor.columns[0], options.editor.columns[1]] = [ options.editor.columns[1], options.editor.columns[0], ]; // Replace column titles options.editor.columns[0].title = 'param'; options.editor.columns[1].title = 'value'; } } }); } }, onLoaded(question: Question) { question.onPropertyChanged.add(function (sender, options) { if (options.name === 'masterApiType') { switch (options.newValue) { case 'continent': question.setPropertyValue('queryParams', []); break; case 'country': question.setPropertyValue('queryParams', [{ value: 'continent_id', text: 'continent_id' }]); break; case 'region': question.setPropertyValue('queryParams', []); break; case 'province': question.setPropertyValue('queryParams', []); break; case 'district': question.setPropertyValue('queryParams', [{ value: '{province_code}', text: 'province_code' }]); break; case 'subdistrict': question.setPropertyValue('queryParams', [ { value: '{province_code}', text: 'province_code' }, { value: '{district_code}', text: 'district_code' }, ]); break; default: question.setPropertyValue('queryParams', [{ value: '{parent_id}', text: 'parent_id' }]); break; } changeApiType(question); } }); question.onItemValuePropertyChanged.add(function (sender, options) { changeApiType(question); }); changeApiType(question); }, }); };
Comments (1)

    Answers approved by surveyjs Support

    created 7 days ago (modified 7 days ago)

    Hello,
    Our developers researched this issue. To solve the issue, when you populate the queryParams property with choices, it is necessary to create an instance of an ItemValue class as follows.

    JavaScript
    import { ItemValue } from "survey-core"; //... onLoaded(question: Question) { question.onPropertyChanged.add(function (sender, options) { if (options.name === "masterApiType") { switch (options.newValue) { case "posts": question.setPropertyValue("queryParams", []); break; case "comments": question.setPropertyValue("queryParams", [ new ItemValue("{question1}", "postId"), ]); break; } changeApiType(question); } });

    As a result, a valid survey JSON schema is generated:

    JSON
    { "pages": [ { "name": "page1", "elements": [ { "type": "master-data-dropdown", "name": "question1", "masterApiType": "comments", "queryParams": [ { "value": "{question1}", "text": "postId" } ] } ] } ], "headerView": "advanced" }

    Please let us know if you require further assistance.

      Comments (2)

        Hello,

        Thank you very much for your fast and clear response. Your solution worked perfectly, and the provided example made it straightforward and easy to understand.

        I'm impressed with your products and support quality. This genuinely turned around a difficult day for me. I truly appreciate the dedication and effort of your team.

        Thanks again for your excellent support and for developing such a robust survey creation tool.

        Best regards

          Hello,
          Thank you for your kind words! We're happy to hear that the solution worked for you.
          If you have more questions or need any help, please feel free to reach out. We’re always happy to assist.

          Looking forward to working with you.