Labs ICT
Pro Login

Custom Hooks

Creating reusable logic with custom hooks.

What Are Custom Hooks?

Custom hooks are functions that start with "use" and can call other hooks. They let you extract component logic into reusable functions. Instead of copying and pasting the same stateful logic between components, you put it in a custom hook and use it anywhere.

Think of custom hooks as recipes. Once you figure out how to do something — fetch data, handle forms, manage animations — you package that logic into a hook. Then any component can use that recipe without rewriting the code.

Creating Your First Custom Hook

Let's create a hook that manages a counter. Instead of writing counter logic in every component, we'll put it in a reusable hook.

// useCounter.js
import { useState } from 'react';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

export default useCounter;

// Using the hook in a component
import React from 'react';
import { View, Text, Button } from 'react-native';
import useCounter from './useCounter';

function CounterComponent() {
  const { count, increment, decrement, reset } = useCounter(10);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text style={{ fontSize: 48 }}>{count}</Text>
      <Button title="+" onPress={increment} />
      <Button title="-" onPress={decrement} />
      <Button title="Reset" onPress={reset} />
    </View>
  );
}

The useCounter hook encapsulates all the counter logic. Components that use it don't need to know how the counter works — they just get the count and functions. You can use this hook in as many components as you want, each with their own independent state.

A Practical Hook: useFetch

Here's a more practical example — a hook that handles data fetching with loading and error states:

// useFetch.js
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    setError(null);

    fetch(url)
      .then(response => {
        if (!response.ok) throw new Error('Network error');
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

// Usage
function UserList() {
  const { data: users, loading, error } = useFetch('https://api.example.com/users');

  if (loading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error}</Text>;

  return (
    <View>
      {users.map(user => <Text key={user.id}>{user.name}</Text>)}
    </View>
  );
}

This hook handles all the boilerplate of data fetching — loading state, error handling, and data management. Every time you need to fetch data, just call useFetch(url). The component stays clean and focused on rendering.

Rules of Custom Hooks

Custom hooks follow the same rules as built-in hooks. They must start with "use" (so React knows they're hooks), and they can only be called from React function components or other hooks. Don't call hooks inside loops, conditions, or regular functions.

The power of custom hooks is that they can call other hooks — useState, useEffect, useContext, and even other custom hooks. This composition lets you build complex logic from simple pieces. Each hook manages one concern, and together they create powerful, reusable functionality.