Home > front end >  Why can't I focus on the current field when using useFieldArray() for dynamic form in react-hoo
Why can't I focus on the current field when using useFieldArray() for dynamic form in react-hoo

Time:05-07

Using react-hook-form 7.14.2. I've got a dynamic form and I'm using the built-in validation. I'm getting the feeling that react-hook-form can't handle dynamic forms very elegantly, and I should probably just quit churning on this, and fall back to a plain HTML form.

I simply want to focus to stay on the field I'm typing in, after validation occurs. The fields are required, but after the validation message shows, and I begin typing, a re-render happens and focus is lost on the current field...and it jumps back to the last field in the form. It's a clunky, poor user experience and I just can't seem to find the magic combo of tricks and techniques to get it to work like a normal react-hook-form form would work!

Form:

import React, { useEffect } from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { Icon } from "@iconify/react";

import socials from "../../enum/social";

const SocialLinksEdit = props => {
    const {
        socialLinks,
        onSave,
        onCancel
    } = props;

    const { control, register, setValue, setFocus, handleSubmit, formState: { errors } } = useForm();

    const { fields, append } = useFieldArray({
        control,
        name: "socialItems"
    });

    useEffect(() => {
        Object.entries(socials).find(([, s]) => append(s));
    }, []);

    const getErrorMessage = i => {
        if (!errors || Object.keys(errors).length === 0) {
            return;
        }
        if (errors.socialItems && errors.socialItems[i]) {
            return errors.socialItems[i].url.message;
        }
        return null;
    };

    const SocialItems = () => {
        const onFocus = field => {
            // setValue(e.target.value);
            setFocus(field);
        };
        const onFocusOut = e => {
            setValue(e.target.value);
        };
        return (
            <>
                {
                    fields.map((field, i) => {
                        let socialData = socialLinks.find(sl => sl.social_service === field.name);
                        console.log(errors);
                        return (
                            <div key={i} className="modal-input-field modal-input-mb">
                                <div className="modal-label-container">
                                    <label className="modal-label">
                                        <span className="modal-label-icon"><Icon icon={field.icon} /></span>
                                        {field.label}
                                    </label>
                                </div>
                                <input
                                    type="text"
                                    // autoFocus={i === 0}
                                    ref={control}
                                    autoFocus
                                    // onFocus={() => onFocus(`socialItems.${i}.url`)}
                                    // onFocusOut={onFocusOut}
                                    {...register(`socialItems.${i}.url`, { required: "URL is required!" })}
                                    defaultValue={socialData?.url}
                                />
                                {errors && <span className="validation">{getErrorMessage(i)}</span>}
                            </div>
                        );
                    })
                }
            </>
        );
    };

    return (
        <div className="modal-container">
            <div>
                <form onSubmit={handleSubmit(onSave)}>
                    <div className="modal-title-container">
                        <div className="modal-title-icon"><Icon icon="bi:megaphone" /></div>
                        <div className="modal-title">Social Links</div>
                    </div>
                    <SocialItems />
                    <div className="modal-buttons">
                        <button type="submit" className="form-button-primary">Save</button>
                        <button type="button" className="form-button-cancel" onClick={e => onCancel(e)}>Cancel</button>
                    </div>
                </form>
            </div>
        </div>
    );
};

export default SocialLinksEdit;

Some 'splainin' to do:

  • I left a bunch of commented stuff in there so you can see all the wacky Stack-O suggestions I've tried already - none of them works.
  • I'm appending on load because defaultValues did not work - nothing renders
  • I'm only concerned w/ the "url" field of the object registered/bound on the field
  • The validation message line is commented out because I'm not sure what to do w/ it

Here's a quick video of how it behaves. If I could just get the field to retain focus and not skip down to the bottom field like this, I'd be a happy guy.

https://www.tiktok.com/@zambizzi/video/7093151720624147755?is_from_webapp=1&sender_device=pc&web_id=7039430208454280710

CodePudding user response:

Turns out, was just a matter of nesting the fields directly inside, rather than in a separate component, to prevent a re-render:

return (
    <div className="modal-container">
        <div>
            <form onSubmit={handleSubmit(onSave)}>
                <div className="modal-title-container">
                    <div className="modal-title-icon"><Icon icon="bi:megaphone" /></div>
                    <div className="modal-title">Social Links</div>
                </div>
                {
                    fields.map((field, i) => {
                        let socialData = socialLinks.find(sl => sl.social_service === field.name);
                        console.log(errors);
                        return (
                            <div key={i} className="modal-input-field modal-input-mb">
                                <div className="modal-label-container">
                                    <label className="modal-label">
                                        <span className="modal-label-icon"><Icon icon={field.icon} /></span>
                                        {field.label}
                                    </label>
                                </div>
                                <div>
                                    <input
                                        type="text"
                                        autoFocus
                                        {...register(`socialItems.${i}.url`, { required: "URL is required!" })}
                                        defaultValue={socialData?.url}
                                    />
                                    {errors && <span className="validation">{getErrorMessage(i)}</span>}
                                </div>
                            </div>
                        );
                    })
                }
                <div className="modal-buttons">
                    <button type="submit" className="form-button-primary">Save</button>
                </div>
            </form>
        </div>
    </div>
);
  • Related