<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kalai Arasan </title>
    <description>The latest articles on DEV Community by Kalai Arasan  (@kalaiarasan-dev).</description>
    <link>https://dev.to/kalaiarasan-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3892135%2Fbd002776-eee2-423e-bbe8-22d7281ad826.jpg</url>
      <title>DEV Community: Kalai Arasan </title>
      <link>https://dev.to/kalaiarasan-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kalaiarasan-dev"/>
    <language>en</language>
    <item>
      <title>I Built a Production-Ready useFetch Hook with TypeScript — Here's What I Learned (Full Source Code)</title>
      <dc:creator>Kalai Arasan </dc:creator>
      <pubDate>Wed, 22 Apr 2026 16:41:35 +0000</pubDate>
      <link>https://dev.to/kalaiarasan-dev/i-built-a-production-ready-usefetch-hook-with-typescript-heres-what-i-learned-full-source-code-19ie</link>
      <guid>https://dev.to/kalaiarasan-dev/i-built-a-production-ready-usefetch-hook-with-typescript-heres-what-i-learned-full-source-code-19ie</guid>
      <description>&lt;p&gt;🎣 The Hook&lt;/p&gt;

&lt;p&gt;The first 3 lines decide if someone stays .&lt;br&gt;
"I wasted months rewriting the same fetch logic in every React project. One weekend, I built a hook that eliminated 100% of that boilerplate. Here it is, free and open-source."&lt;/p&gt;

