Reusing Logic with Custom Hooks in React JS
Reusing Logic with Custom Hooks is an important React concept that allows developers to extract reusable stateful logic from components and use it across multiple components. Custom Hooks help reduce duplicated code, improve readability, and make React applications easier to maintain.
Course Link: JustAcademy React JS Training Course
Course Demo: Register for React JS Course Demo
1. What Does Reusing Logic Mean in React?
Reusing logic means writing a piece of functionality once and using it in multiple places instead of repeating the same code in different components. In React, developers can reuse stateful logic through Custom Hooks.
For example, multiple components may need to track network status, handle form inputs, fetch API data, manage a counter, or listen for browser events. Instead of repeating the same logic, developers can create a Custom Hook and call it wherever required.
Example of Repeated Logic
import { useState } from 'react';
function CounterOne() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Counter One: {count}
</button>
);
}
function CounterTwo() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Counter Two: {count}
</button>
);
}
Explanation: Both components contain similar counter logic. If the same logic is needed in many components, maintaining repeated code can become difficult. A Custom Hook can extract and reuse the counter behavior.
2. What Are Custom Hooks?
Custom Hooks are JavaScript functions created by developers to reuse logic that uses React Hooks. A Custom Hook can combine built-in Hooks such as useState, useEffect, and useContext to perform a particular task.
Custom Hooks follow a naming convention: their names start with use, followed by a capital letter. Examples include useCounter, useOnlineStatus, useFormInput, and useFetch.
Basic Syntax
import { useState } from 'react';
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(previousCount => previousCount + 1);
};
return { count, increment };
}
export default useCounter;
Explanation: The useCounter Hook manages counter state and returns the current count and an increment function. Components can import this Hook and use its returned values.
3. Why Is Reusing Logic Important?
- Code reusability: Use the same logic in different components.
- Less duplication: Avoid copying the same state and effect code repeatedly.
- Easy maintenance: Update shared behavior in one place.
- Improved readability: Keep components focused on the UI they display.
- Better organization: Separate reusable behavior from presentation.
- Consistent behavior: Use the same implementation wherever the functionality is required.
- Testability: Make reusable behavior easier to verify through components and suitable Hook-testing tools.
4. Reusing Logic Without Custom Hooks
Before creating a Custom Hook, developers may copy the same state and effect logic into multiple components. This works, but changes must be made in every copy, which can introduce errors.
Example: Online Status in One Component
import { useState, useEffect } from 'react';
function StatusBar() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return (
<h2>{isOnline ? 'Online' : 'Disconnected'}</h2>
);
}
export default StatusBar;
If another component needs to show network status, the same logic might be copied into that component. A Custom Hook allows both components to reuse the implementation.
5. Reusing Logic with a Custom Hook
We can extract the online-status logic into a Hook named useOnlineStatus. Components can then use the Hook without needing to manage browser event listeners themselves.
File: useOnlineStatus.js
import { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(
typeof navigator === 'undefined' ? true : navigator.onLine
);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
export default useOnlineStatus;
File: StatusBar.jsx
import useOnlineStatus from './useOnlineStatus';
function StatusBar() {
const isOnline = useOnlineStatus();
return (
<h2>{isOnline ? 'Online' : 'Disconnected'}</h2>
);
}
export default StatusBar;
File: SaveButton.jsx
import useOnlineStatus from './useOnlineStatus';
function SaveButton() {
const isOnline = useOnlineStatus();
function handleSave() {
console.log('Progress saved');
}
return (
<button disabled={!isOnline} onClick={handleSave}>
{isOnline ? 'Save Progress' : 'Reconnecting...'}
</button>
);
}
export default SaveButton;
Explanation: Both components use the same Custom Hook. The status display shows whether the browser is online, while the Save button becomes disabled when the browser reports that it is offline. The Hook removes duplicated implementation code and keeps each component focused on its purpose.
Note: The browser's online indicator does not guarantee that a particular website or server is reachable.
6. Custom Hooks Share Logic, Not State
An important concept in React is that Custom Hooks share the implementation of logic, but each Hook call normally has its own independent state.
Example
import useCounter from './useCounter';
function CounterOne() {
const { count, increment } = useCounter();
return (
<button onClick={increment}>
Counter One: {count}
</button>
);
}
function CounterTwo() {
const { count, increment } = useCounter();
return (
<button onClick={increment}>
Counter Two: {count}
</button>
);
}
Explanation: Each component calls useCounter() separately. Counter One and Counter Two therefore maintain independent count values. Updating one counter does not automatically update the other.
If multiple components need to share the same state, you can lift state to a common parent, use Context, or use a suitable shared state-management solution.
7. Reusing Form Logic with Custom Hooks
Forms often contain repeated logic for storing input values, handling changes, and connecting values to input elements. A Custom Hook can extract this behavior and reuse it across multiple fields.
File: useFormInput.js
import { useState } from 'react';
function useFormInput(initialValue = '') {
const [value, setValue] = useState(initialValue);
function handleChange(event) {
setValue(event.target.value);
}
return {
value,
onChange: handleChange,
setValue
};
}
export default useFormInput;
File: ContactForm.jsx
import useFormInput from './useFormInput';
function ContactForm() {
const firstName = useFormInput('');
const lastName = useFormInput('');
function handleSubmit(event) {
event.preventDefault();
alert(`Name: ${firstName.value} ${lastName.value}`);
}
return (
<form onSubmit={handleSubmit}>
<input
value={firstName.value}
onChange={firstName.onChange}
placeholder="First Name"
required
/>
<input
value={lastName.value}
onChange={lastName.onChange}
placeholder="Last Name"
required
/>
<button type="submit">Submit</button>
</form>
);
}
export default ContactForm;
Explanation: The same Hook handles both fields, but each call maintains its own value. This reduces duplicated code while allowing the first name and last name to update independently.
8. Reusing API Fetching Logic
Multiple components may need to retrieve data from APIs. A Custom Hook can centralize loading, error, and data-handling logic so that components do not need to repeat it.
File: useFetch.js
import { useEffect, useState } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function loadData() {
setLoading(true);
setError(null);
try {
const response = await fetch(url, {
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message || 'Unable to fetch data');
}
} finally {
if (!controller.signal.aborted) {
setLoading(false);
}
}
}
loadData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
export default useFetch;
File: Users.jsx
import useFetch from './useFetch';
function Users() {
const { data, loading, error } = useFetch(
'https://jsonplaceholder.typicode.com/users'
);
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
Explanation: The useFetch Hook accepts a URL and returns data, loading status, and any error. The same Hook can be reused by other components with different URLs. It also aborts an outstanding request when the URL changes or the component using the Hook unmounts.
9. Passing Values Between Custom Hooks
Custom Hooks can receive values from component props or state and pass data to other Hooks. Because Hooks run as part of component rendering, they receive the latest values provided by the component.
Example
import { useState } from 'react';
import useChatRoom from './useChatRoom';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState(
'https://localhost:1234'
);
useChatRoom({ roomId, serverUrl });
return (
<div>
<label>
Server URL:
<input
value={serverUrl}
onChange={event => setServerUrl(event.target.value)}
/>
</label>
<h2>Current room: {roomId}</h2>
</div>
);
}
export default ChatRoom;
Explanation: The component passes roomId and serverUrl to the useChatRoom Hook. The Hook can use these values to establish or update a connection. Its implementation should include the required effect dependencies and cleanup logic.
Important: This example shows the component-to-Hook data flow. A working chat connection also requires a correctly implemented connection module and a reachable server.
10. When Should You Create a Custom Hook?
You should consider creating a Custom Hook when it makes your code clearer or allows useful stateful logic to be reused.
- When multiple components need the same stateful behavior.
- When several components repeat the same browser event subscription logic.
- When form fields share the same input-handling behavior.
- When different screens need common API-loading logic.
- When you want to hide implementation details behind a descriptive function.
- When an effect synchronizes a component with an external system and the same behavior is useful elsewhere.
You do not need to create a Custom Hook for every small piece of duplicated code. For example, a simple calculation that does not use React Hooks may be better as an ordinary JavaScript function.
11. Rules for Reusing Logic with Custom Hooks
- Use the correct naming convention: Name a Hook with the
use prefix, such as useCounter.
- Call Hooks at the top level: Do not call Hooks inside conditions, loops, or nested functions.
- Call Hooks from React functions: Call Custom Hooks from function components or other Custom Hooks.
- Keep rendering pure: Do not perform side effects directly during rendering.
- Specify dependencies: Include the reactive values required by an effect or callback.
- Clean up resources: Remove event listeners, clear timers, and disconnect subscriptions when necessary.
- Understand state ownership: Separate Hook calls normally maintain separate state.
- Keep the purpose clear: Prefer specific names such as
useOnlineStatus or useChatRoom over vague lifecycle wrappers.
12. Custom Hooks vs Regular Functions
| Feature |
Custom Hook |
Regular Function |
| Naming |
Starts with use. |
Can use any valid function name. |
| Can call React Hooks |
Yes, following the Rules of Hooks. |
No. |
| Primary purpose |
Reuse stateful React logic. |
Perform general JavaScript operations. |
| Example |
useOnlineStatus() |
calculateTotal() |
| Return value |
Can return state, functions, objects, arrays, or other values. |
Can return any JavaScript value. |
13. Custom Hooks vs Copying and Pasting Code
| Without a Custom Hook |
With a Custom Hook |
| Similar logic is repeated across components. |
Reusable logic is extracted into one function. |
| Changes may need to be made in several places. |
Shared implementation can be maintained centrally. |
| Components may contain implementation details. |
Components can focus on their intended behavior and UI. |
| Repeated code can make components longer. |
Components can become shorter and easier to understand. |
14. Advantages of Reusing Logic with Custom Hooks
- Reduces duplicated code.
- Improves maintainability.
- Separates behavior from UI rendering.
- Makes component code easier to read.
- Supports consistent implementation across screens.
- Allows logic to be updated without rewriting every component.
- Helps organize larger React projects into smaller, focused modules.
15. Common Mistakes to Avoid
- Calling Hooks conditionally: Always call Hooks at the top level.
- Incorrect dependencies: Missing reactive values can cause stale data or incorrect synchronization.
- Assuming state is shared: A Custom Hook does not automatically create shared state across components.
- Missing cleanup: Event listeners and subscriptions can remain active if they are not removed.
- Creating unnecessary Hooks: Extract logic when it improves clarity or reuse, not just to reduce line count.
- Using vague names: Name Hooks after the specific behavior they provide.
- Using Hooks in regular functions: Only React function components and Custom Hooks should call Hooks.
16. Interview Questions and Answers
Q1. What does reusing logic mean in React?
Answer: It means implementing functionality once and using it in multiple components instead of duplicating the same logic.
Q2. How do Custom Hooks help reuse logic?
Answer: Custom Hooks extract stateful behavior into reusable functions that components can call whenever they need that functionality.
Q3. What is the naming convention for Custom Hooks?
Answer: Their names start with use followed by a capital letter, such as useCounter.
Q4. Do Custom Hooks share state between components?
Answer: No. Each call normally has its own state. To share the same state, use an approach such as lifting state to a common parent or using Context.
Q5. Can a Custom Hook use useState and useEffect?
Answer: Yes. A Custom Hook can combine built-in React Hooks to implement reusable stateful behavior.
Q6. When should we create a Custom Hook?
Answer: Create one when it improves logic reuse, readability, organization, or maintainability.
Q7. Can a regular JavaScript function call useState?
Answer: No. React Hooks must be called from function components or Custom Hooks.
Q8. What is the difference between a Custom Hook and a component?
Answer: A component typically returns JSX describing UI, while a Custom Hook reuses behavior and can return values, functions, objects, or arrays.
Q9. Why is cleanup important in a Custom Hook?
Answer: Cleanup removes event listeners, disconnects subscriptions, or clears resources created by an effect to prevent unwanted behavior and resource leaks.
Q10. Can one Custom Hook call another Custom Hook?
Answer: Yes. Custom Hooks can be composed by calling other Hooks at the top level.
17. Practice Exercises
- Create a
useCounter Hook and use it in two different components.
- Create a
useToggle Hook for showing and hiding a message.
- Create a
useFormInput Hook for managing name and email fields.
- Create a
useOnlineStatus Hook for displaying browser network status.
- Create a
useFetch Hook for loading data from an API.
- Use a Custom Hook in two components and verify that each call maintains independent state.
- Create a
useDocumentTitle Hook that updates the browser tab title.
18. Quick Revision Checklist
- Understand the purpose of reusable logic.
- Know how Custom Hooks reduce code duplication.
- Understand the
use naming convention.
- Know the Rules of Hooks.
- Understand the difference between sharing logic and sharing state.
- Practice creating Hooks with
useState and useEffect.
- Learn how to pass values into Hooks and return values from them.
- Know when to create a Custom Hook and when to use a regular function.
19. Official React Documentation
20. React JS Training Resources
Learn React concepts and develop practical application-building skills with these resources:
Conclusion
Reusing logic with Custom Hooks helps developers build cleaner, more maintainable React applications. By extracting repeated stateful behavior into focused functions, developers can reduce duplication and keep components focused on their UI. Remember that Custom Hooks share logic rather than state, follow the Rules of Hooks, and should be created when they provide a clear benefit.