DEV Community

loading...
Cover image for Creating Custom React Hooks: useForm

Creating Custom React Hooks: useForm

zachsnoek profile image Zach Snoek ใƒป2 min read

When working with forms in React, we typically want to have control over the form's state. React makes this easy with the useState hook, but there's still a bit of code to write on our end. Take the following simple example:

function Form() {
    const [formData, setFormData] = React.useState({
        username: "",
        password: "",
    });

    const { username, password } = formData;

    const handleInputChange = (e) => {
        setFormData({ ...form, [e.target.name]: e.target.value });
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        console.dir(formData);
    }

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                name="username"
                value={username}
                onChange={handleInputChange}
            />
            <input
                type="password"
                name="password"
                value={password}
                onChange={handleInputChange}
            />
            <button type="submit">Submit</button>
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

For one form, this is straightforward and not too taxing on our part. But what if we have lots of forms like this on our site? Re-writing the state management multiple times seems like more work than necessary for us and would probably introduce a lot of mistakes.

Instead, let's convert the state management to a custom hook that we'll call useForm.

Let's start by managing our form state in useForm. The user should be able to pass in the initial state as a parameter:

const useForm = (initialState = {}) => {
    const [formData, setFormData] = React.useState(initialState);

    return { formData };
}
Enter fullscreen mode Exit fullscreen mode

It would also be nice to not have to re-write handleInputChange either, so let's add that to the hook:

const useForm = (initialState = {}) => {
    const [formData, setFormData] = React.useState(initialState);

    const handleInputChange = (e) => {
        setFormData({ ...formData, [e.target.name]: e.target.value })
    }

    return { formData, handleInputChange };
}
Enter fullscreen mode Exit fullscreen mode

Great! Now we can just get handleInputChange from useForm and pass that to our inputs' onChange.

This is what our previous example looks like now with useForm:

function Form() {
    const { formData, handleInputChange } = useForm(
        {
            username: "",
            password: "",
        }
    );

    const { username, password } = formData;

    const handleSubmit = (e) => {
        e.preventDefault();
        console.dir(formData);
    }

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                name="username"
                value={username}
                onChange={handleInputChange}
            />
            <input
                type="password"
                name="password"
                value={password}
                onChange={handleInputChange}
            />
            <button type="submit">Submit</button>
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

Finally, let's return a handleSubmit function from useForm so that we can reuse that logic in our forms' onSubmit. We'll want to call e.preventDefault() to prevent the page from reloading, but it would also be nice if the user could add some custom behavior when the submit handler is called.

Let's add another parameter to useForm: an onSubmit function that takes in the formData. useForm's handleSubmit can take care of preventing the default behavior, then call the user's onSubmit function and pass it the formData.

const useForm = (initialState = {}, onSubmit) => {
    const [formData, setFormData] = React.useState(initialState);

    const handleInputChange = (e) => {
        setFormData({ ...formData, [e.target.name]: e.target.value })
    }

    const handleSubmit = (e) => {
        e.preventDefault();
        onSubmit?.(formData);
    }

    return { formData, handleInputChange, handleSubmit };
}
Enter fullscreen mode Exit fullscreen mode

Here's the final result with our custom onSubmit function passed to useForm:

function Form() {
    const { formData, handleInputChange, handleSubmit } = useForm(
        {
            username: "",
            password: "",
        },
        (formData) => console.dir(formData)
    );

    const { username, password } = formData;

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                name="username"
                value={username}
                onChange={handleInputChange}
            />
            <input
                type="password"
                name="password"
                value={password}
                onChange={handleInputChange}
            />
            <button type="submit">Submit</button>
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

That's it! Thanks to React hooks, we can create nice reusable form data logic that can be used across our app's forms.

Discussion (2)

pic
Editor guide
Collapse
hkhattabii profile image
hkhattabii

Very interesting :) Hooks make developers' life better

Collapse
rafael_guinho profile image
Rafael Guinho

Awersome, congratulations!