&lt;p&gt;You know the drill. You start a new React component, and before you write a single line of UI, you're already typing:&lt;br&gt;
const [data, setData] = useState(null);&lt;br&gt;
const [loading, setLoading] = useState(false);&lt;br&gt;
const [error, setError] = useState(null);&lt;br&gt;
useEffect(() =&amp;gt; { ...&lt;/p&gt;

&lt;p&gt;It's tedious. It clutters your components. And if you forget to clean up with an AbortController? You've got memory leaks and that dreaded "Can't perform a React state update on an unmounted component" warning.&lt;/p&gt;

&lt;p&gt;I finally got tired of it and built useFetch : a single, production-ready custom hook that handles fetching, caching, loading states, and request cancellation with full TypeScript support.&lt;/p&gt;

&lt;p&gt;Today, I'm giving you the full source code and explaining the key design decisions that make it "production-ready."&lt;/p&gt;

&lt;p&gt;Why Not Just Use React Query or SWR?&lt;br&gt;
This is the first question I get. Libraries like React Query and SWR are fantastic, but they are heavy.&lt;/p&gt;

&lt;p&gt;Zero Dependencies: My hook is under 3KB. It doesn't bring an entire state management layer into your bundle.&lt;/p&gt;

&lt;p&gt;Full Control: You own the code. It's a single file you can drop into your src/hooks folder and customize to your heart's content.&lt;/p&gt;

&lt;p&gt;Learning Experience: Understanding how these hooks work under the hood makes you a better engineer.&lt;/p&gt;

&lt;p&gt;🚀 The Code: A Step-by-Step Breakdown&lt;br&gt;
Let's build this. We need it to be type-safe, handle race conditions, and include a smart caching layer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TypeScript First: Defining the Contract
Always start with the types. This makes the actual implementation trivial.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;typescript&lt;br&gt;
interface UseFetchOptions {&lt;br&gt;
  initialData?: T | null;&lt;br&gt;
  cacheDuration?: number; // Cache time in ms&lt;br&gt;
  skip?: boolean;         // Don't fetch automatically&lt;br&gt;
  onSuccess?: (data: T) =&amp;gt; void;&lt;br&gt;
  onError?: (error: Error) =&amp;gt; void;&lt;br&gt;
  deps?: unknown[];       // Re-fetch dependencies&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;interface UseFetchReturn {&lt;br&gt;
  data: T | null;&lt;br&gt;
  loading: boolean;&lt;br&gt;
  error: Error | null;&lt;br&gt;
  refetch: () =&amp;gt; Promise;&lt;br&gt;
  clearCache: () =&amp;gt; void;&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Singleton Cache (The Secret Sauce)
To avoid fetching the same user profile 50 times across different components, we need a global cache. We use a simple JavaScript object with a TTL (Time To Live) to ensure data freshness.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;typescript&lt;br&gt;
// cacheManager.ts&lt;br&gt;
const cache = new Map();&lt;/p&gt;

&lt;p&gt;export const cacheManager = {&lt;br&gt;
  get(key: string, ttl: number): T | null {&lt;br&gt;
    const entry = cache.get(key);&lt;br&gt;
    if (!entry) return null;&lt;br&gt;
    if (Date.now() - entry.timestamp &amp;gt; ttl) {&lt;br&gt;
      cache.delete(key);&lt;br&gt;
      return null;&lt;br&gt;
    }&lt;br&gt;
    return entry.data as T;&lt;br&gt;
  },&lt;br&gt;
  set(key: string, data: unknown): void {&lt;br&gt;
    cache.set(key, { data, timestamp: Date.now() });&lt;br&gt;
  },&lt;br&gt;
  clear(key: string): void {&lt;br&gt;
    cache.delete(key);&lt;br&gt;
  }&lt;br&gt;
};&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Hook Implementation: Taming Race Conditions
The critical part is the AbortController. Without it, if a user clicks "User 1" then quickly clicks "User 2", a slow API response for User 1 might arrive after User 2's data, overriding the state with stale information .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;tsx&lt;br&gt;
import { useEffect, useState, useCallback, useRef } from 'react';&lt;/p&gt;

&lt;p&gt;export function useFetch(&lt;br&gt;
  url: string | null,&lt;br&gt;
  options: UseFetchOptions = {}&lt;br&gt;
): UseFetchReturn {&lt;br&gt;
  const { cacheDuration = 300000, skip = false, deps = [] } = options;&lt;br&gt;
  const [data, setData] = useState(options.initialData || null);&lt;br&gt;
  const [loading, setLoading] = useState(!skip);&lt;br&gt;
  const [error, setError] = useState(null);&lt;br&gt;
  const abortControllerRef = useRef(null);&lt;/p&gt;

&lt;p&gt;const fetchData = useCallback(async () =&amp;gt; {&lt;br&gt;
    if (!url) return;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1. Check Cache
const cached = cacheManager.get&amp;lt;T&amp;gt;(url, cacheDuration);
if (cached) {
  setData(cached);
  setLoading(false);
  return;
}

// 2. Cancel previous call
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();

try {
  setLoading(true);
  const response = await fetch(url, { signal: abortControllerRef.current.signal });
  if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

  const result = await response.json();
  cacheManager.set(url, result);
  setData(result);
  options.onSuccess?.(result);
} catch (err) {
  // Ignore abort errors - they are intentional
  if (err instanceof Error &amp;amp;&amp;amp; err.name !== 'AbortError') {
    setError(err);
    options.onError?.(err);
  }
} finally {
  setLoading(false);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}, [url, cacheDuration]);&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
    if (skip) return;&lt;br&gt;
    fetchData();&lt;br&gt;
    // Cleanup: Abort request on unmount&lt;br&gt;
    return () =&amp;gt; abortControllerRef.current?.abort();&lt;br&gt;
  }, [fetchData, skip, ...deps]);&lt;/p&gt;

&lt;p&gt;const refetch = useCallback(() =&amp;gt; {&lt;br&gt;
    cacheManager.clear(url!);&lt;br&gt;
    fetchData();&lt;br&gt;
  }, [fetchData, url]);&lt;/p&gt;

&lt;p&gt;return { data, loading, error, refetch, clearCache: () =&amp;gt; cacheManager.clear(url!) };&lt;br&gt;
}&lt;br&gt;
🛠️ How to Use It (It's Stupidly Simple)&lt;br&gt;
Using the hook is cleaner than your current useEffect soup.&lt;/p&gt;

&lt;p&gt;tsx&lt;br&gt;
interface User {&lt;br&gt;
  id: number;&lt;br&gt;
  name: string;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const UserProfile = ({ userId }: { userId: number }) =&amp;gt; {&lt;br&gt;
  const { data: user, loading, error } = useFetch(&lt;br&gt;
    &lt;code&gt;https://api.example.com/users/${userId}&lt;/code&gt;,&lt;br&gt;
    { deps: [userId] } // Auto re-fetch when userId changes&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;if (loading) return ;&lt;br&gt;
  if (error) return ;&lt;/p&gt;

&lt;p&gt;return &lt;/p&gt;Welcome back, {user?.name}!;&lt;br&gt;
};&lt;br&gt;
🏆 Why This Will Save You Hours&lt;br&gt;
No More Memory Leaks: The AbortController cleanup ensures you never update an unmounted component.

&lt;p&gt;Instant Back-Navigation: Caching means data appears instantly when revisiting a page. No loading spinners.&lt;/p&gt;

&lt;p&gt;Type Safety: Generics ensure data is fully typed as User or Product[]. No more any.&lt;/p&gt;

&lt;p&gt;📦 Full Production-Ready Code&lt;br&gt;
I've prepared a complete, split-source GitHub repository with:&lt;/p&gt;

&lt;p&gt;✅ Full TypeScript support&lt;/p&gt;

&lt;p&gt;✅ Comprehensive Unit Tests&lt;/p&gt;

&lt;p&gt;✅ Advanced Caching with TTL&lt;/p&gt;

&lt;p&gt;✅ GitHub Actions CI/CD setup&lt;/p&gt;

&lt;p&gt;✅ A beautiful README ready for npm&lt;/p&gt;

&lt;p&gt;👉 GitHub Repo: use-fetch-cache&lt;br&gt;
Don't forget to drop a ⭐ if you find it useful!&lt;/p&gt;

&lt;p&gt;💬 Final Thoughts&lt;br&gt;
Building your own tools is the best way to master a framework. You don't always need a 20kB third-party library to solve a 2kB problem. By writing useFetch, you've taken control of your data layer and reduced your bundle size simultaneously.&lt;/p&gt;

&lt;p&gt;Have you built a custom hook that changed your workflow? Drop a comment below—I'd love to see what you're building!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>tutorial</category>
      <category>typescript</category>
    </item>
    <item>
      <title>🏗 How I Structure Scalable React Projects (Feature-Based Architecture)</title>
      <dc:creator>Kalai Arasan </dc:creator>
      <pubDate>Wed, 22 Apr 2026 09:52:48 +0000</pubDate>
      <link>https://dev.to/kalaiarasan-dev/how-i-structure-scalable-react-projects-feature-based-architecture-46m2</link>
      <guid>https://dev.to/kalaiarasan-dev/how-i-structure-scalable-react-projects-feature-based-architecture-46m2</guid>
      <description>&lt;p&gt;Most React tutorials show this:&lt;br&gt;
src/&lt;br&gt;
  components/&lt;br&gt;
  pages/&lt;br&gt;
  hooks/&lt;br&gt;
It works for small apps.&lt;/p&gt;

&lt;p&gt;It breaks at scale.&lt;br&gt;
Once your app grows:&lt;/p&gt;

&lt;p&gt;Files become tightly coupled&lt;br&gt;
Features leak into each other&lt;br&gt;
Refactoring becomes painful&lt;br&gt;
Here’s the structure I use for scalable frontend systems.&lt;/p&gt;

&lt;p&gt;🎯 The Problem With Type-Based Structure&lt;br&gt;
The traditional structure groups by file type:&lt;br&gt;
Once your app grows:&lt;br&gt;
Files become tightly coupled&lt;br&gt;
Features leak into each other&lt;br&gt;
Refactoring becomes painful&lt;br&gt;
Here’s the structure I use for scalable frontend systems.&lt;/p&gt;

&lt;p&gt;🎯 The Problem With Type-Based Structure&lt;br&gt;
The traditional structure groups by file type:&lt;br&gt;
components/&lt;br&gt;
hooks/&lt;br&gt;
utils/&lt;br&gt;
This causes:&lt;br&gt;
Cross-folder dependencies&lt;br&gt;
Hard-to-track feature logic&lt;br&gt;
Poor domain separation&lt;br&gt;
You don’t build features.&lt;br&gt;
You build file collections.&lt;br&gt;
That’s the issue.&lt;/p&gt;

&lt;p&gt;✅ Feature-Based Architecture&lt;br&gt;
Instead, group by feature/domain.&lt;/p&gt;

&lt;p&gt;src/&lt;br&gt;
  features/&lt;br&gt;
    auth/&lt;br&gt;
      components/&lt;br&gt;
      hooks/&lt;br&gt;
      services/&lt;br&gt;
      types.ts&lt;br&gt;
    dashboard/&lt;br&gt;
      components/&lt;br&gt;
      hooks/&lt;br&gt;
      services/&lt;br&gt;
      types.ts&lt;br&gt;
  shared/&lt;br&gt;
    components/&lt;br&gt;
    hooks/&lt;br&gt;
    utils/&lt;/p&gt;

&lt;p&gt;Now:&lt;br&gt;
Everything related to authentication lives together&lt;br&gt;
Dashboard logic stays isolated&lt;br&gt;
Shared utilities remain global&lt;br&gt;
This scales cleanly.&lt;br&gt;
🔁 Dependency Flow Rule&lt;br&gt;
Follow this rule:&lt;/p&gt;

&lt;p&gt;features → shared&lt;br&gt;&lt;br&gt;
shared → nothing&lt;br&gt;
Features can depend on shared modules.&lt;br&gt;
Shared modules should NEVER depend on features.&lt;br&gt;
This prevents circular architecture.&lt;br&gt;
🧠 Example: Auth Feature&lt;/p&gt;

&lt;p&gt;features/auth/&lt;br&gt;
  components/LoginForm.tsx&lt;br&gt;
  hooks/useLogin.ts&lt;br&gt;
  services/authApi.ts&lt;br&gt;
  types.ts&lt;br&gt;
useLogin.ts&lt;br&gt;
TypeScript&lt;br&gt;
import { login } from "../services/authApi";&lt;/p&gt;

&lt;p&gt;export const useLogin = () =&amp;gt; {&lt;br&gt;
  const handleLogin = async (email: string, password: string) =&amp;gt; {&lt;br&gt;
    return login(email, password);&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;return { handleLogin };&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;Notice:&lt;br&gt;
Business logic stays inside the feature&lt;br&gt;
API calls stay inside the feature&lt;br&gt;
No global pollution&lt;/p&gt;

&lt;p&gt;📦 When to Move to Shared&lt;br&gt;
Move code to shared/ only if:&lt;br&gt;
It’s reused in 2+ features&lt;br&gt;
It has no domain-specific logic&lt;br&gt;
It’s generic (Button, Modal, useDebounce, etc.)&lt;br&gt;
Premature sharing creates coupling.&lt;/p&gt;

&lt;p&gt;⚡ Benefits&lt;br&gt;
Clear ownership&lt;br&gt;
Easier onboarding&lt;br&gt;
Safer refactoring&lt;br&gt;
Cleaner scaling&lt;br&gt;
Better test isolation&lt;br&gt;
This structure works well for:&lt;br&gt;
SaaS dashboards&lt;br&gt;
Admin panels&lt;br&gt;
Startup MVPs&lt;br&gt;
Growing production apps&lt;/p&gt;

&lt;p&gt;🚨 Common Mistake&lt;br&gt;
Don’t over-engineer early.&lt;br&gt;
For:&lt;/p&gt;

&lt;p&gt;2-page apps&lt;br&gt;
Landing pages&lt;br&gt;
Simple structure is fine.&lt;br&gt;
Architecture should evolve with complexity.&lt;/p&gt;

&lt;p&gt;📌 Final Thought&lt;br&gt;
Folder structure is not cosmetic.&lt;/p&gt;

&lt;p&gt;It defines:&lt;br&gt;
Maintainability&lt;br&gt;
Scalability&lt;br&gt;
Developer velocity&lt;/p&gt;

&lt;p&gt;Frontend architecture matters more than flashy UI.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
