Question T22462
Visible to All Users

Dropdown itemComponent together with renderAs

created 10 days ago

I have the following itemComponent to render currencies with icons:

TypeScript
import { ReactElementFactory } from "survey-react-ui"; import React from "react"; import { ItemValue, ListModel } from "survey-core"; const CurrencyItemComponent: React.FC<{ item: ItemValue; key: number; model: ListModel; }> = ({ item }) => { return ( <div className="flex items-center gap-2"> <img src={`https://hatscripts.github.io/circle-flags/flags/${item.isoCode.toLowerCase()}.svg`} alt={item.title} width={20} height={20} /> <span>{item.title}</span> </div> ); }; export default CurrencyItemComponent; ReactElementFactory.Instance.registerElement("currency-item", (props) => { return React.createElement(CurrencyItemComponent, props); });

Assuming I also have a custom dropdown component that is being used in the prop

JSON
renderAs: "dropdown-question"

This is the question definition in the schema:

Code
{ "type": "dropdown", "name": "crp-factory-yearly-turnover-currency-dropdown", "width": "10%", "maxWidth": "10%", "renderAs": "dropdown-question", "titleLocation": "hidden", "startWithNewLine": false, "itemComponent": "currency-item", "choicesByUrl": { "url": "our_url", "valueName": "currency", "titleName": "currency", } },
  1. What is the best way to access the rendered items of the itemComponent function from within the custom dropdown component?
  2. can I add custom props to "choicesByUrl", for example, "isoCode": "isoCode", so i can accees it from the itemComponent as item.isoCode?
Comments (2)

    Hello Amit,
    You may use the Serializer API to register custom properties for the itemvalue class objects. You'll be able to access these custom properties within your custom item component as item.yourCustomProp.

    What is the best way to access the rendered items of the itemComponent function from within the custom dropdown component?

    Unfortunately, I don't completely follow this requirement. Would you please elaborate your task in greater detail?

    Thank you

      Hey Jane, thank you for your answer.

      After adding the custom prop for the itemvalue object like this:

      TypeScript
      Serializer.addProperty("itemvalue", { name: "isoCode", default: "", });

      I then use it in our itemComponent like this:

      TypeScript
      import { ReactElementFactory } from "survey-react-ui"; import React from "react"; import { ItemValue, ListModel } from "survey-core"; const CurrencyItemComponent: React.FC<{ item: ItemValue; key: number; model: ListModel; }> = ({ item }) => { return ( <div className="flex items-center gap-2"> <img src={`https://hatscripts.github.io/circle-flags/flags/${item.isoCode.toLowerCase()}.svg`} alt={item.title} width={20} height={20} /> <span>{item.title}</span> </div> ); }; export default CurrencyItemComponent; ReactElementFactory.Instance.registerElement("currency-item", (props) => { return React.createElement(CurrencyItemComponent, props); });

      Then we have a custom dropdown question that looks something like this:

      TypeScript
      import { ChangeEvent, FC, createElement, useCallback, useMemo, useState, } from "react"; import { QuestionDropdownModel, RendererFactory } from "survey-core"; import { ReactQuestionFactory } from "survey-react-ui"; import { Dropdown } from "../ui/dropdown"; import classNames from "classnames"; interface DropdownQuestionProps { isDisplayMode: boolean; question: QuestionDropdownModel; } const DropdownQuestion: FC<DropdownQuestionProps> = ({ isDisplayMode, question, }) => { const { value, isReadOnly, name, allowClear, isRequired, placeholder, otherItem, noneItem, hasOther, hasNone, isOtherSelected, commentPlaceHolder, } = question; const [textAreaInput, setTextAreaInput] = useState(question.comment); const isDisabled = useMemo(() => { return isReadOnly; }, [isReadOnly]); const onValueChange = useCallback( (newVal: string) => { question.value = newVal; }, [question] ); const onCommentChange = (event: ChangeEvent<HTMLTextAreaElement>) => { setTextAreaInput(event.target.value); }; const onCommentBlur = () => { question.comment = textAreaInput; }; const getItems = useMemo(() => { const items = question.dataChoices.map(({ title, value }) => ({ title, value, })); if (hasNone) items.push(noneItem); if (hasOther) items.push(otherItem); if (allowClear) items.unshift({ value: undefined, title: "" }); return items; }, [ question.dataChoices, hasNone, noneItem, hasOther, otherItem, allowClear, ]); return ( <div className="flex flex-col gap-2"> <Dropdown aria-required={question.ariaRequired} aria-label={question.ariaLabel} aria-invalid={question.ariaInvalid} aria-errormessage={question.ariaErrormessage} autoComplete={question.autocomplete} onClick={question.onClick} onKeyUp={question.onKeyUp} id={question.id} items={getItems} required={isRequired} disabled={isDisabled} value={value} name={name} dense onValueChange={onValueChange} placeholder={placeholder} defaultValue={question.getDefaultValue()} /> {isOtherSelected && ( <textarea disabled={isDisabled} onChange={onCommentChange} onBlur={onCommentBlur} className={classNames( "appearance-none static border-box bg-white w-full py-2 px-[14px] shadow-xs rounded-lg border border-gray-300 focus:text-gray-800 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-100 outline-none disabled:bg-gray-50 disabled:text-gray-500 text-xs md:text-sm leading-[16px] md:leading-5 text-start font-regular text-gray-500 placeholder-gray-300" )} value={textAreaInput} placeholder={commentPlaceHolder} /> )} </div> ); }; ReactQuestionFactory.Instance.registerQuestion( "sv-dropdown-question", function (props: any) { return createElement(DropdownQuestion, props); } ); RendererFactory.Instance.registerRenderer( "dropdown", "dropdown-question", "sv-dropdown-question" );

      As you can see, we use the question.dataChoices as the options values for the dropdown question but this just brings the array of values and titles. What I want is the ReactNodes of all the rendered options with the custom itemComponent.

      Answers approved by surveyjs Support

      created 7 days ago

      Hello Amit,
      Thank you for sharing the code. A custom itemComponent works only if you preserve the default renderer for a Dropdown question. When you use a default renderer, a Dropdown creates a Popup model which use a list of ListItem objects. It renders a standard (default) or a custom item component. If you'd like to learn more, check the following line of code: https://github.com/surveyjs/survey-library/blob/master/packages/survey-react-ui/src/components/list/list-item.tsx#L28.

      However, since you're creating a custom renderer for a Dropdown question model, your custom item component is not used. You may want to modify your custom Dropdown question's renderer and render your custom item component.

        Show previous comments (6)

          ok perfect that's exactly what I wanted

            I would suggest you add it to the doc page of ChoicesRestful

              Thank you for your input. We'll consider adding this option to the ChoicesRestful documentation.

              Thanks