Skip to main content

Custom Hooks — Reusable Logic

Level: Beginner

ℹ️ What You'll Learn
  • Extracting logic into custom hooks
  • useRef for imperatives and persisting values
  • useReducer for complex state machines
  • useCallback for memoizing functions
  • useMemo for expensive computations
  • useLayoutEffect and other advanced hooks
  • Building reusable SMS hooks (useFetch, useForm)
  • Hook rules and best practices

Why This Matters

Custom Hooks — Reusable Logic is part of building maintainable React applications. You will use it when creating student dashboards, forms, tables, API-connected screens, routing flows, and reusable UI components.

Custom hooks let you extract and reuse component logic. A custom hook is just a JavaScript function using other hooks.

The Problem

Beginners often write React code that works for a small demo but becomes difficult when data, forms, API calls, and reusable components grow. This lesson explains Custom Hooks — Reusable Logic in a way that helps you build predictable UI for real .NET Web API projects.

Creating Your First Custom Hook

Instead of repeating fetch logic:

// ❌ Repeated in every component
function StudentDetail() {
const [student, setStudent] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
fetch(`/api/students/1`)
.then(r => r.json())
.then(data => setStudent(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []);

return { student, loading, error };
}

Extract to a custom hook:

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

useEffect(() => {
fetch(url)
.then(r => r.json())
.then(data => setData(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, [url]);

return { data, loading, error };
}

// Now use everywhere
function StudentDetail() {
const { data: student, loading, error } = useFetch('/api/students/1');

if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return <h1>{student.name}</h1>;
}

function TeacherDetail() {
const { data: teacher, loading } = useFetch('/api/teachers/1');

if (loading) return <p>Loading...</p>;
return <h1>{teacher.name}</h1>;
}

Custom Hook for Form Handling

function useForm(initialValues, onSubmit) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});

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

const handleSubmit = (e) => {
e.preventDefault();
onSubmit(values);
};

return {
values,
errors,
setErrors,
handleChange,
handleSubmit
};
}

// Usage
function StudentForm() {
const { values, handleChange, handleSubmit } = useForm(
{ name: '', email: '' },
(values) => {
console.log('Submitting:', values);
}
);

return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={values.name}
onChange={handleChange}
/>
<input
name="email"
value={values.email}
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
);
}

useRef — Accessing DOM Directly

useRef lets you access DOM elements directly.

function TextInput() {
const inputRef = useRef(null);

const focusInput = () => {
inputRef.current.focus();
};

return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}

// Get value
function StudentSearch() {
const searchRef = useRef(null);

const handleSearch = () => {
const query = searchRef.current.value;
console.log('Searching for:', query);
};

return (
<>
<input ref={searchRef} type="text" placeholder="Search..." />
<button onClick={handleSearch}>Search</button>
</>
);
}

useReducer — Complex State

For complex state with multiple actions, use useReducer:

function useTodoList(initialTodos) {
const [todos, dispatch] = useReducer(todoReducer, initialTodos);

const addTodo = (text) => {
dispatch({ type: 'ADD', payload: text });
};

const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE', payload: id });
};

const deleteTodo = (id) => {
dispatch({ type: 'DELETE', payload: id });
};

return { todos, addTodo, toggleTodo, deleteTodo };
}

function todoReducer(todos, action) {
switch (action.type) {
case 'ADD':
return [...todos, { id: Date.now(), text: action.payload, done: false }];
case 'TOGGLE':
return todos.map(t => t.id === action.payload ? { ...t, done: !t.done } : t);
case 'DELETE':
return todos.filter(t => t.id !== action.payload);
default:
return todos;
}
}

// Usage
function TodoApp() {
const { todos, addTodo, toggleTodo, deleteTodo } = useTodoList([]);

return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
</div>
);
}

useCallback — Memoize Functions

Prevent function recreation on every render:

function useStudentActions() {
const [students, setStudents] = useState([]);

const addStudent = useCallback((student) => {
setStudents(prev => [...prev, student]);
}, []);

const removeStudent = useCallback((id) => {
setStudents(prev => prev.filter(s => s.id !== id));
}, []);

return { students, addStudent, removeStudent };
}

useLocalStorage — Persist State

function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});

