0

I'm working with Vaadin 24 and Kendo React, working to bring these together. I'd like to use the Kendo React webcomponents inside of Vaadin. So far, I've had excellent results with this, insofar as my work with the Grid is concerned. I've recently been requested to display "Yes"/"No" in boolean columns instead of "true"/"false", and this is turning out to be a nightmare situation.

To help explain my situation, here's the full code of what I'm doing, integrating Kendo React with Vaadin:

@Route(value = "testKendoGrid")
@PageTitle("Kendo Grid Demo")
public class TestKendoGrid extends ComponentNavBar {
    /*****************************************************************
     * Managers
     *****************************************************************/

    /*****************************************************************
     * Fields
     *****************************************************************/

    /*****************************************************************
     * Constructors
     *****************************************************************/

    public TestKendoGrid(BureauManager bureauManager) {
        super();
        setSizeFull();
        getHeaderBarControls().setTitleText("Kendo Grid Demo");

        /*****************************************************************
         * UI Elements
         *****************************************************************/
        KendoGrid<Bureaus> kendoGrid = new KendoGrid<>(Bureaus.class);
        kendoGrid.setKendoDataSourceManager(bureauManager);

        kendoGrid.setPageableSettings(new KendoGrid.GridPageable(
            8, true, "numeric", new int[] {5, 10, 25, 100},
            true, true
        ));
        kendoGrid.setPageSize(10);

        kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "id", "ID", true, false, KendoGrid.ColumnFilterType.NUMERIC, false));
        kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "index", "Index", false, true, KendoGrid.ColumnFilterType.NUMERIC, true));
        kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "name", "Name", false, true, KendoGrid.ColumnFilterType.TEXT, true));
        kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "description", "Description", false, true, KendoGrid.ColumnFilterType.TEXT, true));
        kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "enabled", "Enabled", false, null, true, KendoGrid.ColumnFilterType.BOOLEAN, true,
            "<td {...props.tdProps} colSpan={1}> {props.children == 'true' ? 'Yes' : 'No'} </td>"
        ));
        kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "money", "Money", false, "{0:c}", true, KendoGrid.ColumnFilterType.NUMERIC, true));

        kendoGrid.addToolbarButton("removeEnabledColumn", "Remove Enabled Column", removeEvent ->
            kendoGrid.removeColumnDefinition("enabled"));
        kendoGrid.addToolbarButton("addEnabledColumn", "Add Enabled Column", addEvent ->
            kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
                "enabled", "Enabled", false, true, KendoGrid.ColumnFilterType.BOOLEAN, true)));
        kendoGrid.addToolbarButton("setGridHeight", "Set Height to 500px", addEvent ->
            kendoGrid.setKendoStyle(new ComponentStyle("500px", null)));
        kendoGrid.addToolbarButton("removeGridHeight", "Remove Grid Height Setting", addEvent ->
            kendoGrid.setKendoStyle(new ComponentStyle(null, null)));

        kendoGrid.setSortable(true);
        kendoGrid.setFilterable(true);

        kendoGrid.setKendoStyle(new ComponentStyle("500px", null));

        add(kendoGrid);
    }
}
@NpmPackage(value = "@progress/kendo-react-grid", version = "8.3.0")
@NpmPackage(value = "@progress/kendo-react-common", version = "8.3.0")
@NpmPackage(value = "@progress/kendo-react-buttons", version = "8.3.0")
@NpmPackage(value = "@progress/kendo-data-query", version = "1.7.0")
@JsModule("./kendoUi/kendoGrid.tsx")
@Tag("kendo-grid")
@StyleSheet("https://kendo.cdn.telerik.com/themes/8.2.1/default/default-ocean-blue.css")
@CssImport("./kendoUi/kendoGrid.css")
public class KendoGrid<T extends Serializable> extends ReactAdapterComponent {
    /*****************************************************************
     * Statics & Enums & Records
     *****************************************************************/

    public enum ColumnFilterType {
        BOOLEAN,
        TEXT,
        DATE,
        NUMERIC
    }

