suhrav hussen image

Suhrav Hussen

June 28, 2025

React Query এর Powerful Features - একটি Complete Guide

React application development এ server state management একটা বিশাল চ্যালেঞ্জ। আর এই সমস্যার সমাধানে React Query (এখন TanStack Query নামে পরিচিত) একটা অসাধারণ library। আজকে আমরা React Query এর কিছু important features নিয়ে বিস্তারিত আলোচনা করব।


Infinite Queries - Endless Scrolling এর জন্য


আপনি কি কখনো Facebook বা Instagram এর মতো infinite scroll দেখেছেন? যেখানে scroll করলে নতুন নতুন post load হতে থাকে? React Query এর useInfiniteQuery দিয়ে এটা খুব সহজেই করা যায়।

import { useInfiniteQuery } from '@tanstack/react-query'

function PostList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: ({ pageParam = 1 }) => fetchPosts(pageParam),
    getNextPageParam: (lastPage, pages) => {
      return lastPage.hasMore ? pages.length + 1 : undefined
    }
  })

  return (
    <div>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.posts.map(post => (
            <div key={post.id}>{post.title}</div>
          ))}
        </div>
      ))}
      
      {hasNextPage && (
        <button onClick={() => fetchNextPage()}>
          {isFetchingNextPage ? 'Loading...' : 'আরো দেখুন'}
        </button>
      )}
    </div>
  )
}

এখানে getNextPageParam function টি বলে দেয় next page এর parameter কি হবে। খুবই simple, তাই না?


Parameterized Queries - Dynamic Data Fetching


অনেক সময় আমাদের query parameter এর উপর ভিত্তি করে data fetch করতে হয়। যেমন একটি specific user এর profile দেখানো বা কোনো particular product এর details।

function UserProfile({ userId }) {
  const { data: user, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    enabled: !!userId // userId থাকলেই query চালু হবে
  })

  if (isLoading) return <div>User info loading...</div>
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  )
}

// অন্য component থেকে
function App() {
  const [selectedUserId, setSelectedUserId] = useState(null)
  
  return (
    <div>
      <button onClick={() => setSelectedUserId(1)}>User 1</button>
      <button onClick={() => setSelectedUserId(2)}>User 2</button>
      
      {selectedUserId && <UserProfile userId={selectedUserId} />}
    </div>
  )
}

queryKey তে userId দেওয়ার কারণে প্রতিটি different user এর জন্য আলাদা cache তৈরি হবে।


Custom Queries - নিজস্ব Query Hook


বারবার একই ধরনের query লিখতে boring লাগে? Custom hook বানিয়ে ফেলুন!

// hooks/useUser.js
function useUser(userId) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
    retry: 3,
    enabled: !!userId
  })
}

// hooks/usePosts.js
function usePosts(filters = {}) {
  return useQuery({
    queryKey: ['posts', filters],
    queryFn: () => fetchPosts(filters),
    select: (data) => data.filter(post => post.published) // শুধু published posts
  })
}

// এখন যেকোনো component এ use করুন
function Profile({ userId }) {
  const { data: user, isLoading } = useUser(userId)
  const { data: posts } = usePosts({ authorId: userId })
  
  // ...
}


Query Key Factory - Smart Key Management


বড় application এ query key গুলো organize করা জরুরি। Query Key Factory pattern use করে এটা clean ভাবে করা যায়।

// utils/queryKeys.js
export const queryKeys = {
  users: {
    all: ['users'],
    lists: () => [...queryKeys.users.all, 'list'],
    list: (filters) => [...queryKeys.users.lists(), filters],
    details: () => [...queryKeys.users.all, 'detail'],
    detail: (id) => [...queryKeys.users.details(), id],
  },
  posts: {
    all: ['posts'],
    lists: () => [...queryKeys.posts.all, 'list'],
    list: (filters) => [...queryKeys.posts.lists(), filters],
    detail: (id) => [...queryKeys.posts.all, 'detail', id],
  }
}

