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:
- Switch to the JSON Editor tab, OR
- 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:
Codesurvey-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
- Create a custom dropdown component using
choicesByUrl
and a custommasterApiType
property - Add functionality to populate a custom
queryParams
property based on the selectedmasterApiType
- Change
masterApiType
to 'country', 'district', or 'subdistrict' - Set some values in the
queryParams
itemvalues property - 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
- Go to
/survey
page - add
Master Data Dropdown
question - Go to question setting > Master Data > Master Type = Comments (Query Parameters will append item from my script)
- 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
- Is there a proper way to update the
queryParams
property without causing this error? - Could there be differences in object structure when certain
masterApiType
values are selected? - 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);
},
});
};
Hello,
Thank you for sharing a demo. We will research this issue and update you as soon as we get any news to share.
Specialized Question - The 'Uncaught TypeError: Cannot set property elements of #<QuestionCustomModelBase> which has only a getter' exception is thrown when activating the JSON Editor tab