    public record ToolbarItem(String title, String endpoint, String eventName) {}
    public record ColumnDefinition(String field, String title, boolean hidden, String format, boolean filterable, ColumnFilterType filter, boolean sortable, String template) {
        public ColumnDefinition(String field, String title) {
            this(field, title, false, null, true, ColumnFilterType.TEXT, true, null);
        }
        public ColumnDefinition(String field, String title, boolean hidden) {
            this(field, title, hidden, null, true, ColumnFilterType.TEXT, true, null);
        }
        public ColumnDefinition(String field, String title, boolean filterable, ColumnFilterType filter) {
            this(field, title, false, null, filterable, filter, true, null);
        }
        public ColumnDefinition(String field, String title, boolean filterable, ColumnFilterType filter, boolean sortable) {
            this(field, title, false, null, filterable, filter, sortable, null);
        }
        public ColumnDefinition(String field, String title, String format) {
            this(field, title, false, format, true, ColumnFilterType.TEXT, true, null);
        }
        public ColumnDefinition(String field, String title, boolean hidden, boolean filterable, ColumnFilterType filter, boolean sortable) {
            this(field, title, hidden, null, filterable, filter, sortable, null);
        }
        public ColumnDefinition(String field, String title, boolean hidden, String format, boolean filterable, ColumnFilterType filter, boolean sortable) {
            this(field, title, hidden, format, filterable, filter, sortable, null);
        }
    }
    public record SelectionNavEvent(String navParameterField, String endpoint) {}
    public record GridPageable(
        int buttonCount, //Sets the maximum numeric buttons count before the buttons are collapsed.
        boolean info, //Toggles the information about the current page and the total number of records.
        String pagerType, //Accepts 'numeric' (buttons with numbers) and 'input' (input for typing the page number) values.
        int[] pageSizes, //Boolean or Array<number>—Shows a menu for selecting the page size. If an array, then the number of items displayed in a single page of the grid will be selectable as the array provided, ie - [5, 10, 100, 200, 500]
        boolean previousNext, //Toggles the Previous and Next buttons.
        boolean navigatable //Defines if the pager will be navigatable.
    ) {}
    public record GridFilter(GridFilter[] filters, String logic, String field, String operator, Object value) {}
    public record GridGroup() {}
    public record GridSort(String dir, String field) {}
    public record GridDataState(GridFilter filter, GridGroup group, GridSort[] sort, int skip, int take) {}

    /*****************************************************************
     * Fields
     *****************************************************************/

    private final Class<T> parameterClass;

    private final LinkedHashMap<String, ToolbarItem> toolbarItems = new LinkedHashMap<>();

    private final LinkedHashMap<String, ColumnDefinition> columnDefinitions = new LinkedHashMap<>();

    private List<T> data = new ArrayList<>();

    /**
     * This is a manager that will retrieve data for populating into kendo elements. Necessary data will be fed into the manager,
     * and the manager will provide the desired results.
     */
    private KendoDataSourceManager<T> manager = null;

    /*****************************************************************
     * Constructors
     *****************************************************************/

    public KendoGrid(Class<T> parameterClass) {
        this.parameterClass = parameterClass;

        getElement().addEventListener("selectNavEvent", event -> {
            SelectionNavEvent value = new Gson().fromJson(event.getEventData().getObject("event.detail").toJson(), SelectionNavEvent.class);
            UI.getCurrent().navigate(String.format("%s/%s", value.endpoint, value.navParameterField));
        }).addEventData("event.detail");

        getElement().addEventListener("gridDataStateChangeEvent", event -> {
            GridDataState dataState = new Gson().fromJson(event.getEventData().getObject("event.detail").toJson(), GridDataState.class);
            setGridDataDisplay(dataState);
        }).addEventData("event.detail");
    }

    /*****************************************************************
     * Getters & Setters
     * As these are almost always utility wrappers that utilize the 'setState' and 'getState' methods at a bare minimum,
     * Lombok is not utilized.
     *****************************************************************/

    public void setKendoDataSourceManager(KendoDataSourceManager<T> manager) {
        this.manager = manager;
    }

    public ComponentStyle getKendoStyle() {
        return getState("kendoStyle", ComponentStyle.class);
    }
    public void setKendoStyle(ComponentStyle style) {
        setState("kendoStyle", style);
    }

