Plugin for custom InputType for an object

I am trying to create a plugin for a custom InputType called ‘ComplexLink’. I have the plugin working to handle a single text field but my goal is to be able to capture a more complex input form that could be captured in the form of an object. Much like what you get back from the cloudinaryImageEditor plugin. My problem is that I can’t figure out how to manage the object state. I have tried defining an interface so I can have props.value.input1 and props.value.input2 but that is not working and I am either getting an error or the fields get set to the same value. Can someone look at this code and provide some pointers:

// contents of plugin.jsx
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Builder } from '@builder.io/react';
import ComplexLink from './complexlink';


Builder.registerEditor({
    name: 'ComplexLink',
    component: ComplexLink,
});
//contents of complexlink.tsx
import React from 'react';

// TODO: Add a link component that can be used in the CMS


interface ComplexLinkProps {
    value: {
        input1: string;
        input2: string;
    };
    onChange: (value: { input1: string; input2: string }) => void;
}

const ComplexLink = (props: ComplexLinkProps) => {
    return (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', alignItems: 'center' }}>
            <label style={{ gridColumn: '1 / 2' }}>
                Input 1:
            </label>
            <input style={{ gridColumn: '2 / 3' }} type="text" value={props.value.input1} onChange={e => props.onChange({ ...props.value, input1: e.target.value })} />
            <label style={{ gridColumn: '1 / 2' }}>
                Input 2:
            </label>
            <input style={{ gridColumn: '2 / 3' }} type="text" value={props.value.input2} onChange={e => props.onChange({ ...props.value, input2: e.target.value })} />
        </div>
    );
};

export default ComplexLink;

To test this in the cms, I have just created a structured data model called plugin-poc and then added a filed called links referencing this new input type.

The full code can be found here:

Thanks in advance for your help!

Hello @jhs129,

Could you confirm what is the error you are getting?

Sometimes, the problem might be with the handling of props . Make sure the onChange function passed to the component correctly handles updating the full object. For example:

const handleOnChange = (newValue) => {
  setState(prev => ({
    ...prev,
    someField: newValue.someField,
    anotherField: newValue.anotherField
  }));
}

Could you also add console logs to see what props.value looks like each time onChange is triggered. This can help you verify that the workflow is as expected.

Thanks,

Hi-

I am getting the following error, which as you can see is not very helpful in troubleshooting:

I also am link to my github repo of my project here so you can try and run this locally.

Thanks,
John

Hello @jhs129,

The issue seems to be with your plugin code, I haven’t been able to use your repo to be able to reproduce the issue because of webpack error.

Below is updated version which may help resolve the issue

import React, { useState, ChangeEvent } from 'react';

interface ComplexLinkProps {
    value: string;
    onChange: (value: string) => void;
    defaultType?: string;
}

const ComplexLink: React.FC<ComplexLinkProps> = ({ value, onChange, defaultType = 'model' }) => {
    const [type, setType] = useState(defaultType);

    const handleTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
        setType(e.target.value);
    };

    const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
        onChange(e.target.value);
    };

    return (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', alignItems: 'center' }}>
            <label htmlFor="type">Link Type:</label>
            <select id="type" value={type} onChange={handleTypeChange}>
                <option value="model">Model</option>
                <option value="url">URL</option>
            </select>
            {type === 'url' && (
                <>
                    <label htmlFor="link">Enter Url:</label>
                    <input id="link" type="text" value={value} onChange={handleChange} />
                </>
            )}
        </div>
    );
};

export default ComplexLink;

Hi @manish-sharma, thanks for the update. Your wasn’t quite working as it was always loading with a type = model. I want to update this so that it remembers both the type of the model selection as well as the value of the link. I tried refactoring the code as follows:

import React, { useState, ChangeEvent } from 'react';

interface ComplexLinkProps {
    value: string;
    onChange: (value: string) => void;
    defaultType?: string;
}

const ComplexLink: React.FC<ComplexLinkProps> = ({ value, onChange, defaultType = 'model' }) => {
    const [type, setType] = useState(defaultType);

    const handleTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
        setType(e.target.value);
    };

    const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
        onChange(e.target.value);
    };

    return (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', alignItems: 'center' }}>
            <label htmlFor="type">Link Type:</label>
            <select id="type" value={type} onChange={handleTypeChange}>
                <option value="model">Model</option>
                <option value="url">URL</option>
            </select>
            {type === 'url' && (
                <>
                    <label htmlFor="link">Enter Url:</label>
                    <input id="link" type="text" value={value} onChange={handleChange} />
                </>
            )}
        </div>
    );
};

export default ComplexLink;

Here is a screen shot of how this diff’s from the version you sent.

Unfortunately, this gives me an error in Builder.io that is not very helpful

Can you provide some suggestions for:

1.) How I troubleshoot this error? Unfortunately there is not enough of an error message for me to even know where to look
2.) How might I actually change the code to achieve my desire which is to save a value that is an object so that I can ultimately store complex data coming out of this inputtype.

Finally, here is an updated link to my github project. The latest code is in the develop2 branch:

Thanks,
John

@manish-sharma, i just noticed I copied in the wrong updated code in my earlier thread. Here is the actual updated code that is giving me the error:

import React, { useState, ChangeEvent } from 'react';

interface ComplexLinkProps {
    value: {
        type: string;
        link: string;
    };
    onChange: (value: { type: string; link: string }) => void;
    defaultType?: string;
}

const ComplexLink: React.FC<ComplexLinkProps> = ({ value, onChange, defaultType = 'model' }) => {
    const [type, setType] = useState(value.type || defaultType);

    const handleTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
        const newType = e.target.value;
        setType(newType);
        onChange({ ...value, type: newType });
    };

    const handleLinkChange = (e: ChangeEvent<HTMLInputElement>) => {
        onChange({ ...value, link: e.target.value });
    };

    return (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', alignItems: 'center' }}>
            <label htmlFor="type">Link Type:</label>
            <select id="type" value={type} onChange={handleTypeChange}>
                <option value="model">Model</option>
                <option value="url">URL</option>
            </select>
            {type === 'url' && (
                <>
                    <label htmlFor="link">Enter Url:</label>
                    <input id="link" type="text" value={value.link} onChange={handleLinkChange} />
                </>
            )}
        </div>
    );
};

export default ComplexLink;

Hi @manish-sharma… any update on this? particularly how to debug the error message I am getting? The stack trace is suppressed so I don’t know where to start troubleshooting.

I was able to fix my problem. Key elements were:

1.) adding some try catch blocks around some of the onChange handlers
2.) using the value.get(“propertyName”) method to retrieve elements of the value property rather than trying to access them directly (e.g. use value.get(“link”) vs value.link).

Here is the updated code for others to reference in the future:

import React, { useState, ChangeEvent, useEffect } from 'react';

interface ComplexLinkProps {
    value: {
        type: string;
        link: string;
    };
    onChange: (value: { type: string; link: string }) => void;
    defaultType?: string;
}

const ComplexLink: React.FC<ComplexLinkProps> = ({ value, onChange, defaultType = 'url' }) => {

    // Initialize state directly from value prop
    const [type, setType] = useState<string>(() => {
        try {
            return value.get("type") || defaultType;
        } catch (error) {
            console.error('Error initializing type state:', error);
            return defaultType;
        }
    });
    
    const [link, setLink] = useState<string>(() => {
        try {
            return value.get("link") || '';
        } catch (error) {
            console.error('Error initializing link state:', error);
            return '';
        }
    });

    const [error, setError] = useState<string | null>(null);
    const [debugInfo, setDebugInfo] = useState<string>('');

 

    // Sync effect - runs when value prop changes
    useEffect(() => {
        console.log('Value changed:', value);
        if (value) {
            setType(value.get("type") || defaultType);
            setLink(value.get("link") || '');
        }
    }, [value?.type, value?.link, defaultType]);

    // Debug info update
    useEffect(() => {
        setDebugInfo(JSON.stringify({
            value,
            internalState: { type, link }
        }, null, 2));
    }, [value, type, link]);

    const handleTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
        const newType = e.target.value;
        console.log('Type changed to:', newType);
        setType(newType);
        setError(null);
        
        try {
            const newValue = { link: value.get("link") || '', type: newType };
            console.log('handleTypeChange - sending value:', newValue);
            onChange(newValue);
        } catch (error) {
            console.error('Error in handleTypeChange:', error);
            setError(error instanceof Error ? error.message : 'An error occurred');
        }
    };

    const handleLinkChange = (e: ChangeEvent<HTMLInputElement>) => {
        const newLink = e.target.value;
        console.log('Link changed to:', newLink);
        setLink(newLink);
        setError(null);

        try {
            if (type === 'url' && newLink && !isValidUrl(newLink)) {
                throw new Error('Invalid URL format');
            }
            const newValue = { type, link: newLink };
            console.log('handleLinkChange - sending value:', newValue);
            onChange(newValue);
        } catch (error) {
            console.error('Error in handleLinkChange:', error);
            setError(error instanceof Error ? error.message : 'An error occurred');
        }
    };

    const isValidUrl = (urlString: string): boolean => {
        try {
            new URL(urlString);
            return true;
        } catch {
            return false;
        }
    };

    return (
        <div className="complex-link-container" style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', alignItems: 'center' }}>
            <label htmlFor="type">Link Type:</label>
            <select 
                id="type" 
                value={type} 
                onChange={handleTypeChange}
                className="complex-link-select"
            >
                <option value="url">URL</option>
                <option value="model">Model</option>
            </select>
            
            {(type === 'url' || value?.type === 'url') && (
                <>
                    <label htmlFor="link">Enter URL:</label>
                    <input 
                        id="link" 
                        type="text" 
                        value={link}
                        onChange={handleLinkChange}
                        className="complex-link-input"
                        placeholder="Enter URL..."
                    />
                </>
            )}
            
            {error && (
                <div className="complex-link-error" style={{ gridColumn: '1 / -1', color: 'red' }}>
                    {error}
                </div>
            )}

            {/* Debug information */}
            <div style={{ display: 'flex', gridColumn: '1 / -1', marginTop: '10px', padding: '8px', background: '#f5f5f5', fontSize: '12px' }}>
                <pre>{debugInfo}</pre>
            </div>
        </div>
    );
};

export default ComplexLink;
1 Like