// hooks/useUser.js
function useUser(userId) {
  return useQuery({
    queryKey: queryKeys.users.detail(userId),
    queryFn: () => fetchUser(userId)
  })
}

// এখন invalidation খুব easy
function deleteUser(userId) {
  // specific user এর cache clear করা
  queryClient.invalidateQueries({ queryKey: queryKeys.users.detail(userId) })
  
  // সব user list refresh করা
  queryClient.invalidateQueries({ queryKey: queryKeys.users.lists() })
}



Query Invalidation - Cache Refresh করা


Data update হওয়ার পর cache outdated হয়ে যায়। Query invalidation দিয়ে এটা fix করা যায়।

function useCreatePost() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      // নতুন post create এর পর সব post list refresh করো
      queryClient.invalidateQueries({ queryKey: ['posts'] })
      
      // specific user এর post list refresh করো
      queryClient.invalidateQueries({ 
        queryKey: ['posts'], 
        predicate: (query) => query.queryKey.includes('user-posts')
      })
    }
  })
}

function CreatePostForm() {
  const createPost = useCreatePost()
  
  const handleSubmit = (formData) => {
    createPost.mutate(formData)
  }
  
  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button type="submit" disabled={createPost.isLoading}>
        {createPost.isLoading ? 'Creating...' : 'Post করুন'}
      </button>
    </form>
  )
}


Optimistic Updates - Instant UI Update


User experience বাড়ানোর জন্য optimistic update use করা হয়। এতে server response এর জন্য wait না করেই UI update হয়ে যায়।

function useUpdatePost() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: updatePost,
    onMutate: async (updatedPost) => {
      // ongoing queries cancel করো
      await queryClient.cancelQueries({ queryKey: ['posts', updatedPost.id] })
      
      // previous data backup রাখো
      const previousPost = queryClient.getQueryData(['posts', updatedPost.id])
      
      // new data দিয়ে optimistically update করো
      queryClient.setQueryData(['posts', updatedPost.id], updatedPost)
      
      return { previousPost, updatedPost }
    },
    onError: (err, updatedPost, context) => {
      // error হলে previous data restore করো
      if (context?.previousPost) {
        queryClient.setQueryData(
          ['posts', updatedPost.id], 
          context.previousPost
        )
      }
    },
    onSettled: (data, error, updatedPost) => {
      // যাই হোক, query refresh করো
      queryClient.invalidateQueries({ queryKey: ['posts', updatedPost.id] })
    }
  })
}


Simple Mutations - Basic CRUD Operations


Common CRUD operations এর জন্য mutation use করা হয়।

function useDeletePost() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: deletePost,
    onSuccess: (deletedPost) => {
      // specific post cache থেকে remove করো
      queryClient.removeQueries({ queryKey: ['posts', deletedPost.id] })
      
      // post lists update করো
      queryClient.setQueryData(['posts'], (oldData) => {
        return oldData?.filter(post => post.id !== deletedPost.id)
      })
      
      toast.success('Post successfully deleted!')
    },
    onError: (error) => {
      toast.error('Post delete করতে problem হয়েছে!')
    }
  })
}

function PostItem({ post }) {
  const deletePost = useDeletePost()
  
  const handleDelete = () => {
    if (confirm('Are you sure?')) {
      deletePost.mutate(post.id)
    }
  }
  
  return (
    <div>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
      <button 
        onClick={handleDelete} 
        disabled={deletePost.isLoading}
      >
        {deletePost.isLoading ? 'Deleting...' : 'Delete করুন'}
      </button>
    </div>
  )
}


Global Error Handling - Centralized Error Management


সব জায়গায় আলাদা আলাদা error handling না করে global setup করা যায়।

// utils/queryClient.js
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 3,
      staleTime: 5 * 60 * 1000,
      onError: (error) => {
        if (error.status === 401) {
          // Unauthorized - login page এ redirect করো
          window.location.href = '/login'
        } else {
          toast.error('Something went wrong!')
        }
      }
    },
    mutations: {
      onError: (error) => {
        if (error.status === 403) {
          toast.error('আপনার এই action এর permission নেই!')
        } else {
          toast.error('Operation failed!')
        }
      }
    }
  }
})

