DEV Community

Cover image for Creating Custom React Hooks: useForm
Zach Snoek
Zach Snoek

Posted on • Updated on

Creating Custom React Hooks: useForm

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.


Let's connect

If you liked this post, come connect with me on Twitter, LinkedIn, and GitHub! You can also subscribe to my mailing list and get the latest content and news from me.

Latest comments (2)

Collapse
 
rafael_guinho profile image
Rafael Guinho

Awersome, congratulations!

Collapse
 
hkhattabii profile image
hkhattabii • Edited

Very interesting :) Hooks make developers' life better