    /**
     * Retrieves a list of the data currently set as the datasource for the grid.
     * @return - A list of T currently bound to the grid.
     */
    public List<T> getGridData() {
        return getState("data", List.class);
    }
    /**
     * Sets the data to be displayed in the grid.
     * @param gridData - A list of T for display within the grid.
     */
    public void setGridData(List<T> gridData) {
        this.data = gridData;
        setState("data", gridData);
        setState("total", gridData.size());
    }
    private void setGridDataDisplay(GridDataState dataState) {
        KendoDataSource<T> dataSource = manager.getKendoControlData(dataState);
        setState("data", dataSource.getData());
        setState("total", dataSource.getTotal());
    }

    /**
     * Retrieves the currently configured GridPageable settings for the grid.
     * @return - An instance of GridPageable with the current settings.
     */
    public GridPageable getPageableSettings() {
        return getState("pageableSettings", GridPageable.class);
    }
    /**
     * Sets the pageable configuration of the grid as per the provided GridPageable configuration.
     * @param pageableSettings - The configuration for the grid's pageable.
     */
    public void setPageableSettings(GridPageable pageableSettings) {
        setState("pageableSettings", pageableSettings);
        int pageSize;
        try {
            pageSize = getPageSize();
        }
        catch (Exception ignored) {
            pageSize = pageableSettings.pageSizes[0];
            setPageSize(pageSize);
        }
        GridDataState dataStateCurrent = getState("dataState", GridDataState.class);
        GridDataState newDataState = new GridDataState(dataStateCurrent.filter, dataStateCurrent.group, dataStateCurrent.sort,
            0, pageSize);
        setState("dataState", newDataState);
        setGridDataDisplay(newDataState);
    }

    public boolean getSortable() {
        return getState("sortable", Boolean.class);
    }
    public void setSortable(boolean sortable) {
        setState("sortable", sortable);
    }

    public boolean getFilterable() {
        return getState("filterable", Boolean.class);
    }
    public void setFilterable(boolean filterable) {
        setState("filterable", filterable);
    }

    /**
     * Retrieves the currently configured pageSize for the grid.
     */
    public int getPageSize() {
        GridDataState dataStateCurrent = getState("dataState", GridDataState.class);
        return dataStateCurrent.take;
    }
    /**
     * Sets the size of each page for the grid. This is the number of rows that will be displayed. Should be a number found in the
     * PageableSettings GridPageable.pageSizes array, if configured as such.
     * @param pageSize - The number of rows to display on each page of the grid.
     */
    public void setPageSize(int pageSize) {
        GridDataState newDataState;
        try {
            /**
             * Update the current dataState, if it is available.
             */
            GridDataState dataStateCurrent = getState("dataState", GridDataState.class);
            newDataState = new GridDataState(dataStateCurrent.filter, dataStateCurrent.group, dataStateCurrent.sort,
                0, pageSize);
        }
        catch (Exception ignored) {
            /**
             * Otherwise, create a new one with no filters, sorts, or groups.
             */
            newDataState = new GridDataState(null, null, null, 0, pageSize);
        }
        setState("dataState", newDataState);
        setGridDataDisplay(newDataState);
    }

    /**
     * Gets the selectable state of the grid, a boolean indicating whether single selections are allowed or not.
     */
    public boolean getSelectable() {
        return getState("selectable", Boolean.class);
    }
    /**
     * Sets the selectable state for the grid.
     * @param selectable - If true, then a single row can be selected (clicked in the grid). If false, then no rows can be selected at all.
     */
    public void setSelectable(boolean selectable) {
        setState("selectable", selectable);
    }

    public SelectionNavEvent getSelectNavigation() {
        return getState("selectNavigation", SelectionNavEvent.class);
    }
    public void setSelectNavigation(SelectionNavEvent selectionNavEvent) {
        setState("selectNavigation", selectionNavEvent);
    }