// App.js
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <ErrorBoundary fallback={<ErrorPage />}>
        <Router>
          {/* your app routes */}
        </Router>
      </ErrorBoundary>
    </QueryClientProvider>
  )
}


Suspense Queries - React Suspense Integration


React Suspense এর সাথে React Query use করে loading states আরো elegant করা যায়।

// components/UserProfile.js
function UserProfile({ userId }) {
  const { data: user } = useSuspenseQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId)
  })
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  )
}

// App.js
function App() {
  return (
    <Suspense fallback={<div>User info loading...</div>}>
      <UserProfile userId={1} />
    </Suspense>
  )
}


Disabling Queries - Conditional Query Control


কিছু সময় আমাদের query conditionally enable/disable করতে হয়।

function UserPosts({ userId, isVisible }) {
  const { data: posts } = useQuery({
    queryKey: ['user-posts', userId],
    queryFn: () => fetchUserPosts(userId),
    enabled: !!userId && isVisible, // userId আছে এবং visible হলেই চালু
    refetchOnWindowFocus: false, // window focus এ refetch করবে না
    refetchInterval: isVisible ? 30000 : false // visible হলে 30 seconds interval এ update
  })
  
  return (
    <div>
      {posts?.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}


Selectors - Data Transformation


API থেকে আসা raw data কে component এর জন্য required format এ transform করা যায়।

function useUserStats(userId) {
  return useQuery({
    queryKey: ['user-stats', userId],
    queryFn: () => fetchUserData(userId),
    select: (userData) => ({
      totalPosts: userData.posts.length,
      publishedPosts: userData.posts.filter(p => p.published).length,
      averageViews: userData.posts.reduce((acc, p) => acc + p.views, 0) / userData.posts.length,
      recentPosts: userData.posts
        .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
        .slice(0, 5)
    })
  })
}

function UserDashboard({ userId }) {
  const { data: stats, isLoading } = useUserStats(userId)
  
  if (isLoading) return <div>Statistics loading...</div>
  
  return (
    <div>
      <h3>আপনার Statistics</h3>
      <p>Total Posts: {stats.totalPosts}</p>
      <p>Published Posts: {stats.publishedPosts}</p>
      <p>Average Views: {stats.averageViews.toFixed(1)}</p>
    </div>
  )
}


Practical Tips এবং Best Practices


1. Query Key Consistency

// ❌ Inconsistent
const userQuery = useQuery(['user', userId])
const postsQuery = useQuery(['userPosts', userId])

// ✅ Consistent
const userQuery = useQuery(['users', userId])
const postsQuery = useQuery(['users', userId, 'posts'])


2. Error Boundary Integration

function PostList() {
  const { data: posts, error } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    throwOnError: true // Error boundary এ error throw করবে
  })
  
  return (
    <div>
      {posts.map(post => <PostItem key={post.id} post={post} />)}
    </div>
  )
}


3. Background Refetch Control

function useUserProfile(userId) {
  return useQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId),
    staleTime: 5 * 60 * 1000, // 5 minutes fresh রাখো
    refetchOnMount: 'always',
    refetchOnWindowFocus: false,
    refetchOnReconnect: 'always'
  })
}


Final Thoughts

React Query এর এই features গুলো use করে আপনি complex data fetching এবং state management problems solve করতে পারবেন। Key points:

এগুলো master করলে আপনার React app অনেক বেশি performant এবং user-friendly হবে। সবচেয়ে ভাল কথা হল, এগুলো implement করতে খুব বেশি complex code লিখতে হয় না!

প্রতিটি feature নিয়ে practice করুন এবং নিজের project এ apply করার চেষ্টা করুন। React Query এর official documentation ও খুবই comprehensive, তাই আরো detail জানতে সেখানে check করতে পারেন।

Essential Links:

GithubLinkedinFacebookInstagram

Address:

Moulvibazar,

Sylhet,

Bangladesh

suhrav hussen image

Suhrav's Bot

Ask me a question