JavaScript

Custom Hooks in React: Creating Reusable Logic with Examples

React’s custom hooks are a powerful feature that allows developers to encapsulate and reuse logic across components. By creating custom hooks, you can avoid code duplication, improve readability, and make your components more focused on rendering rather than logic. In this article, we’ll explore how to create custom hooks, with practical examples like useFetchuseLocalStorage, and useAuth. We’ll also discuss best practices and provide useful resources for further learning.

1. What Are Custom Hooks?

Custom hooks are JavaScript functions that start with the prefix use and can call other hooks (e.g., useStateuseEffect). They allow you to extract component logic into reusable functions. Custom hooks follow the same rules as built-in hooks:

  1. They must start with the word use.
  2. They can only be called at the top level of a functional component or another hook.

2. Why Use Custom Hooks?

Custom hooks offer several benefits:

  1. Reusability: Share logic across multiple components.
  2. Separation of Concerns: Keep components focused on rendering.
  3. Readability: Simplify complex components by moving logic into hooks.
  4. Testability: Isolate logic for easier testing.

3. Examples of Custom Hooks

1. useFetch: Fetching Data

A common use case is fetching data from an API. Instead of writing the same logic in multiple components, you can create a useFetch hook.

Implementation

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { useState, useEffect } from 'react';
 
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };
 
    fetchData();
  }, [url]);
 
  return { data, loading, error };
}

Usage

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function App() {
  const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
 
  return (
    <ul>
      {data?.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

2. useLocalStorage: Persisting Data

Another common use case is persisting data in the browser’s local storage. The useLocalStorage hook simplifies this process.

Implementation

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { useState } from 'react';
 
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
 
  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
 
  return [storedValue, setValue];
}

Usage

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function App() {
  const [name, setName] = useLocalStorage('name', 'John Doe');
 
  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>Hello, {name}!</p>
    </div>
  );
}

3. useAuth: Managing Authentication

Managing authentication state is a common requirement in applications. The useAuth hook can handle login, logout, and user state.

Implementation

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import { useState } from 'react';
 
function useAuth() {
  const [user, setUser] = useState(null);
 
  const login = (userData) => {
    setUser(userData);
    localStorage.setItem('user', JSON.stringify(userData));
  };
 
  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };
 
  return { user, login, logout };
}

Usage

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
function App() {
  const { user, login, logout } = useAuth();
 
  return (
    <div>
      {user ? (
        <div>
          <p>Welcome, {user.name}!</p>
          <button onClick={logout}>Logout</button>
        </div>
      ) : (
        <button onClick={() => login({ name: 'John Doe' })}>Login</button>
      )}
    </div>
  );
}

4. Best Practices for Custom Hooks

Custom hooks in React are a powerful way to encapsulate and reuse logic across components. They help keep your code DRY (Don’t Repeat Yourself) and make your components more focused and readable. However, to ensure your custom hooks are effective and maintainable, it’s important to follow best practices. Below, we’ll outline these best practices in a table format for easy reference, along with necessary implementations.

Here’s a table summarizing the best practices for creating and using custom hooks:

Best PracticeDescriptionExample
Prefix with useAlways start the hook name with use to follow React’s naming conventions.useFetchuseLocalStorageuseAuth
Single ResponsibilityEach hook should handle one specific piece of logic.useFetch for data fetching, useLocalStorage for local storage management.
Document the HookAdd comments or documentation to explain the purpose and usage of the hook.Include a description of inputs, outputs, and side effects.
Test Custom HooksTest hooks in isolation using libraries like React Testing Library.Write unit tests for useFetch to ensure it handles loading and errors.
Avoid OveruseOnly create custom hooks when the logic is reusable across multiple components.Avoid creating hooks for one-off logic.
Handle Edge CasesEnsure your hook handles edge cases like errors, null values, or missing data.In useFetch, handle network errors and invalid responses.
Keep Hooks SmallBreak down complex logic into smaller, reusable hooks.Split useAuth into useLogin and useLogout if needed.
Use Dependency ArraysProperly use dependency arrays in useEffect to avoid unnecessary re-renders.In useFetch, include url in the dependency array.

5. Useful Resources

  1. React DocumentationCustom Hooks
  2. useHooks: A collection of reusable custom hooks (useHooks.com)
  3. React Testing LibraryTesting Custom Hooks
  4. Baeldung TutorialsCreating Custom Hooks
  5. GitHub Repositories: Explore open-source custom hooks on GitHub for inspiration.

By leveraging custom hooks, you can write cleaner, more maintainable React code. Whether you’re fetching data, managing local storage, or handling authentication, custom hooks provide a flexible and reusable solution. Start creating your own hooks today and take your React development to the next level!

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button