    /**
     * Removes the requested button from the toolbar.
     *
     * @param buttonId - The ID of the toolbar button to be removed.
     */
    public void removeToolbarButton(String buttonId) {
        toolbarItems.remove(buttonId);
        setState("toolbarItems", toolbarItems.values());
    }
    /**
     * Adds the specified button to the grid's toolbar. Clicking the button will execute the provided listener, performing the desired actions.
     *
     * @param buttonId - The internal ID of the toolbar button. Must be distinct, otherwise this action will overwrite the previous button
     *                 that utilized this ID.
     * @param title - The text on the button that will be displayed to the user.
     * @param listener - The listener that will be executed when the button is clicked. As the listener can be defined as a lambda, it can have
     *                 access to variables, methods, and other details from the origination point that are not available within the immediate scope
     *                 of the KendoGrid class.
     */
    public void addToolbarButton(String buttonId, String title, DomEventListener listener) {
        toolbarItems.put(buttonId, new ToolbarItem(title, null, buttonId));
        setState("toolbarItems", toolbarItems.values());

        getElement().addEventListener(buttonId, listener);
    }

    /**
     * Adds a new column to the kendo grid, based on the provided configuration.
     * @param columnDefinition - The definition of the column to be added to the grid.
     */
    public void addColumnDefinition(ColumnDefinition columnDefinition) {
        columnDefinitions.put(columnDefinition.field, columnDefinition);
        setState("columnDefinitions", columnDefinitions.values());
    }
    /**
     * Removes the column for the provided field.
     * @param field - The column to be removed from the grid.
     */
    public void removeColumnDefinition(String field) {
        columnDefinitions.remove(field);
        setState("columnDefinitions", columnDefinitions.values());
    }
}
import {ReactAdapterElement, RenderHooks} from "Frontend/generated/flow/ReactAdapter";
import {ReactElement} from "react";
import {
    Grid, GridToolbar, GridColumn as Column, GridSelectionChangeEvent,
    GridSelectableMode, GridSelectableSettings, GridPageChangeEvent,
    GridPagerSettings, PagerTargetEvent, PageState, GridDataStateChangeEvent
} from "@progress/kendo-react-grid";
import { Button } from "@progress/kendo-react-buttons";
import { State } from "@progress/kendo-data-query";

interface SelectNavEvent {
    navParameterField: string,
    endpoint: string
}
interface ToolbarItem {
    title: string,
    endpoint: string,
    eventName: string
}
interface ColumnDefinition {
    field: string,
    title: string,
    hidden: boolean,
    filterable: boolean,
    filter: string,
    sortable: boolean,
    format: string,
    template: string
}
interface GridStyleConfig {
    height: string,
    width: string
}

/**
 * Initial state values for the grid. These are the defaults in place for if the application code does not
 * define a value for the corresponding field.
 */
const initialSortable: boolean = false;
const initialFilterable: boolean = false;
const initialSelectable: boolean = false;
const initialDataState: State = { skip: 0, take: 10 };

class KendoGridElement extends ReactAdapterElement {

    protected override render(hooks: RenderHooks): ReactElement | null {

        const [dataState, setDataState] = hooks.useState<Object[]>('dataState', initialDataState);
        const [gridData] = hooks.useState<Object[]>('data');
        const [selectable] = hooks.useState<boolean>('selectable', initialSelectable);
        const [sortable] = hooks.useState<boolean>('sortable', initialSortable);
        const [filterable] = hooks.useState<boolean>('filterable', initialFilterable);
        const [toolbarItems] = hooks.useState<Array<ToolbarItem>>('toolbarItems');
        const [columnDefinitions] = hooks.useState<Array<ColumnDefinition>>('columnDefinitions');
        const [selectNavigation] = hooks.useState<SelectNavEvent>('selectNavigation');
        const [pageableSettings] = hooks.useState<GridPagerSettings>('pageableSettings');
        const [total] = hooks.useState<number>('total');
        const [kendoStyle] = hooks.useState<GridStyleConfig>('kendoStyle');

        const gridDataStateChangeEvent = hooks.useCustomEvent<Object>('gridDataStateChangeEvent');

        const dataChange = (event: GridDataStateChangeEvent) => {
            gridDataStateChangeEvent(event.dataState);
            setDataState(event.dataState);
        }

        const selectNavEvent = hooks.useCustomEvent<SelectNavEvent>("selectNavEvent",
            { detail: {navParameterField: "navParameterField", endpoint: "endpoint"} }
        );

        let onSelectEvent = null;
        let selectableSettings: GridSelectableSettings = {
            enabled: false
        };
        if (selectable && selectNavigation != null) {
            selectableSettings = {
                enabled: true,
                mode: "single"
            }

            onSelectEvent = (event: GridSelectionChangeEvent) => {
                selectNavEvent(
                    {navParameterField: event.startDataItem[selectNavigation.navParameterField], endpoint: selectNavigation.endpoint}
                );
            };
        }

        return (
            <Grid
                style={kendoStyle}
                data={gridData}
                pageable={pageableSettings}
                sortable={sortable}
                filterable={filterable}
                {...dataState}
                total={total}
                selectable={selectableSettings}
                onSelectionChange={onSelectEvent}
                onDataStateChange={dataChange}
            >
                {this.prepareToolbarRender(toolbarItems, hooks)}
                {this.prepareColumnsRender(columnDefinitions)}
            </Grid>
        );
    }

