DEV Community

Cover image for How to make a custom form item in React Hook Form
The Teabag Coder
The Teabag Coder

Posted on

How to make a custom form item in React Hook Form

Prerequisite

  • Basic knowledge of React
  • Basic knowledge of React Hook Form

What is a custom form item?

  • A combination of one or more form items and their values
  • Required value and onChange as props

How to make a custom form item?

In this article we will make a select of dog breeds and show a picture of that breed called DogSelect

Install react-hook-form

  • npm i react-hook-form

In your project, create a file called dog-select.tsx, DogSelect will have

  • When mounted, it will fetch a list of dog breeds as option.
  • When select a breed, it will fetch a random dog picture of that breed.
  • When select a breed, the form will validate if that breed is corgi or not
  • value is an object with breed is the name and imgUrl

Ignore the forwardRef and ref as we don't use them in this example.

import { forwardRef, useEffect, useState } from "react";

export type TDogSelectValue = {
    breed: string;
    imgUrl: string;
};

type TDogSelectProps = {
    value: TDogSelectValue;
    onChange: Function;
};

interface IDogApiResponse {
    message: Object | Array<string> | string;
}

export const DogSelect = forwardRef<HTMLDivElement, TDogSelectProps>(({ value, onChange }: TDogSelectProps, ref) => {
    const [breeds, setBreeds] = useState<string[]>([]);
    
    useEffect(() => {
        fetchAllBreeds().then((res) => setBreeds(res));
    }, []);

    const handleOnSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
        const selectedBreed = e.target.value;
        fetchRandomDogImgByBreed(selectedBreed).then((imgUrl) => {
            const newValue: TDogSelectValue = {
                breed: selectedBreed,
                imgUrl,
            };
            onChange(newValue);
        });
    };

    return (
        <div className="dog-selector" ref={ref}>
            <div style={{ display: "flex", flexDirection: "column", width: "300px" }} ref={ref}>
                <option disabled value="pick a breed">
                    pick a breed
                </option>
                {breeds.map((b) => (
                    <option key={b} value={b}>
                        {b}
                    </option>
                ))}
            </select>
            {value.imgUrl && <img src={value.imgUrl} alt="dog picture" />}
        </div>
    );
});

const fetchAllBreeds = async () => {
    const res = await fetch("https://dog.ceo/api/breeds/list/all");
    const resJson: IDogApiResponse = await res.json();
    return Object.keys(resJson.message);
};

const fetchRandomDogImgByBreed = async (breed: string) => {
    const res = await fetch(`https://dog.ceo/api/breed/${breed}/images/random`);
    const resJson: IDogApiResponse = await res.json();
    return resJson.message as string;
};
Enter fullscreen mode Exit fullscreen mode

Use DogSelect in react-hook-form in App.tsx

  • When we select a new breed or press the submit button the form will be validated (mode: "onChange")
  • If the selected breed is not corgi it will show an error Wrong dog breed
  • If submit with no error an alert will pop out with breed name and image's URL.
import { useForm, Controller, SubmitHandler } from "react-hook-form";
import { DogSelect, TDogSelectValue } from "./components/dog-select";

type TFormValues = {
    DogPicture: TDogSelectValue;
};

export default function App() {
    const {
        handleSubmit,
        control,
        formState: { errors },
    } = useForm<TFormValues>({
        defaultValues: {
            DogPicture: {
                breed: "pick a breed",
                imgUrl: "",
            },
        },
        mode: "onChange",
    });
    
    const onSubmit: SubmitHandler<TFormValues> = (data) => {
        alert(`Submit succeeded!! breed: ${data.DogPicture.breed} url: ${data.DogPicture.imgUrl}`);
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <Controller
                name="DogPicture"
                control={control}
                rules={{
                    validate: (value: TDogSelectValue) => {
                        return value.breed === "corgi";
                    },
                }}
                render={({ field }) => <DogSelect {...field} />}
            />

            {errors.DogPicture?.type === "validate" && (
                <p role="alert" style={{ color: "red" }}>
                    Wrong dog breed
                </p>
            )}
            <input type="submit" />
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

That's it! Thank you for reading this far. Till next time!

Source code: https://github.com/TheTeabagCoder/react-hook-form-custom-form-item-demo

Top comments (0)