DEV Community

Cover image for Custom Hooks and Logic Encapsulation in React
Ifeanyi Emmanuel
Ifeanyi Emmanuel

Posted on • Originally published at Medium

Custom Hooks and Logic Encapsulation in React

In React development, making code that’s efficient and reusable is a top priority. As our applications get more complex, we need a smart way to handle and share our code. This is where custom hooks step in — they’re a powerful way to neatly bundle and distribute logic.

Unleashing the Power of Custom Hooks

Custom hooks are like building blocks that you can use all over your app. They help tidy up your code, make it reusable, and keep different tasks separate, which makes your code easier to understand and maintain.

Streamlined Code Organization

Imagine your app as a big puzzle, with each piece being a different task. Custom hooks help you put those puzzle pieces together smoothly. By bundling specific tasks into hooks, you create clear modules that you can easily use in different parts of your app. This makes your code organized and lets developers work together easily.

Efficient Reusability

Being able to reuse code is a superpower in programming. Custom hooks give you that power. You can wrap up common tasks in a custom hook and use it again and again. So, if you build a useful hook once, you can use it in future projects without starting from scratch. This saves time and keeps your work consistent.

Clear Separation of Concerns

In a symphony, every instrument plays its own part. Custom hooks help with this harmony by keeping different tasks separate. When you put a specific task in a hook, your components can focus on what they do best. It’s like having a composer and a musician — each does their role without stepping on each other’s toes.

Creating Custom Hooks

Now, let’s learn how to make these cool custom hooks that bundle up logic and make our lives easier.

1. Finding Reusable Logic

Before we dive into the nitty-gritty, let’s figure out when a custom hook is the right tool:

Duplicates Everywhere: If you’re copying the same code over and over, it’s custom hook time. Hooks banish repetition, letting you manage logic in one place.

Common Situations:

Hooks are great for common tasks like:

Getting Data: A hook like useFetch can fetch data, handling loading and errors.

State Handling: useStateManager can simplify complex state logic.

Animations: Yep, hooks can even manage animations.

Form Magic: useForm wrangles form state, validation, and submission.

2. Making Your First Custom Hook

Let’s dive in with a simple example. Say you want to toggle a boolean value. Instead of writing this in many places, you’ll make a custom hook called useToggle:

import { useState } from 'react';

const useToggle = (initialValue = false) => {
  const [value, setValue] = useState(initialValue);

  const toggle = () => {
    setValue((prevValue) => !prevValue);
  };

  return [value, toggle];
};
Enter fullscreen mode Exit fullscreen mode

Handling Dependencies Right

Dependencies are like ingredients for your hook’s recipe. Getting them right is crucial, and the useEffect hook is key.

Importance of Dependencies

Dependencies are outside stuff your hook relies on, like other data or services. Nailing these keeps your hook working smoothly.

The useEffect Hook

This is your hook’s best friend. It’s like a watchman — it keeps an eye on things and does stuff when they change.

Perfectly Managed Dependencies

Imagine you’re building a timer. Here’s how useEffect helps:

import { useState, useEffect } from 'react';

const useTimer = (initialDuration, interval) => {
  const [timeRemaining, setTimeRemaining] = useState(initialDuration);

  useEffect(() => {
    const timerId = setInterval(() => {
      setTimeRemaining((prevTime) => Math.max(prevTime - interval, 0));
    }, interval);

    return () => {
      clearInterval(timerId);
    };
  }, [interval]);

  return timeRemaining;
};
Enter fullscreen mode Exit fullscreen mode

Sharing Stateful Logic

Now let’s tackle more complex stuff — managing stateful logic with custom hooks.

Suppose you’re dealing with paginated data fetching. Instead of juggling this in every component, you can use a custom hook called usePagination.

import { useState, useEffect } from 'react';

const usePagination = (initialPage = 1, itemsPerPage = 10, fetchFunction) => {
  const [currentPage, setCurrentPage] = useState(initialPage);
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const startIndex = (currentPage - 1) * itemsPerPage;
        const endIndex = startIndex + itemsPerPage;
        const newData = await fetchFunction(startIndex, endIndex);
        setData(newData);
        setLoading(false);
      } catch (error) {
        setLoading(false);
      }
    };

    fetchData();
  }, [currentPage, itemsPerPage, fetchFunction]);

  const nextPage = () => setCurrentPage((prevPage) => prevPage + 1);
  const prevPage = () => setCurrentPage((prevPage) => Math.max(prevPage - 1, 1));

  return { data, loading, currentPage, nextPage, prevPage };
};
Enter fullscreen mode Exit fullscreen mode

Encapsulating API Calls

Fetching data from APIs? Say hello to the useAPI custom hook.

import { useState, useEffect } from 'react';

const useAPI = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchData = async (url, options = {}) => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch(url, options);
      const responseData = await response.json();
      setData(responseData);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  return { data, loading, error, fetchData };
};
Enter fullscreen mode Exit fullscreen mode

Making Forms

Forms can be a hassle, but not with the useForm custom hook.

import { useState } from 'react';

const useForm = (initialValues, onSubmit, validate) => {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues((prevValues) => ({ ...prevValues, [name]: value }));
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    const formErrors = validate(values);
    setErrors(formErrors);

    if (Object.keys(formErrors).length === 0) {
      setIsSubmitting(true);
      onSubmit(values);
    }
  };

  return {
    values,
    errors,
    isSubmitting,
    handleChange,
    handleSubmit,
  };
};
Enter fullscreen mode Exit fullscreen mode

Reuse in Action

Now, let’s see how we can put these custom hooks to work in real-life scenarios.

1. Authentication Management

For handling user authentication, you can create a custom hook like useAuth. This hook takes care of user sessions, tokens, and login/logout operations, making authentication consistent and secure.

2. Data Fetching and Pagination

Imagine dealing with paginated data fetching. Instead of rewriting the logic everywhere, use the usePagination hook. It fetches data, handles loading, and enables smooth navigation between pages.

3. Internationalization (i18n)

If you need to translate your app into different languages, the useTranslation hook can simplify language switching and provide translated strings, ensuring a seamless multilingual experience.

4. Form Handling and Validation

Forms are often tricky, but not with the useForm hook. It handles form state, user input, and validation, saving you from repetitive code and ensuring consistent form behavior.

Conclusion

Custom hooks are your secret weapon in React development. They help you organize, reuse, and maintain your code effortlessly. With them, you’ll build applications faster, keep your codebase tidy, and create user-friendly interfaces.

Keep Breaking Code Barriers!

Top comments (0)