Search code examples
javascriptreactjstypescriptforms

How to get the value of a custom input component inside a form?


I wrote a small custom component in react-ts that wraps an <input /> with some additional functionality, mainly fuzzy searching through a list of options. I am planning to use this inside a form.

However now that I created the component, I can not retrieve its value inside a <form>, since it is not a html element and has no value attribute that i can read.

I am reading the docs here, but it is not helping me. I also tried googling things like "custom input in react form", "get value from custom react component" etc. but could not find any results, although I am sure this should be a pretty straight-forward thing happening to a lot of people. the closest result I could find was this and the answers did not help me at all.

the component:

export interface SmartInputProps {
    items: any[],
    itemKey: string
    //[...]
}

export const SmartInput: React.FC<SmartInputProps> = ({
    items,
    itemKey = 'name'
    //[...]
}: SmartInputProps) => {

    // [...]
    // (lots of funcionality)

    return (
        <div>
            <input /*[...]*/ />
            //some more visual stuff, like displaying list of results
        </div>
    );
}

And the form I want to use the component in

interface Props {
    className?: string
}

export const SearchBar: React.FC<Props> = () => {
    // [...]
    
    return (
        <div className="searchbar-wrapper">
            <form onSubmit={handleSubmit}>
                <SmartInput
                    items={options}
                    itemKey='name' 
                    //[...]
                />
                <input type='submit' value='OK' />
            </form>
        </div>
    );
}

I would like to access the current value of the <input /> that is inside <SmartInput /> in the form.

I tried using hacky ways of getting the value, such as delegating the onChange of the <input /> upwards (as proposed here), but that only allows me to get and store the value whenever onChange is triggered, not read it at submit-time. This workaround does allow me to use the value at submit-time but I have to handle every single onChange first which is sub-optimal.


Solution

  • I found a better solution using useRef which is also more elegant.

    Using the initial examples:

    interface Props {
        className?: string
    }
    
    export const SearchBar: React.FC<Props> = () => {
        const inputRef = useRef<HTMLInputElement>(null); //<- here
        // [...]
        
        return (
            <div className="searchbar-wrapper">
                <form onSubmit={handleSubmit}>
                    <SmartInput
                        items={options}
                        itemKey='name'
                        ref={inputRef}   //<- here
                        //[...]
                    />
                    <input type='submit' value='OK' />
                </form>
            </div>
        );
    }
    

    We add a useRef with the input type interface to the component. then we pass it to the custom component <SmartInput /> as a prop.

    On the side of the receiving component:

    export interface SmartInputProps {
        items: any[],
        itemKey: string,
        ref: MutableRefObject<HTMLInputElement> //<- here
        //[...]
    }
    
    export const SmartInput: React.FC<SmartInputProps> = ({
        items,
        itemKey = 'name',
        ref, //<- here
        //[...]
    }: SmartInputProps) => {
    
        // [...]
        // (lots of funcionality)
    
        return (
            <div>
                <input ref={ref} /*<- here*/ /*[...]*/ />
                //some more visual stuff, like displaying list of results
            </div>
        );
    }
    

    We add the ref to the custom component props and pass it to the ref attribute of the vanilla html input field inside our custom component.

    No we can query the field state from the root ref object.

    e.g. let fieldValue = inputRef.current.value.