Search code examples
javascriptreactjsreact-hooksreact-hook-formchakra-ui

Why does React Hook Form think field not filled in with Chakra UI?


I have built an autocomplete input in React using Chakra UI for styling. I also use react-hook-form to handle the form submitting hooks.

When I fill in the form using the Autocomplete I can successfully select an option from the drop down. However when submitting the form, React-Hook-Form seems to think I haven't filled in the fields because nothing gets submitted and the onSubmit call does not get called.

I pass the ref along to the input with forwardRef which should be all it takes to register these fields.


here is a sandbox to make debugging easier - note there is no log called from the submit nor any errors from RHF - one of the two should appear - https://codesandbox.io/s/chakra-hook-fail-49ppb


The form code is:

const test: AutoCompleteSuggestions[] = [
    {key: "US", value: "USA"},
    {key: "UK", value: "United Kingdom"},
]
function onSubmit(values: QueryFormParams) {
    console.log('V'); //Never gets called
}
return (

                <form onSubmit={handleSubmit(onSubmit)}>
                    <FormControl isInvalid={errors.name}>
                                <AutoComplete
                                    {...register('selectedDeparture', {
                                        required: 'Select a departure',
                                    })}
                                    placeholder={'Departure country...'}
                                    isValid={v => setIsItemSelected(v)}
                                    suggestions={test}
                                />
                    </FormControl>
              </form>
)

The autocomplete code is as follows:

import React, {Ref, useState} from 'react';
import {Input, Table, Tbody, Td, Tr, VStack} from '@chakra-ui/react';

type AutoCompleteProps = {
    suggestions: AutoCompleteSuggestions[]
    isValid: (b: boolean) => void
    placeholder: string
}

export type AutoCompleteSuggestions = {
    key: string,
    value: string
}

// eslint-disable-next-line react/display-name
export const AutoComplete = React.forwardRef(({suggestions, isValid, placeholder}: AutoCompleteProps, ref: Ref<HTMLInputElement>) => {
    
    const [filteredSuggestions, setFilteredSuggestions] = useState<AutoCompleteSuggestions[]>();
    const [showSuggestions, setShowSuggestions] = useState<boolean>(false);
    const [userInput, setUserInput] = useState<string>('');

    function onChange(e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
        console.log('Changing', userInput, e.currentTarget.value);
        const newUserInput = e.currentTarget.value;
        if (!newUserInput) {
            //setActiveSuggestion(-1);
            setFilteredSuggestions([]);
            setShowSuggestions(false);
            isValid(true);
            setUserInput(e.currentTarget.value);
        }
        const filteredSuggestions = suggestions.filter(suggestion => suggestion.value.toLowerCase().startsWith(userInput.toLowerCase()));
        //setActiveSuggestion(e.target.innerText);
        setFilteredSuggestions(filteredSuggestions);
        setShowSuggestions(true);
        isValid(false);
        setUserInput(e.currentTarget.value);
    }

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    function onClick(e: MouseEvent<HTMLLIElement, MouseEvent>) {
        console.log('Clicked', e.target.innerText);
        setFilteredSuggestions([]);
        setShowSuggestions(false);
        isValid(true);
        setUserInput(e.target.innerText);
    }

    let suggestionsListComponent;

    if (showSuggestions && userInput) {
        if (filteredSuggestions?.length) {
            suggestionsListComponent = (
                <Table className={'suggestions'} position={'absolute'} top={10} left={0} right={0} variant='simple' zIndex={999}>
                    <Tbody>
                        {filteredSuggestions?.map((suggestion, index) => {
                            return (
                                <Tr key={index}
                                    _hover={{
                                        background: 'gray.200',
                                        color: 'green',
                                    }}
                                    onClick={onClick}>
                                    <Td>
                                        <span className={'selectedText'}>{suggestion.value}</span>
                                    </Td>
                                </Tr>
                            );
                        })}
                    </Tbody>
                </Table>
            );
        } else {
            suggestionsListComponent = (
                <div className="no-suggestions">
                    <em>No countries found for that input!</em>
                </div>
            );
        }
    }

    return (
        <>
            <VStack position={'relative'}>
                <Input
                    ref={ref}
                    type="text"
                    onChange={onChange}
                    value={userInput}
                    placeholder={placeholder}
                />
                {suggestionsListComponent}
            </VStack>
        </>
    );
});

export default AutoComplete;

Solution

  • I solved this by adding a Controller

                                    <Controller
                                        control={control}
                                        name="selectedDeparture"
                                        rules={{required: true}}
                                        render={({field: {onChange, value}}) => (
                                            <AutoComplete
                                                onChange={(event, item) => {
                                                    onChange(item);
                                                }}
                                                placeholder={'Departure country...'}
                                                isValid={v => setIsItemSelected(v)}
                                                suggestions={countriesMap}
                                            />
                                        )}
                                    />