const setValue = (value) => {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
};

return [storedValue, setValue];
}

// Usage
function StudentPreferences() {
const [theme, setTheme] = useLocalStorage('theme', 'light');

return (
<>
<p>Theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle
</button>
</>
);
}

useAsync — Handle Async Operations

function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = useState('idle');
const [data, setData] = useState(null);
const [error, setError] = useState(null);

const execute = useCallback(async () => {
setStatus('pending');
try {
const response = await asyncFunction();
setData(response);
setStatus('success');
} catch (err) {
setError(err);
setStatus('error');
}
}, [asyncFunction]);

useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);

return { status, data, error, execute };
}

// Usage
function StudentList() {
const { status, data: students } = useAsync(() =>
fetch('/api/students').then(r => r.json())
);

if (status === 'pending') return <p>Loading...</p>;
if (status === 'error') return <p>Error</p>;

return (
<ul>
{students.map(s => <li key={s.id}>{s.name}</li>)}
</ul>
);
}

Custom Hooks Organization

Store in separate files:

// hooks/useFetch.js
export function useFetch(url) {
// ...
}

// hooks/useForm.js
export function useForm(initialValues, onSubmit) {
// ...
}

// hooks/index.js
export { useFetch } from './useFetch';
export { useForm } from './useForm';

// Use
import { useFetch, useForm } from './hooks';

Rules of Hooks

  1. Only call at top level — Not in loops or conditions
  2. Only in functions — In components or custom hooks, not regular functions
  3. Custom hooks start with use — Convention so React knows they're hooks

Key Takeaways

  • Custom hooks extract reusable logic
  • useRef for direct DOM access
  • useReducer for complex state
  • useCallback to memoize functions
  • Create library of reusable hooks
  • Next: Best practices and patterns
⚠️ Hook Mistakes
  1. Calling hooks conditionally — Breaks React's hook system
  2. Not following rules of hooks — Hooks can't be in regular functions
  3. Not naming with use — Confuses other developers
  4. Too much logic in one hook — Break into smaller pieces
💡 Custom Hook Library

Build a library of custom hooks you reuse:

  • useFetch
  • useForm
  • useAsync
  • useLocalStorage
  • useDebounce
  • useThrottle
🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on Custom Hooks. Try these prompts:

  • "What's the difference between a custom hook and a utility function?"
  • "When would you use useRef vs. useState?"
  • "Why would you use useReducer instead of useState?"
  • "Quiz me on custom hooks"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

Quick Definitions

  • Custom Hooks — Reusable Logic - The main React concept explained in this lesson.
  • Component - A reusable function that returns UI.
  • Props/state/effects - Core React ideas used to pass data, remember data, and run side effects.
  • Real project usage - How this appears in forms, dashboards, routes, and API-connected pages.

Common Mistakes

  • Copying React code without understanding data flow
  • Mutating arrays or objects directly instead of creating new values
  • Forgetting keys, dependencies, loading states, or error states where needed
  • Putting too much logic in one component
  • Not testing the screen with realistic School Management System data

Practice Task

Create a small React example using Custom Hooks — Reusable Logic. Keep it focused on one School Management System screen.

Suggested practice:

  1. Build a small component or page for students, attendance, marks, or fees.
  2. Pass realistic data into the component.
  3. Add one success state and one empty/error state where relevant.
  4. Explain the data flow in your own words.
  5. Rebuild the same example once without looking at the article.

Quick Revision

QuestionAnswer
What is the main idea?Understand and apply Custom Hooks — Reusable Logic in React.
Where is it used?Student dashboards, forms, tables, routes, and API-connected screens.
What should beginners focus on?Clear components, predictable data flow, and small examples.
What is the best debugging habit?Check props, state, render output, and browser console step by step.
🎯 How would you explain Custom Hooks — Reusable Logic in an interview?

Custom Hooks — Reusable Logic is a React concept used to build clear, reusable, and predictable user interfaces. I would explain the problem it solves, show a small component example, and mention a common mistake beginners should avoid.

🎯 Where is this used in a real React project?

It is used in screens like student lists, admission forms, attendance dashboards, marks reports, routing pages, and API-connected admin panels.

Next Article

Best Practices and Anti-Patterns ->

nexcoding.in