Streamlining Your React App with Custom Hooks

Jimoh Sherifdeen / May 30, 2024

7 min read

This has become a habit for me. Anytime I start a project or build a feature, this is my approach, and I am glad to share this with you all in this article. Creating custom hooks to encapsulate related logic can significantly enhance the clarity and reusability of your code. In this article we will be using forms as a use case, since creating forms is a common thing in a React app, the principles of custom hooks can be applied broadly across your React app.

Why Custom Hooks?

React hooks provide a powerful mechanism to abstract and manage stateful logic in functional components. Custom hooks take this further by allowing you to encapsulate related logic into reusable functions. Whether managing forms, fetching data, handling authentication, or any other stateful operation, custom hooks can simplify your codebase.

Benefits of Custom Hooks

1. Separation of Concerns: By moving specific logic into custom hooks, you keep your components clean and focused on rendering. This separation of concerns makes your codebase easier to navigate and understand.

2. Reusability: Once you've created a custom hook, you can reuse it across multiple components. This promotes DRY (Don't Repeat Yourself) principles and ensures consistency across your application.

3. Testability: Isolating logic in a custom hook makes it easier to test independently of the components that use it. You can write unit tests for the hook to ensure that your logic behaves as expected.

4. Maintenance: Having a single source of truth for your logic simplifies maintenance. If you need to update how a particular operation is handled, you can do so in one place without having to touch multiple components.

Example: useForm Hook

Let’s dive into an example of a useForm hook that handles all form-related logic.

Step 1: Create the Hook

import { useState } from 'react'; function useForm(initialState, onSubmit) { const [formData, setFormData] = useState(initialState); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const handleChange = (e) => { }; const handleSubmit = async (e) => {}; const handleFormUpdate = (newData) => {}; return { formData, isSubmitting, error, handleChange, handleSubmit, handleFormUpdate }; } export default useForm;

Step 2: Use the Hook in a Component

Now, let's see how we can use this hook in a component.

import React from 'react'; import useForm from './useForm'; const ContactForm = () => { const initialState = { name: '', email: '', message: '', }; const onSubmit = async (data) => {}; const { formData, isSubmitting, error, handleChange, handleSubmit,} = useForm(initialState, onSubmit); return; ()}; export default ContactForm;

Explanation

  • Initial State: The useForm hook takes an initial state object and an onSubmit function as parameters. This allows you to define the initial values of your form fields and the logic to handle the form submissions.

  • Form State Management: The hook manages the form data (formData), submission state (isSubmitting), and any errors (error). The handleChange function updates the form data whenever an input changes, while handleSubmit handles form submission and handleUpdateData handles data updating.

  • Component Usage: In the ContactForm component, we utilize the useForm hook to manage the form state and submission logic. This keeps the component clean and focused on rendering the form.

  • Form Submission Handling: The handleSubmit function prevents the default form submission behavior, sets the submission state, and invokes the onSubmit the function passed to the hook. It handles any errors and resets the submission state once the process is complete.

  • Error Handling: If an error occurs during form submission, the error message is captured and displayed to the user, providing clear feedback.

By using the useForm hook, we encapsulate all form-related logic, making the ContactForm component more readable and maintainable. This approach also allows for easy reuse of the useForm hook across different forms within the application, ensuring consistent behavior and reducing code duplication.

Other Use Cases

The concept of custom hooks can be extended to various use cases beyond forms and data fetching, to mention a few:

- Authentication: A useAuth hook can manage user authentication state and logic. - Pagination: A usePagination hook can handle pagination logic for lists of items. - WebSockets: A useWebSocket hook can manage WebSocket connections and message handling. - Debouncing: A useDebounce hook can manage debounced values for input fields or search queries.

Conclusion

By encapsulating specific logic within custom hooks, you can create more modular, maintainable, and reusable code. This approach not only streamlines your development process but also enhances the overall quality of your codebase. Next time you are building a feature that involves stateful logic, consider creating a custom hook to handle the logic—it might just become a habit for you too.