    protected prepareToolbarRender(toolbarItems: Array<ToolbarItem>, hooks: RenderHooks): any {
        const toolbarNavEvent = hooks.useCustomEvent<string>("toolbarNavEvent",
            { detail: "endpoint" }
        );

        let toolbarItemsRender: any = null;
        if (toolbarItems != undefined) {
            toolbarItemsRender = toolbarItems.map(((value) => {
                if (value.endpoint != null)
                    return (
                        <Button onClick={() => { toolbarNavEvent(value.endpoint); }}>
                            {value.title}
                        </Button>
                    );
                else {
                    const toolbarSpecialEvent = hooks.useCustomEvent<string>(value.eventName);
                    return (
                        <Button onClick={() => {
                            toolbarSpecialEvent();
                        }}>
                            {value.title}
                        </Button>
                    );
                }
            }));
            toolbarItemsRender =
                <GridToolbar>
                    {toolbarItemsRender}
                </GridToolbar>;
        }
        return toolbarItemsRender;
    }

    protected prepareColumnsRender(columnDefinitions: Array<ColumnDefinition>): any {
        let columnsRender: any = null;
        if (columnDefinitions != undefined) {
            columnsRender = columnDefinitions.map(((columnDefinition) => {
                if (columnDefinition.field == "enabled") {
                    return <Column
                        field={columnDefinition.field}
                        title={columnDefinition.title}
                        hidden={columnDefinition.hidden}
                        filterable={columnDefinition.filterable}
                        filter={columnDefinition.filter.toLowerCase()}
                        sortable={columnDefinition.sortable}
                        format={columnDefinition.format}
                        cells={{data: props => {
                                return <td {...props.tdProps} colSpan={1}>
                                    {props.children == 'true' ? 'Yes' : 'No'}
                                </td>;
                            }}}
                    />;
                }
                else {
                    return <Column
                        field={columnDefinition.field}
                        title={columnDefinition.title}
                        hidden={columnDefinition.hidden}
                        filterable={columnDefinition.filterable}
                        filter={columnDefinition.filter.toLowerCase()}
                        sortable={columnDefinition.sortable}
                        format={columnDefinition.format}
                    />;
                }
            }));
        }

        return columnsRender;
    }
}

customElements.define('kendo-grid', KendoGridElement);

To try and roughly explain the above, I'm using the new Vaadin ReactAdapterComponent and using the RenderHooks to feed configuration details from the server to the typescript. The values are set to the desired attributes of the Grid and Column objects and everything is passed back for render to the page. Up to now, everything has worked great, but as you can see most everything has just been a case of setting attributes directly, or utilizing callbacks to handle data processing. Trying to provide custom templating to a cell is where I'm hitting a wall.

If I was directly utilizing Typescript files in my application, this would be very simple. I've already done a proof of concept, which you can see in the above tsx, in the 'prepareColumnsRender' function:

<Column
    field={columnDefinition.field}
    title={columnDefinition.title}
    hidden={columnDefinition.hidden}
    filterable={columnDefinition.filterable}
    filter={columnDefinition.filter.toLowerCase()}
    sortable={columnDefinition.sortable}
    format={columnDefinition.format}
    cells={{data: props => {
            return <td {...props.tdProps} colSpan={1}>
                {props.children == 'true' ? 'Yes' : 'No'}
            </td>;
        }}}
/>

The 'cells' attribute is what handles the templating for customized column grid cells. Just check the value of the boolean (handled as a string in function, apparently), and return "Yes" or "No" as needed.

