Search code examples
react-selectreact-hook-form

How to create a dynamic form with custom components?


I'm using version 7 of the library, but I'm having a problem in two situations:

  1. I need to dynamically render the Controller, because the form is assembled with data from the backend response;

  2. I'm trying to create a specific component for the Controller and reuse it inside forms;

I'm trying to do it this way, but it's probably wrong:

ControllerComponent

import { Control, Controller, FieldValues } from 'react-hook-form';
import Select from '../../core/Select';

interface IProps {
    name: string;
    tabIndex: string;
    // eslint-disable-next-line @typescript-eslint/ban-types
    control: Control<FieldValues, object>;
    isDisabled: boolean;
    placeholder: string;
    options: [
        {
            label: string;
            value: string;
        }
    ];
}

const SelectControllerForm = ({ name, control, ...props }: IProps) => (
    <Controller
        name={name}
        control={control}
        render={({ field, fieldState: { error } }) => (
            <Select required={true} isClearable={true} error={!!error} {...field} {...props} />
        )}
    />
);

FormComponent

const { register, control } = useForm({
        resolver: yupResolver(schema),
    });

const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
        control, // control props comes from useForm (optional: if you are using FormContext)
        name: 'test', // unique name for your Field Array
        keyName: 'key', //default to "id", you can change the key name
    });

    const onSubmit = (data: any) => {
        console.log('AQUI');
        console.log(data);
    };

    useEffect(() => {
        append({ firstName: 'bill', lastName: 'luo' });
        append({ a: 'oi', b: 'aq' });
    }, []);

return (
<form onSubmit={}>
    {fields.map((field, index) => (
                        <SelectControllerForm
                            key={field.key}
                            name={index.toString()}
                            control={control}
                            tabIndex='-1'
                            isDisabled={false}
                            placeholder='ae'
                            options={[
                                {
                                    label: 'string',
                                    value: 'string',
                                },
                            ]}
                        />
                    ))}
</form>
)

It's updating the screen all the time and giving REF error:


Solution

  • There are couple of problems with your code:

    1- You can not call actions one after another. Actions need to be triggered per render, so you need to change your apped like this:

    useEffect(() => {
      append([
        { value: 'a', label: 'a' },
        { value: 'b', label: 'b' },
      ]);
    }, []);
    

    2- react-hook-form does not support flat field array, so you need to set the name of fields as nested field of arrayField, like this:

    {fields.map((field, index) => (
        <SelectControllerForm
          key={field.key}
          name={`test.${index.toString()}`}
          control={control}
          ...
        />
      ))}
    

    I hope theses changes solve your problem, if it doesn't helpe, it will be better to include your code on a Code sandbox example and link it here.