My problem is that all of the above code is in a separate project that I'm adding as a Maven dependency to my primary application. So, in my actual applications, I can't modify the .tsx. It's part of the dependency. And I'm utilizing Vaadin in order to have highly dynamic presentations.

So, what I'm trying to do is add a new 'template' value for the ColumnDefinitions, and then assign this to the 'cells' attribute at runtime.

I have tried the following, all without any success. Sometimes with it being unable to even compile. The last String argument in the following snippets are the 'template' attribute of my ColumnDefinition:

kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "enabled", "Enabled", false, null, true, KendoGrid.ColumnFilterType.BOOLEAN, true,
            "props => { return <td {...props.tdProps} colSpan={1}> {props.children == 'true' ? 'Yes' : 'No'} </td>; }"
        ));
<Column
    field={columnDefinition.field}
    title={columnDefinition.title}
    hidden={columnDefinition.hidden}
    filterable={columnDefinition.filterable}
    filter={columnDefinition.filter.toLowerCase()}
    sortable={columnDefinition.sortable}
    format={columnDefinition.format}
    cells={{data: columnDefinition.template}}
/>
kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "enabled", "Enabled", false, null, true, KendoGrid.ColumnFilterType.BOOLEAN, true,
            "{{data: props => { return <td {...props.tdProps} colSpan={1}> {props.children == 'true' ? 'Yes' : 'No'}</td>; }}}"
        ));
<Column
    field={columnDefinition.field}
    title={columnDefinition.title}
    hidden={columnDefinition.hidden}
    filterable={columnDefinition.filterable}
    filter={columnDefinition.filter.toLowerCase()}
    sortable={columnDefinition.sortable}
    format={columnDefinition.format}
    cells={columnDefinition.template}
/>
kendoGrid.addColumnDefinition(new KendoGrid.ColumnDefinition(
            "enabled", "Enabled", false, null, true, KendoGrid.ColumnFilterType.BOOLEAN, true,
            "<td {...props.tdProps} colSpan={1}> {props.children == 'true' ? 'Yes' : 'No'} </td>"
        ));
<Column
    field={columnDefinition.field}
    title={columnDefinition.title}
    hidden={columnDefinition.hidden}
    filterable={columnDefinition.filterable}
    filter={columnDefinition.filter.toLowerCase()}
    sortable={columnDefinition.sortable}
    format={columnDefinition.format}
    cells={{data: props => {
        return columnDefinition.template;
    }}}
/>

None of it is working, and I do understand the root problem, but I'm at a loss of how to address it - the columnDefinition.template is a String. In the very last case, the actual code snippet is displayed in the column. I tried to modify my ColumnDefinition interface in the tsx to 'template: any' and 'template: Function', but it doesn't change the fact that a String is being provided from the server. I have tried to use 'eval(columnDefinition.template)' and 'new Function(columnDefinition.template)', but neither works. I'm honestly new to typescript, so I'm not sure if it's because those are not available outside of JS, or if I'm using those incorrectly. I did attempt to change my file from tsx to jsx, but that failed entirely. Seems that the Vaadin React Adapter can only function in tsx.

The long and short of my problem is that it looks like I need to receive a String from the server, and then convert that into either a full JS lambda function, or a code snippet that will work in the scope of an existing function. I'm not even sure that something like this is possible. I'm getting close to resigning myself to just creating a series of "prefab" template functions, and simply passing an enum value that states which one I'd like to have applied. I know that will work, but I'm really hoping to find a way of maintaining full control over the templating via my Java code.

4
  • I do not have now time to setup a project with all your code as it is pretty complex, but I just have one hint in my mind. Is it possible in your case to use useRef hook for the cell, so that you can set the innerHtml property of the element with the template value?
    – Tatu Lund
    Commented Nov 18, 2024 at 9:13
  • Did you have success with using useRef, if yes, could you answer your question here for the benefit of the community?
    – Tatu Lund
    Commented Nov 25, 2024 at 7:20
  • No, I ultimately just went with prefabs and passed an enum value instead. I don't like it, but it's been serving its purpose well enough. In all honesty, I'm not entirely sure what you mean by useRef. Commented Feb 7 at 20:33
  • Ok, please describe your solution in the answer to this question so that it is not left unanswered and open. In SO it is fully ok to answer your own questions.
    – Tatu Lund
    Commented Feb 9 at 9:37

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.