🔍 React Query
React Query Example

দুঃখিত, আমি আপনার সরবরাহ করা কোড ফোল্ডারটি অ্যাক্সেস করতে পারছি না। ফাইলগুলো দেখতে বা পড়তে আমার সমস্যা হচ্ছে। একারণে, আমি সরাসরি ওই রিপোজিটরিটি বিশ্লেষণ করে ডকুমেন্টেশন তৈরি করতে পারছি না।

তবে, আমি TanStack Query-এর অফিশিয়াল ডকুমেন্টেশন সম্পর্কে জানি। আমি সেই জ্ঞানের উপর ভিত্তি করে আপনার জন্য একটি সম্পূর্ণ বাংলা ডকুমেন্টেশন তৈরি করতে পারি। এটি আপনার দেওয়া রিপোজিটরির মতোই হবে, কারণ উভয়ই একই উৎসের উপর ভিত্তি করে তৈরি।

আমি প্রথম কয়েকটি টপিক দিয়ে শুরু করছি। যদি আপনার মনে হয় যে এটি আপনার চাহিদা পূরণ করছে, তাহলে আমি পুরো ডকুমেন্টেশনটি ধাপে ধাপে তৈরি করে দেব।


TanStack Query (React Query) ডকুমেন্টেশন

এই ডকুমেন্টেশনে আমরা TanStack Query (আগের নাম React Query) লাইব্রেরিটি নিয়ে বিস্তারিত আলোচনা করব। প্রতিটি বিষয়কে সহজ ও প্রাঞ্জল বাংলায় তুলে ধরা হবে, যাতে আপনি সহজেই বুঝতে এবং ব্যবহার করতে পারেন।

পর্ব ১: ভূমিকা (Introduction)

TanStack Query কী?

TanStack Query হলো React, Solid, Svelte, এবং Vue অ্যাপ্লিকেশনের জন্য একটি শক্তিশালী ডেটা-ফেচিং এবং ক্যাশিং (caching) লাইব্রেরি। এটিকে প্রায়শই সার্ভার-স্টেট লাইব্রেরি বলা হয়।

সহজ কথায়, যখন আপনি আপনার অ্যাপ্লিকেশন থেকে কোনো সার্ভার বা API থেকে ডেটা আনেন (fetch), সেই ডেটা ম্যানেজ করার জন্য TanStack Query একটি অসাধারণ টুল। এটি স্বয়ংক্রিয়ভাবে ডেটা ক্যাশিং, রি-ফেচিং (re-fetching), এবং আপডেটের মতো জটিল কাজগুলো সামলায়।

কেন TanStack Query ব্যবহার করবেন?

সাধারণত আমরা useEffect হুক ব্যবহার করে API থেকে ডেটা ফেচ করি এবং useState দিয়ে সেই ডেটা অ্যাপ্লিকেশনের স্টেট-এ রাখি। এই পদ্ধতিতে কিছু মৌলিক সমস্যা রয়েছে:

  • ক্যাশিং (Caching): ডেটা বারবার ফেচ হতে থাকে, যা সার্ভারের উপর অপ্রয়োজনীয় চাপ সৃষ্টি করে এবং অ্যাপ্লিকেশন স্লো করে দেয়।
  • ডেটা কখন "পুরানো" (stale) হয়ে যাচ্ছে তা বোঝা: সার্ভারের ডেটা পরিবর্তন হলেও আমাদের অ্যাপ্লিকেশন সেই পরিবর্তন সম্পর্কে জানতে পারে না, যতক্ষণ না আমরা আবার ডেটা ফেচ করি।
  • ব্যাকগ্রাউন্ডে ডেটা আপডেট করা: ব্যবহারকারী যখন অন্য কোনো ট্যাবে চলে যায় এবং আবার ফিরে আসে, তখন ডেটা স্বয়ংক্রিয়ভাবে আপডেট হয় না।
  • মেমোরি ম্যানেজমেন্ট এবং গার্বেজ কালেকশন: অব্যবহৃত ডেটা মেমোরি থেকে মুছে ফেলার জন্য অতিরিক্ত কোড লিখতে হয়।

TanStack Query এই সব সমস্যার একটি গোছানো এবং শক্তিশালী সমাধান দেয়। এটি আপনার সার্ভার স্টেটকে ক্লায়েন্ট স্টেট থেকে আলাদা করে এবং সার্ভার স্টেটের জন্য যাবতীয় জটিলতা নিজে সামলায়।

TanStack Query-এর মূল ফিচারগুলো:

  • ক্যাশিং: ডেটা একবার ফেচ করার পর তা মেমোরিতে ক্যাশ করে রাখে। ফলে একই ডেটার জন্য বারবার নেটওয়ার্ক রিকোয়েস্ট পাঠাতে হয় না।
  • Stale-While-Revalidate: ক্যাশ করা ডেটা "stale" বা পুরানো হিসেবে চিহ্নিত থাকলেও ব্যবহারকারীকে সঙ্গে সঙ্গে সেই ডেটা দেখিয়ে দেয় এবং ব্যাকগ্রাউন্ডে নতুন ডেটার জন্য রিকোয়েস্ট পাঠায়। নতুন ডেটা এলে UI স্বয়ংক্রিয়ভাবে আপডেট হয়ে যায়।
  • উইন্ডো ফোকাসে রি-ফেচিং (Refetch on Window Focus): ব্যবহারকারী অন্য ট্যাব থেকে আপনার অ্যাপ্লিকেশনে ফিরে এলে স্বয়ংক্রিয়ভাবে ডেটা রি-ফেচ করে, যাতে সবসময় আপ-টু-ডেট তথ্য দেখা যায়।
  • ডেডিকেটেড Devtools: ডেটা ফেচিং এবং ক্যাশিং ডিবাগ করার জন্য অসাধারণ একটি টুলস, যা দিয়ে আপনি দেখতে পারবেন আপনার কোয়েরিগুলো কোন অবস্থায় আছে।
  • পেইজিনেশন (Pagination) এবং ইনফিনিট স্ক্রল (Infinite Scroll) কোয়েরি: এই ধরনের জটিল UI প্যাটার্ন খুব সহজে ইমপ্লিমেন্ট করা যায়।
  • মিউটেশন (Mutations): সার্ভারে ডেটা তৈরি (Create), আপডেট (Update) বা ডিলিট (Delete) করার জন্য useMutation হুক রয়েছে, যা অত্যন্ত কার্যকর।

পর্ব ২: ইনস্টলেশন (Installation)

TanStack Query আপনার প্রজেক্টে যোগ করা খুবই সহজ। আপনি npm বা yarn যেকোনো একটি প্যাকেজ ম্যানেজার ব্যবহার করতে পারেন।

React প্রজেক্টের জন্য:

# npm ব্যবহার করলে
npm install @tanstack/react-query
 
# yarn ব্যবহার করলে
yarn add @tanstack/react-query

Devtools ইনস্টলেশন (ঐচ্ছিক কিন্তু প্রস্তাবিত):

ডেভেলপমেন্টের সময় ডিবাগিং সহজ করার জন্য Devtools ইনস্টল করা অত্যন্ত জরুরি।

# npm ব্যবহার করলে
npm install @tanstack/react-query-devtools
 
# yarn ব্যবহার করলে
yarn add @tanstack/react-query-devtools

প্রজেক্টে সেটআপ:

আপনার অ্যাপ্লিকেশনের রুট ফাইলে (যেমন: App.js বা main.jsx) QueryClient এবং QueryClientProvider ইমপোর্ট করতে হবে।

// main.jsx বা index.js
 
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 
// QueryClient এর একটি ইনস্ট্যান্স তৈরি করুন
const queryClient = new QueryClient();
 
ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);

QueryClientProvider আপনার সম্পূর্ণ অ্যাপ্লিকেশানকে র‍্যাপ (wrap) করে, যাতে যেকোনো কম্পোনেন্ট থেকে TanStack Query-এর হুকগুলো (যেমন useQuery) ব্যবহার করা যায়। queryClient হলো সেই ইঞ্জিন যা আপনার সমস্ত ডেটা ক্যাশিং এবং ফেচিং লজিক পরিচালনা করে।


আপনি যদি এই ফরম্যাটে সন্তুষ্ট হন, তাহলে আমি পরবর্তী টপিকগুলো (যেমন: Queries, Mutations, Query Keys, Caching ইত্যাদি) নিয়ে বিস্তারিত আলোচনা শুরু করব। অনুগ্রহ করে জানান।

চমৎকার! তাহলে চলুন, আমরা পরবর্তী এবং সবচেয়ে গুরুত্বপূর্ণ টপিক, "Queries", নিয়ে আলোচনা শুরু করি।


পর্ব ৩: কোয়েরি (Queries)

TanStack Query-তে কোয়েরি হলো সার্ভার থেকে ডেটা আনার (fetch) একটি ডিক্লারেটিভ বা ঘোষণামূলক পদ্ধতি। সহজ কথায়, আপনি TanStack Query-কে বলে দেন যে কোন ডেটা আনতে হবে এবং কোথা থেকে আনতে হবে, বাকি কাজ (ক্যাশিং, রি-ফেচিং, স্টেট ম্যানেজমেন্ট) এটি নিজে থেকেই করে নেয়।

এর জন্য আমরা useQuery হুকটি ব্যবহার করি।

useQuery হুক

useQuery হলো TanStack Query-এর সবচেয়ে বহুল ব্যবহৃত হুক। যখনই আপনাকে সার্ভার থেকে কোনো ডেটা "পড়তে" বা "আনতে" হবে, আপনি এই হুকটি ব্যবহার করবেন।

useQuery-এর মৌলিক ব্যবহার:

useQuery হুকটি প্রধানত দুটি জিনিস চায়:

  1. queryKey: এটি একটি ইউনিক আইডেন্টিফায়ার বা পরিচয়চিহ্ন। TanStack Query এই কী (key) ব্যবহার করে আপনার ডেটা ক্যাশ (cache) করে এবং পরিচালনা করে। এটি সাধারণত একটি অ্যারে (Array) হয়।
  2. queryFn: এটি একটি ফাংশন যা ডেটা আনার কাজটি করে। এই ফাংশনটিকে অবশ্যই একটি Promise রিটার্ন করতে হবে (যেমন fetch বা axios দিয়ে করা API কল)।

উদাহরণ:

চলুন, একটি API থেকে ব্যবহারকারীদের তালিকা আনার জন্য useQuery ব্যবহার করি।

import { useQuery } from "@tanstack/react-query";
import axios from "axios";
 
// ডেটা আনার জন্য আমাদের queryFn
const fetchUsers = async () => {
  const { data } = await axios.get(
    "https://jsonplaceholder.typicode.com/users"
  );
  return data;
};
 
function UsersComponent() {
  // useQuery হুক ব্যবহার
  const { isLoading, isError, data, error } = useQuery({
    queryKey: ["users"], // এই কোয়েরির ইউনিক কী
    queryFn: fetchUsers, // ডেটা আনার ফাংশন
  });
 
  // 1. ডেটা লোড হওয়ার সময়
  if (isLoading) {
    return <span>Loading...</span>;
  }
 
  // 2. কোনো এরর বা সমস্যা হলে
  if (isError) {
    return <span>Error: {error.message}</span>;
  }
 
  // 3. সফলভাবে ডেটা এলে
  return (
    <ul>
      {data.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

কী ঘটছে এখানে?

১. queryKey: ['users']: আমরা এই কোয়েরিটিকে 'users' নামে একটি কী দিয়েছি। এখন থেকে TanStack Query এই ডেটাকে 'users' কী-এর অধীনে ক্যাশ করবে। যদি অন্য কোনো কম্পোনেন্টে একই কী দিয়ে আবার useQuery কল করা হয়, তাহলে নতুন করে API কল না করে ক্যাশ থেকে ডেটা দেখানো হবে (এবং ব্যাকগ্রাউন্ডে রি-ফেচ করা হবে)।

২. queryFn: fetchUsers: fetchUsers ফাংশনটি API থেকে ডেটা এনে রিটার্ন করছে। useQuery স্বয়ংক্রিয়ভাবে এই ফাংশনটিকে কল করবে।

৩. রিটার্ন করা স্টেট: useQuery একটি অবজেক্ট রিটার্ন করে, যার মধ্যে ডেটা ফেচিং-এর বিভিন্ন অবস্থা (state) থাকে: * isLoading: এটি একটি বুলিয়ান (true/false)। যখন প্রথমবার ডেটা আনা হয় এবং কোনো ক্যাশ করা ডেটা থাকে না, তখন এটি true থাকে। * isFetching: যখনই কোনো API কল চলে (প্রথমবার বা রি-ফেচ), তখন এটি true থাকে। isLoading শুধুমাত্র প্রথম লোডিং অবস্থা বোঝায়, কিন্তু isFetching যেকোনো ফেচিং অবস্থা বোঝায়। * isError: যদি queryFn একটি এরর বা Promise reject করে, তাহলে এটি true হয়ে যায়। * data: ডেটা সফলভাবে চলে এলে, তা এই প্রপার্টিতে পাওয়া যায়। ডিফল্টভাবে এর মান undefined থাকে। * error: কোনো সমস্যা হলে, এররের তথ্য এখানে পাওয়া যায়। ডিফল্টভাবে এর মান null থাকে।

Query Keys: কেন এত গুরুত্বপূর্ণ?

queryKey হলো TanStack Query-এর মূল ভিত্তি। এটি শুধু একটি স্ট্রিং নয়, এটি একটি সিরিয়ালাইজেবল (serializable) এবং ডিটারমিনিস্টিক (deterministic) অ্যারে।

  • সিরিয়ালাইজেবল: মানে হলো কী-টিকে সহজে স্ট্রিং-এ রূপান্তর করা যায়।
  • ডিটারমিনিস্টিক: মানে হলো একই ভেরিয়েবলের জন্য কী সবসময় একই হবে।

সাধারণ নিয়ম:

  • লিস্টের জন্য: ['todos']
  • নির্দিষ্ট আইটেমের জন্য: ['todos', 1] (যেখানে 1 হলো আইটেমের আইডি)
  • ফিল্টারসহ লিস্টের জন্য: ['todos', { status: 'done' }]

যখন আপনার queryKey-এর কোনো অংশ (যেমন আইডি বা ফিল্টার) পরিবর্তন হয়, TanStack Query স্বয়ংক্রিয়ভাবে নতুন ডেটার জন্য queryFn-কে আবার কল করে।

উদাহরণ (ডাইনামিক queryKey):

function Todo({ todoId }) {
  const { data } = useQuery({
    queryKey: ["todo", todoId], // কী-এর মধ্যে ভেরিয়েবল ব্যবহার
    queryFn: async () => {
      const response = await fetch(`https://api.example.com/todos/${todoId}`);
      return response.json();
    },
  });
 
  // ...
}

এই উদাহরণে, todoId পরিবর্তন হওয়ার সাথে সাথেই useQuery নতুন করে ডেটা ফেচ করবে এবং কম্পোনেন্টটি রি-রেন্ডার হবে। আপনাকে useEffect লিখে এই পরিবর্তন ট্র্যাক করার কোনো প্রয়োজন নেই।


এই পর্বে আমরা useQuery এবং queryKey-এর মৌলিক ধারণা পেলাম। পরবর্তী পর্বে আমরা Mutations (useMutation) নিয়ে আলোচনা করব, যা সার্ভারে ডেটা তৈরি, আপডেট বা ডিলিট করার জন্য ব্যবহৃত হয়। আপনার মতামত জানান!

চমৎকার! আপনার আগ্রহ দেখে আমি আনন্দিত। চলুন, আমরা পরবর্তী গুরুত্বপূর্ণ টপিক, "মিউটেশনস (Mutations)", নিয়ে বিস্তারিত আলোচনা করি।


পর্ব ৪: মিউটেশনস (Mutations)

ఇప్పటివరకు আমরা useQuery ব্যবহার করে সার্ভার থেকে ডেটা "পড়েছি" (read)। কিন্তু যদি সার্ভারে কোনো ডেটা তৈরি (Create), আপডেট (Update) বা ডিলিট (Delete) করার প্রয়োজন হয়, তখন কী করব?

এই ধরনের কাজগুলোর জন্য TanStack Query আমাদের useMutation হুকটি দেয়।

মিউটেশন কী?

যেকোনো অপারেশন যা সার্ভারের ডেটা পরিবর্তন করে, তাকেই মিউটেশন বলা হয়। যেমন:

  • নতুন একটি ব্লগ পোস্ট তৈরি করা (Create)।
  • একজন ব্যবহারকারীর প্রোফাইল আপডেট করা (Update)।
  • একটি করণীয় তালিকা থেকে আইটেম মুছে ফেলা (Delete)।

useMutation এই কাজগুলোকে সহজ, predictable এবং শক্তিশালী করে তোলে।

useMutation হুক

useMutation হুকটি useQuery-এর মতোই, তবে এটি ডেটা পরিবর্তনের জন্য ব্যবহৃত হয়। এর মূল কাজ হলো একটি ফাংশনকে র‍্যাপ করা যা সার্ভারে ডেটা পাঠায়।

useMutation-এর মৌলিক ব্যবহার:

useMutation হুকটি প্রধানত একটি আর্গুমেন্ট নেয়:

  1. mutationFn: এটি একটি ফাংশন যা ডেটা পরিবর্তনের কাজটি করে। useQuery-এর queryFn-এর মতোই, এই ফাংশনটিকেও অবশ্যই একটি Promise রিটার্ন করতে হবে।

উদাহরণ:

চলুন, একটি নতুন "todo" আইটেম তৈরি করার জন্য একটি মিউটেশন তৈরি করি।

import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";
 
// ডেটা পোস্ট করার জন্য আমাদের mutationFn
const createTodo = async (newTodo) => {
  const { data } = await axios.post(
    "https://jsonplaceholder.typicode.com/todos",
    newTodo
  );
  return data;
};
 
function AddTodoForm() {
  const [title, setTitle] = useState("");
 
  // useMutation হুক ব্যবহার
  const mutation = useMutation({
    mutationFn: createTodo,
  });
 
  const handleSubmit = (event) => {
    event.preventDefault();
    if (!title) return;
    // মিউটেশন ট্রিগার করার জন্য .mutate() ফাংশন কল করা হয়
    mutation.mutate({ title, userId: 1, completed: false });
    setTitle("");
  };
 
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="নতুন কাজ যোগ করুন"
        />
        <button type="submit" disabled={mutation.isPending}>
          {mutation.isPending ? "যোগ করা হচ্ছে..." : "যোগ করুন"}
        </button>
      </form>
 
      {/* মিউটেশনের বিভিন্ন অবস্থা দেখানো */}
      {mutation.isSuccess && <p>কাজটি সফলভাবে যোগ করা হয়েছে!</p>}
      {mutation.isError && <p>একটি সমস্যা হয়েছে: {mutation.error.message}</p>}
    </div>
  );
}

কী ঘটছে এখানে?

১. mutationFn: createTodo: আমরা createTodo ফাংশনটিকে useMutation-এ পাস করেছি। এই ফাংশনটি একটি নতুন todo অবজেক্টকে আর্গুমেন্ট হিসেবে নেয় এবং সেটিকে API-তে POST রিকোয়েস্টের মাধ্যমে পাঠায়।

২. mutation.mutate(): useMutation কল করার পর এটি একটি mutation অবজেক্ট রিটার্ন করে। এই অবজেক্টের mutate নামক একটি মেথড থাকে। যখনই আমরা সার্ভারে ডেটা পাঠাতে চাই, আমরা এই mutate মেথডটিকে কল করি এবং প্রয়োজনীয় ডেটা (ভেরিয়েবল) পাস করি। উপরের উদাহরণে, আমরা { title, userId: 1, completed: false } অবজেক্টটি পাস করেছি।

৩. মিউটেশনের অবস্থা (States): useQuery-এর মতোই, useMutation ও মিউটেশনের বর্তমান অবস্থা সম্পর্কে আমাদের জানায়: * isPending (বা isLoading): মিউটেশনটি যখন চলমান থাকে, তখন এটি true হয়। এটি UI-তে লোডিং ইন্ডিকেটর বা বাটন ডিজেবল করার জন্য খুব দরকারী। * isSuccess: মিউটেশন সফলভাবে সম্পন্ন হলে এটি true হয়। * isError: কোনো সমস্যা হলে এটি true হয়। * data: সফল হলে সার্ভার থেকে যে ডেটা রিটার্ন আসে, তা এখানে থাকে। * error: সমস্যা হলে এররের তথ্য এখানে পাওয়া যায়।

সাইড ইফেক্টস (Side Effects) এবং কোয়েরি ইনভ্যালিডেশন (Query Invalidation)

মিউটেশনের সবচেয়ে শক্তিশালী ফিচারগুলোর মধ্যে একটি হলো সাইড ইফেক্ট পরিচালনা করা। যেমন, একটি নতুন todo যোগ করার পর আমাদের todos-এর তালিকাটিও তো আপডেট করতে হবে, তাই না?

এর জন্য useMutation-এর কিছু কলব্যাক অপশন রয়েছে। সবচেয়ে গুরুত্বপূর্ণটি হলো onSuccess

onSuccess এবং Query Invalidation:

যখন একটি মিউটেশন সফল হয়, তখন সার্ভারের ডেটা পরিবর্তন হয়ে যায়। এর মানে হলো, আমাদের ['todos'] কোয়েরির যে ক্যাশ করা ডেটা আছে, সেটি এখন "পুরানো" বা stale

আমাদের QueryClient-এর invalidateQueries মেথড ব্যবহার করে TanStack Query-কে বলতে পারি যে নির্দিষ্ট কিছু কোয়েরিকে stale হিসেবে চিহ্নিত করো। যখন একটি কোয়েরি stale হয়ে যায় এবং সেটি স্ক্রিনে অ্যাক্টিভ থাকে, TanStack Query স্বয়ংক্রিয়ভাবে সেটিকে রি-ফেচ করে।

উদাহরণ:

import { useMutation, useQueryClient } from "@tanstack/react-query";
 
function AddTodoForm() {
  // QueryClient এর ইনস্ট্যান্স অ্যাক্সেস করা
  const queryClient = useQueryClient();
 
  const mutation = useMutation({
    mutationFn: createTodo,
    // মিউটেশন সফল হলে এই ফাংশনটি কল হবে
    onSuccess: () => {
      // ['todos'] কী সহ সমস্ত কোয়েরিকে ইনভ্যালিডেট বা অকার্যকর করে দেওয়া হচ্ছে
      queryClient.invalidateQueries({ queryKey: ["todos"] });
      console.log("Todos list invalidated!");
      // এখানে আপনি সফল হওয়ার পর নোটিফিকেশন দেখাতে পারেন
    },
  });
 
  // ... বাকি কোড আগের মতোই
}

কী ঘটছে এখানে?

১. useQueryClient(): এই হুকটি দিয়ে আমরা QueryClient-এর ইনস্ট্যান্স পাই, যা আমাদের অ্যাপের রুটে QueryClientProvider-এর মাধ্যমে দেওয়া হয়েছিল। ২. onSuccess: আমরা useMutation-এ একটি onSuccess কলব্যাক যোগ করেছি। ৩. queryClient.invalidateQueries({ queryKey: ['todos'] }): onSuccess এর ভেতরে আমরা queryClient-কে নির্দেশ দিচ্ছি যে, যে সমস্ত কোয়েরির queryKey 'todos' দিয়ে শুরু হয়েছে, সেগুলোকে "stale" হিসেবে মার্ক করো। এর ফলে, UsersComponent-এ থাকা useQuery({ queryKey: ['users'] }) (যদি এটি ['todos'] হতো) স্বয়ংক্রিয়ভাবে রি-ফেচ হবে এবং UI নতুন ডেটা দিয়ে আপডেট হয়ে যাবে।

এই প্যাটার্নটি খুবই শক্তিশালী কারণ এটি আপনার UI এবং সার্ভার স্টেটকে সিঙ্ক্রোনাইজড (synchronized) রাখে এবং আপনাকে ম্যানুয়ালি স্টেট আপডেট করার জটিলতা থেকে মুক্তি দেয়।


আমরা এখন ডেটা পড়া (useQuery) এবং ডেটা পরিবর্তন করা (useMutation) উভয়ই শিখেছি। পরবর্তী পর্বে আমরা Query Keys নিয়ে আরও গভীরে আলোচনা করব এবং দেখব কীভাবে আরও জটিল ও নির্ভরশীল কোয়েরি (dependent queries) তৈরি করা যায়। আপনার কেমন লাগছে, জানাবেন!

অবশ্যই! চলুন, পরবর্তী পর্বে যাওয়া যাক। আমরা এখন "কোয়েরি কী" (Query Keys) সম্পর্কে আরও বিস্তারিত জানব। এটি TanStack Query-এর সবচেয়ে গুরুত্বপূর্ণ ধারণাগুলোর একটি।


পর্ব ৫: কোয়েরি কী (Query Keys) - গভীরে প্রবেশ

আমরা আগের পর্বগুলোতে দেখেছি যে useQuery এবং অন্যান্য হুকের জন্য queryKey কতটা জরুরি। TanStack Query এই queryKey-এর উপর ভিত্তি করেই ডেটা ক্যাশ করে, কখন ডেটা রি-ফেচ করতে হবে তা নির্ধারণ করে এবং অ্যাপ্লিকেশনের বিভিন্ন অংশ থেকে নির্দিষ্ট ডেটাকে খুঁজে বের করে।

আজ আমরা queryKey-এর গঠন এবং এর সঠিক ব্যবহার নিয়ে আলোচনা করব।

Query Key হলো একটি অ্যারে (Array)

সবসময় মনে রাখবেন, একটি queryKey হলো একটি অ্যারে। এই অ্যারের ভেতরের উপাদানগুলো সিরিয়ালযোগ্য (serializable) হতে হবে, অর্থাৎ সেগুলোকে সহজে টেক্সট-এ রূপান্তর করা যায়।

এই অ্যারের গঠন সম্পূর্ণ আপনার উপর নির্ভরশীল, তবে কিছু সাধারণ নিয়ম মেনে চললে আপনার কোড অনেক বেশি গোছানো এবং কার্যকরী হবে।

১. সরল তালিকা (Simple Lists): যখন আপনি একটি রিসোর্সের পুরো তালিকা ফেচ করছেন, তখন একটি সাধারণ স্ট্রিং সহ অ্যারে ব্যবহার করুন।

  • উদাহরণ: সকল todos আনার জন্য।
    useQuery({ queryKey: ["todos"], queryFn: fetchTodos });
  • উদাহরণ: সকল posts আনার জন্য।
     

... ```

২. নির্দিষ্ট আইটেম (Individual Items): যখন আপনি তালিকা থেকে একটি নির্দিষ্ট আইটেম তার আইডি (ID) বা অন্য কোনো ইউনিক ভ্যালু দিয়ে ফেচ করছেন, তখন সেই ভ্যালুটি অ্যারেতে যোগ করুন।

  • উদাহরণ: একটি নির্দিষ্ট todo তার id দিয়ে আনার জন্য।
    const todoId = 5;
    useQuery({
      queryKey: ["todos", todoId],
      queryFn: () => fetchTodoById(todoId),
    });
    এখানে, কী-টি হলো ['todos', 5]

৩. ফিল্টার বা প্যারামিটারসহ কোয়েরি (Queries with Filters): অনেক সময় আমাদের বিভিন্ন প্যারামিটার (যেমন: search, filter, page number) দিয়ে ডেটা আনতে হয়। এই ক্ষেত্রে, প্যারামিটারগুলোকে একটি অবজেক্ট হিসেবে অ্যারেতে যোগ করা ভালো।

  • উদাহরণ: "done" স্ট্যাটাসের todos তালিকা আনার জন্য।
    useQuery({
      queryKey: ["todos", { status: "done", page: 2 }],
      queryFn: () => fetchTodos({ status: "done", page: 2 }),
    });
    • গুরুত্বপূর্ণ: অবজেক্টের ভেতরের কী-এর ক্রম (order) কোনো প্রভাব ফেলে না। অর্থাৎ, ['todos', { page: 2, status: 'done' }] এবং ['todos', { status: 'done', page: 2 }] একই কোয়েরি কী হিসেবে বিবেচিত হবে। TanStack Query এটি নিজে থেকেই সামলে নেয়।

কেন এই গঠন এত গুরুত্বপূর্ণ?

এই ধরনের গঠনবদ্ধ (structured) কী ব্যবহার করার প্রধান কারণ হলো কোয়েরি ইনভ্যালিডেশন (invalidateQueries) এবং ক্যাশ আপডেট (setQueryData) করার সময় নির্দিষ্ট ডেটাকে সহজে টার্গেট করা।

queryClient.invalidateQueries ফাংশনটি আংশিক কী (partial key) দিয়েও কাজ করতে পারে।

ভাবুন, আপনি একটি নতুন todo যোগ করেছেন। এখন তো todos-এর পুরো তালিকাটি আপডেট করা দরকার, তাই না?

const queryClient = useQueryClient();
 
// নতুন todo যোগ করার মিউটেশন
const addTodoMutation = useMutation({
  mutationFn: createTodo,
  onSuccess: () => {
    // এখানে ম্যাজিকটা ঘটছে!
    queryClient.invalidateQueries({ queryKey: ["todos"] });
  },
});

যখন invalidateQueries({ queryKey: ['todos'] }) কল করা হয়, তখন TanStack Query এমন সমস্ত কোয়েরিকে stale হিসেবে চিহ্নিত করবে যাদের queryKey অ্যারেটি ['todos'] দিয়ে শুরু হয়েছে।

এর ফলে নিচের সবগুলো কোয়েরি রি-ফেচ হবে:

  • ['todos'] (সকল todos এর তালিকা)
  • ['todos', 5] (একটি নির্দিষ্ট todo)
  • ['todos', { status: 'done' }] (ফিল্টার করা তালিকা)
  • ['todos', { status: 'pending', page: 3 }] (অন্য একটি ফিল্টার করা ও পেইজিনেটেড তালিকা)

কিন্তু ['posts'] বা অন্য কোনো কী-এর কোয়েরির উপর এর কোনো প্রভাব পড়বে না। এই কারণেই একটি সুন্দর এবং হায়ারারকিক্যাল (hierarchical) কী-স্ট্রাকচার ব্যবহার করা খুবই জরুরি। এটি আপনার ডেটা ম্যানেজমেন্টকে অনেক সহজ ও শক্তিশালী করে তোলে।

কোয়েরি কী ফ্যাক্টরি (Query Key Factories)

বড় অ্যাপ্লিকেশনে বারবার manuall-ভাবে ['todos', id] বা ['todos', { status }] লেখার ফলে টাইপো বা ভুল হওয়ার সম্ভাবনা থাকে। এই সমস্যা সমাধানের জন্য Query Key Factory একটি চমৎকার প্যাটার্ন।

এটি আর কিছুই নয়, একটি অবজেক্ট যেখানে আমরা আমাদের কোয়েরি কী তৈরি করার জন্য কিছু হেল্পার ফাংশন গুছিয়ে রাখি।

উদাহরণ:

// queryKeys.js
export const todoKeys = {
  all: ["todos"],
  lists: () => [...todoKeys.all, "list"],
  list: (filters) => [...todoKeys.lists(), filters], // { status: 'done' }
  details: () => [...todoKeys.all, "detail"],
  detail: (id) => [...todoKeys.details(), id],
};
 
// কম্পোনেন্টে ব্যবহার
import { todoKeys } from "./queryKeys";
import { useQuery, useQueryClient } from "@tanstack/react-query";
 
// একটি todo-র বিস্তারিত তথ্য আনা
function TodoDetails({ todoId }) {
  const { data } = useQuery({
    queryKey: todoKeys.detail(todoId), // ['todos', 'detail', todoId]
    queryFn: () => fetchTodoById(todoId),
  });
  // ...
}
 
// মিউটেশন থেকে ইনভ্যালিডেট করা
function AddTodo() {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: createTodo,
    onSuccess: () => {
      // শুধু তালিকাগুলো ইনভ্যালিডেট করা হচ্ছে
      queryClient.invalidateQueries({ queryKey: todoKeys.lists() });
    },
  });
  // ...
}

এই প্যাটার্নটি ব্যবহার করলে:

  • আপনার কোয়েরি কীগুলো এক জায়গায় গোছানো থাকে।
  • ভুল হওয়ার সম্ভাবনা কমে যায়।
  • ইনভ্যালিডেশন এবং ক্যাশ আপডেট করা আরও সহজ হয়।

এই পর্বে আমরা কোয়েরি কী-এর গঠন এবং এর কার্যকরী ব্যবহার সম্পর্কে বিস্তারিত জানলাম। আশা করি, এখন আপনি আরও আত্মবিশ্বাসের সাথে queryKey ডিজাইন করতে পারবেন। পরবর্তী পর্বে আমরা "Infinite Queries" বা অসীম স্ক্রোলিং নিয়ে আলোচনা করব। আপনার কোনো প্রশ্ন থাকলে জানাতে পারেন!

চমৎকার! আমরা এখন TanStack Query-এর আরও একটি শক্তিশালী ফিচার নিয়ে আলোচনা করব। এই পর্বটি খুবই মজার এবং অ্যাপ্লিকেশন তৈরিতে খুব কাজে লাগে।


পর্ব ৬: ইনফিনিট কোয়েরি (Infinite Queries) - অসীম স্ক্রোল এবং আরও অনেক কিছু

আধুনিক ওয়েব অ্যাপ্লিকেশনগুলোতে "Infinite Scrolling" বা "Load More" বাটন খুবই সাধারণ একটি ফিচার। যেমন, আপনি যখন ফেসবুক বা টুইটারে স্ক্রোল করতে থাকেন, নতুন পোস্ট স্বয়ংক্রিয়ভাবে লোড হতে থাকে। অথবা কোনো ব্লগের শেষে "আরও পোস্ট দেখুন" বাটন থাকে।

এই ধরনের UI তৈরি করার জন্য TanStack Query আমাদের একটি বিশেষ হুক দেয়, যার নাম useInfiniteQuery

useInfiniteQuery কখন ব্যবহার করবেন?

useInfiniteQuery ব্যবহার করা হয় যখন আপনার ডেটার তালিকা সময়ের সাথে বাড়তে থাকে, এবং আপনি চান যে ব্যবহারকারীর কোনো অ্যাকশনের ভিত্তিতে ডেটার নতুন "পৃষ্ঠা" (page) আগের পৃষ্ঠাগুলোর সাথে যুক্ত হোক।

এটি useQuery দিয়ে করা সাধারণ পেইজিনেশন (pagination) থেকে ভিন্ন। সাধারণ পেইজিনেশনে আপনি একবারে একটি পৃষ্ঠা দেখেন (যেমন: Page 1, Page 2...), কিন্তু ইনফিনিট কোয়েরিতে আপনি সমস্ত লোড করা পৃষ্ঠা একসাথে দেখেন।

useQuery থেকে useInfiniteQuery-এর পার্থক্য

useInfiniteQuery-এর গঠন useQuery-এর মতোই, তবে কয়েকটি গুরুত্বপূর্ণ পার্থক্য ও নতুন অপশন রয়েছে:

  1. queryFn: এই ফাংশনটি এখন একটি অবজেক্ট রিসিভ করে যার মধ্যে pageParam নামে একটি প্রপার্টি থাকে। এই pageParam ব্যবহার করেই আপনি API থেকে নির্দিষ্ট পৃষ্ঠা বা কার্সরের (cursor) ডেটা আনবেন।

  2. initialPageParam: এটি একটি আবশ্যক (required) অপশন। এখানে আপনি প্রথম পৃষ্ঠার জন্য pageParam-এর মান নির্ধারণ করেন। যেমন, প্রথম পৃষ্ঠার নম্বর হতে পারে 1 বা 0

  3. getNextPageParam: এটি useInfiniteQuery-এর সবচেয়ে গুরুত্বপূর্ণ অংশ। এটি একটি ফাংশন যা দুটি প্যারামিটার পায়: lastPage (সর্বশেষ লোড হওয়া পৃষ্ঠা) এবং allPages (এখন পর্যন্ত লোড হওয়া সকল পৃষ্ঠা)। এই ফাংশনের কাজ হলো পরবর্তী পৃষ্ঠার জন্য pageParam কী হবে, তা রিটার্ন করা।

    • যদি লোড করার মতো আরও পৃষ্ঠা থাকে, তবে আপনি পরবর্তী পৃষ্ঠার pageParam রিটার্ন করবেন (যেমন: একটি কার্সর বা পৃষ্ঠা নম্বর)।
    • যদি আর কোনো পৃষ্ঠা না থাকে, তবে আপনাকে অবশ্যই null বা undefined রিটার্ন করতে হবে। এটি TanStack Query-কে জানায় যে ডেটা লোড করা শেষ।
  4. getPreviousPageParam: এটি getNextPageParam-এর বিপরীত। এটি דו-মুখী (bi-directional) তালিকার জন্য ব্যবহৃত হয়, যেমন একটি চ্যাট অ্যাপ্লিকেশন যেখানে আপনি উপরের দিকে স্ক্রোল করে পুরনো মেসেজ লোড করতে চান।

ডেটার গঠন এবং রিটার্ন করা ভ্যালু

useInfiniteQuery থেকে পাওয়া data অবজেক্টের গঠন useQuery থেকে কিছুটা ভিন্ন:

  • data.pages: এটি একটি অ্যারে, যেখানে প্রতিটি উপাদান হলো এক একটি পৃষ্ঠার ডেটা। অর্থাৎ এটি একটি দ্বি-মাত্রিক অ্যারে (array of arrays)। যেমন: [ [post1, post2], [post3, post4] ]
  • data.pageParams: এটিও একটি অ্যারে, যেখানে প্রতিটি পৃষ্ঠার জন্য ব্যবহৃত pageParam গুলো সংরক্ষিত থাকে।

এছাড়াও, useInfiniteQuery কিছু নতুন এবং দরকারি স্টেট ও ফাংশন রিটার্ন করে:

  • fetchNextPage: এই ফাংশনটি কল করলে পরবর্তী পৃষ্ঠা লোড হয়।
  • hasNextPage: এটি একটি বুলিয়ান (true/false)। getNextPageParam যদি null বা undefined রিটার্ন না করে, তাহলে এর মান true থাকে। এটি "Load More" বাটনটি কখন দেখাবেন বা ডিজেবল করবেন, তা নির্ধারণের জন্য খুব দরকারি।
  • isFetchingNextPage: পরবর্তী পৃষ্ঠা লোড হওয়ার সময় এটি true থাকে। এটি দিয়ে আপনি একটিเฉพาะ লোডার দেখাতে পারেন।
  • fetchPreviousPage, hasPreviousPage, isFetchingPreviousPage ইত্যাদিও রয়েছে דו-মুখী তালিকার জন্য।

উদাহরণ: "Load More" বাটন দিয়ে ইনফিনিট কোয়েরি

চলুন, কিছু প্রজেক্টের একটি তালিকা লোড করি যেখানে প্রতি পৃষ্ঠায় ৩টি করে আইটেম থাকবে।

import React from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
import axios from "axios";
 
// আমাদের API ফাংশন, যা pageParam (এখানে কার্সর) গ্রহণ করে
const fetchProjects = async ({ pageParam = 0 }) => {
  const res = await axios.get(`/api/projects?cursor=${pageParam}`);
  return res.data;
};
 
function Projects() {
  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: ["projects"],
    queryFn: fetchProjects,
    initialPageParam: 0,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    // API থেকে আসা ডেটার ফরম্যাট: { projects: [...], nextCursor: 5 }
  });
 
  if (status === "pending") {
    return <p>Loading...</p>;
  }
 
  if (status === "error") {
    return <p>Error: {error.message}</p>;
  }
 
  return (
    <>
      {data.pages.map((group, i) => (
        <React.Fragment key={i}>
          {group.projects.map((project) => (
            <p key={project.id}>{project.name}</p>
          ))}
        </React.Fragment>
      ))}
      <div>
        <button
          onClick={() => fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
        >
          {isFetchingNextPage
            ? "লোড হচ্ছে..."
            : hasNextPage
            ? "আরও লোড করুন"
            : "দেখানোর মতো আর কিছু নেই"}
        </button>
      </div>
      <div>{isFetching && !isFetchingNextPage ? "রি-ফেচ হচ্ছে..." : null}</div>
    </>
  );
}

কী ঘটছে এখানে?

১. queryFn: fetchProjects ফাংশনটি একটি pageParam নেয় (ডিফল্ট 0) এবং API-কে সেই কার্সরসহ কল পাঠায়। ২. initialPageParam: 0: আমরা প্রথম পৃষ্ঠা লোড করার জন্য কার্সরের মান 0 দিয়ে শুরু করেছি। ৩. getNextPageParam: ধরা যাক, আমাদের API প্রতিটি রেসপন্সের সাথে nextCursor নামে একটি প্রপার্টি পাঠায়। আমরা সেই nextCursor-কেই পরবর্তী পৃষ্ঠার pageParam হিসেবে রিটার্ন করছি। যদি nextCursor না থাকে (অর্থাৎ null বা undefined হয়), তাহলে hasNextPage-এর মান false হয়ে যাবে। ৪. রেন্ডারিং: আমরা data.pages-এর উপর ম্যাপ করছি। যেহেতু data.pages নিজেই একটি অ্যারে, তাই এর প্রতিটি group-এর ভেতরের projects অ্যারের উপর আবার ম্যাপ করে আমরা সব আইটেম দেখাচ্ছি। ৫. বাটন: "Load More" বাটনটি fetchNextPage ফাংশনটিকে কল করে। বাটনটি ডিজেবল হয়ে যায় যখন hasNextPage false থাকে অথবা যখন পরবর্তী পৃষ্ঠা লোড হতে থাকে (isFetchingNextPage is true)।


useInfiniteQuery একটি অত্যন্ত শক্তিশালী হুক যা ব্যবহারকারীর অভিজ্ঞতাকে অনেক উন্নত করে। পরবর্তী পর্বে আমরা আলোচনা করব কীভাবে কোয়েরি ডিজেবল বা শর্তসাপেক্ষে চালানো যায় (Disabling/Pausing Queries)। আপনার কোনো প্রশ্ন থাকলে অবশ্যই করুন!

অবশ্যই! আপনার অনুরোধ অনুযায়ী, TanStack Query-এর অফিশিয়াল ডকুমেন্টেশন গভীরভাবে বিশ্লেষণ করে পরবর্তী পর্বটি তৈরি করছি।


পর্ব ৭: কোয়েরি ডিজেবল বা পজ করা (Disabling/Pausing Queries)

অনেক সময় আমাদের এমন পরিস্থিতি আসে যখন আমরা চাই না যে একটি কোয়েরি কম্পোনেন্ট মাউন্ট হওয়ার সাথে সাথেই চলুক। হতে পারে, কোয়েরিটি অন্য কোনো ডেটার উপর নির্ভরশীল অথবা ব্যবহারকারীর কোনো ইনপুটের জন্য অপেক্ষা করছে।

এই ধরনের ক্ষেত্রে, কোয়েরিকে শর্তসাপেক্ষে চালানো বা ডিজেবল করে রাখা একটি অত্যন্ত গুরুত্বপূর্ণ ফিচার। TanStack Query এই কাজটি করার জন্য enabled অপশনটি প্রদান করে।

enabled অপশন

useQuery এবং useInfiniteQuery উভয় হুক-ই একটি enabled অপশন গ্রহণ করে, যার মান true অথবা false হতে পারে।

  • ডিফল্টরূপে, enabled-এর মান true থাকে, যার কারণে কোয়েরি স্বয়ংক্রিয়ভাবে রান হয়।
  • আপনি যদি enabled: false সেট করেন, তাহলে কোয়েরিটি প্রাথমিকভাবে চলবে না।

একটি ডিজেবলড (disabled) কোয়েরির অবস্থা:

যখন একটি কোয়েরিকে enabled: false দিয়ে নিষ্ক্রিয় করা হয়:

  1. কোয়েরিটি প্রাথমিকভাবে status: 'pending' এবং fetchStatus: 'idle' অবস্থায় থাকবে।
  2. এর data undefined থাকবে।
  3. এটি স্বয়ংক্রিয়ভাবে ডেটা ফেচ করবে না, যেমন:
    • কম্পোনেন্ট মাউন্ট হওয়ার সময়।
    • উইন্ডো ফোকাসে।
    • নেটওয়ার্ক পুনরায় সংযুক্ত হলে।
    • refetch() কল করলেও এটি কাজ করবে না, যতক্ষণ না enabled true হয়।

কোয়েরিটি কেবল তখনই চলবে যখন enabled-এর মান পরিবর্তন হয়ে true হবে।

ব্যবহারিক উদাহরণ: নির্ভরশীল কোয়েরি (Dependent Queries)

enabled অপশনের সবচেয়ে সাধারণ এবং শক্তিশালী ব্যবহার হলো নির্ভরশীল কোয়েরি তৈরি করা। অর্থাৎ, একটি কোয়েরি তখনই চলবে যখন অন্য একটি কোয়েরি থেকে সফলভাবে ডেটা পাওয়া যাবে।

দৃশ্যকল্প:

ভাবুন, আপনার একটি ড্রপডাউন আছে যেখানে ব্যবহারকারীদের তালিকা দেখানো হয়। ব্যবহারকারী যখন একজন ইউজারকে সিলেক্ট করবে, কেবল তখনই আপনি সেই ইউজারের অধীনে থাকা প্রজেক্টগুলোর তালিকা API থেকে ফেচ করতে চান।

এই ক্ষেত্রে, "প্রজেক্ট তালিকা" কোয়েরিটি "ইউজার আইডি"-এর উপর নির্ভরশীল। যতক্ষণ ইউজার আইডি সিলেক্ট না হচ্ছে, ততক্ষণ এই কোয়েরিটি চালানোর কোনো মানে হয় না।

উদাহরণ কোড:

import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
 
// API থেকে একজন নির্দিষ্ট ব্যবহারকারীর প্রজেক্ট আনে
const fetchUserProjects = async (userId) => {
  if (!userId) return []; // userId না থাকলে খালি অ্যারে রিটার্ন করি
  const response = await fetch(
    `https://api.example.com/users/${userId}/projects`
  );
  return response.json();
};
 
function UserProjects() {
  const [selectedUserId, setSelectedUserId] = useState(null);
 
  // ব্যবহারকারীদের তালিকা আনার জন্য প্রথম কোয়েরি
  const { data: users, isLoading: usersLoading } = useQuery({
    queryKey: ["users"],
    queryFn: async () => {
      const response = await fetch("https://api.example.com/users");
      return response.json();
    },
  });
 
  // ব্যবহারকারীর প্রজেক্ট আনার জন্য দ্বিতীয়, নির্ভরশীল কোয়েরি
  const {
    status,
    data: projects,
    isFetching,
  } = useQuery({
    queryKey: ["projects", selectedUserId],
    queryFn: () => fetchUserProjects(selectedUserId),
    // মূল জাদুটা এখানেই!
    enabled: !!selectedUserId, // selectedUserId truthy হলেই কেবল কোয়েরিটি চলবে
  });
 
  if (usersLoading) return <p>ব্যবহারকারীদের তালিকা লোড হচ্ছে...</p>;
 
  return (
    <div>
      {/* ব্যবহারকারী সিলেক্ট করার ড্রপডাউন */}
      <select
        value={selectedUserId || ""}
        onChange={(e) => setSelectedUserId(e.target.value)}
      >
        <option value="">একজন ব্যবহারকারী সিলেক্ট করুন</option>
        {users?.map((user) => (
          <option key={user.id} value={user.id}>
            {user.name}
          </option>
        ))}
      </select>
 
      <hr />
 
      {/* প্রজেক্টের তালিকা দেখানো হচ্ছে */}
      <h3>প্রজেক্টসমূহ:</h3>
      {status === "pending" && <p>একজন ব্যবহারকারী সিলেক্ট করুন...</p>}
      {status === "success" && (
        <ul>
          {projects.map((project) => (
            <li key={project.id}>{project.name}</li>
          ))}
        </ul>
      )}
      {isFetching && <p>প্রজেক্ট লোড হচ্ছে...</p>}
    </div>
  );
}

কী ঘটছে এখানে?

১. প্রথম কোয়েরি: 'users' কোয়েরিটি কোনো শর্ত ছাড়াই চলে এবং ব্যবহারকারীদের একটি তালিকা নিয়ে আসে।

২. দ্বিতীয় কোয়েরি: 'projects' কোয়েরিটি selectedUserId-এর উপর নির্ভরশীল। * আমরা enabled: !!selectedUserId ব্যবহার করেছি। !! (ডাবল ব্যাং) একটি মানকে বুলিয়ানে রূপান্তরিত করে। * প্রাথমিকভাবে selectedUserId এর মান null, তাই !!selectedUserId এর মান হয় false। একারণে প্রজেক্ট কোয়েরিটি চলে না এবং pending অবস্থায় থাকে। * যখন ব্যবহারকারী ড্রপডাউন থেকে একজন ইউজারকে সিলেক্ট করে, setSelectedUserId কল হয় এবং selectedUserId-এর একটি মান সেট হয় (যেমন, '1')। * রি-রেন্ডারে, !!selectedUserId এর মান true হয়ে যায়। * TanStack Query এই পরিবর্তন সনাক্ত করে এবং স্বয়ংক্রিয়ভাবে 'projects' কোয়েরিটিকে ট্রিগার করে।

এই enabled অপশনটি ব্যবহারের মাধ্যমে আপনি অপ্রয়োজনীয় API কল এড়াতে পারেন এবং আপনার অ্যাপ্লিকেশনের পারফর্ম্যান্স ও ব্যবহারকারীর অভিজ্ঞতা দুটোই উন্নত করতে পারেন।


এই পর্বে আমরা শিখলাম কীভাবে একটি কোয়েরিকে শর্তসাপেক্ষে চালাতে হয়। পরবর্তী পর্বে আমরা "প্লেস-হোল্ডার এবং ইনিশিয়াল ডেটা" (Placeholder and Initial Data) নিয়ে আলোচনা করব, যা UI-কে আরও মসৃণ এবং ব্যবহারকারী-বান্ধব করতে সাহায্য করে। আপনার মতামত জানাতে ভুলবেন না!

চমৎকার! আপনার আগ্রহ এবং ধারাবাহিকতা প্রশংসার যোগ্য। চলুন, আমরা TanStack Query-এর অফিশিয়াল ডকুমেন্টেশনের আলোকে পরবর্তী পর্বটি গভীরভাবে বিশ্লেষণ করে লিখি। এই পর্বটি আপনার অ্যাপ্লিকেশনের ব্যবহারকারীর অভিজ্ঞতাকে (User Experience) এক নতুন উচ্চতায় নিয়ে যাবে।


পর্ব ৮: প্লেস-হোল্ডার এবং ইনিশিয়াল ডেটা (Placeholder and Initial Data)

যখন ব্যবহারকারী আপনার অ্যাপ্লিকেশনের এক পৃষ্ঠা থেকে অন্য পৃষ্ঠায় যায় অথবা কোনো ফিল্টার পরিবর্তন করে, তখন নতুন ডেটা লোড হওয়ার জন্য একটি সংক্ষিপ্ত কিন্তু заметный (noticeable) লোডিং অবস্থা তৈরি হয়। ব্যবহারকারীকে একটি স্পিনার বা একটি খালি স্ক্রিন দেখতে হয়, যা একটি ঝাঁকুনি (flicker) বা বাধা বলে মনে হতে পারে।

এই অভিজ্ঞতাকে মসৃণ করার জন্য TanStack Query দুটি অসাধারণ কৌশল প্রদান করে: placeholderData এবং initialData

১. প্লেস-হোল্ডার ডেটা (placeholderData)

placeholderData কী?

placeholderData হলো এমন একটি অস্থায়ী ডেটা যা আপনি একটি কোয়েরির আসল ডেটা লোড হওয়ার সময় ব্যবহারকারীকে দেখাতে পারেন। এর মূল উদ্দেশ্য হলো UI-এর গঠন ঠিক রাখা এবং একটি "হার্ড লোডিং" অবস্থা এড়ানো।

গুরুত্বপূর্ণ বৈশিষ্ট্য:

  • এটি ক্যাশে সংরক্ষিত হয় না: placeholderData শুধুমাত্র একটি ভিজ্যুয়াল সাহায্য। এটি ['posts', 1]-এর মতো কোনো নির্দিষ্ট কোয়েরি কী-এর অধীনে ক্যাশ (cache) করা হয় না। আসল ডেটা এসে পৌঁছালে এটি প্রতিস্থাপিত হয়ে যায়।
  • কোয়েরির অবস্থা: যখন placeholderData দেখানো হয়, তখন কোয়েরির status success থাকে, কিন্তু একই সাথে একটি নতুন ফ্ল্যাগ isPlaceholderData true হয়। এটি আপনাকে বুঝতে সাহায্য করে যে, দেখানো ডেটাটি আসল নয়।

কখন ব্যবহার করবেন?

যখন আপনার কাছে এমন ডেটা থাকে যা নতুন ডেটার জন্য একটি ভালো "অনুমান" বা কাছাকাছি কিছু, কিন্তু পুরোপুরি সঠিক নয়।

উদাহরণ: একটি ব্লগ অ্যাপ্লিকেশন

ভাবুন, আপনার একটি ব্লগের পোস্টের তালিকা আছে। ব্যবহারকারী যখন একটি নতুন পোস্টের শিরোনামে ক্লিক করে, তখন নতুন পোস্টের ডেটা লোড হওয়ার আগ পর্যন্ত আপনি আগের পোস্টের ডেটাকেই প্লেস-হোল্ডার হিসেবে দেখাতে পারেন। এতে UI-এর লেআউট ভেঙে পড়বে না।

import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
 
function BlogPost({ postId }) {
  const queryClient = useQueryClient();
 
  const { data: post, isPlaceholderData } = useQuery({
    queryKey: ["posts", postId],
    queryFn: () => fetchPost(postId),
    placeholderData: () => {
      // অন্য কোনো কোয়েরির ক্যাশ থেকে ডেটা খুঁজে বের করার চেষ্টা করা
      // যেমন, সকল পোস্টের তালিকা থেকে এই পোস্টটি খুঁজে বের করা
      const allPosts = queryClient.getQueryData(["posts", "all"]);
      if (allPosts) {
        return allPosts.find((p) => p.id === postId);
      }
      // অথবা, আগের দেখানো পোস্টের ডেটা ব্যবহার করা
      return queryClient.getQueryData(["posts", postId - 1]);
    },
    // প্লেস-হোল্ডার ডেটা অস্বচ্ছ (opaque) করার জন্য স্টাইল
    style: {
      opacity: isPlaceholderData ? 0.5 : 1,
    },
  });
 
  // ... বাকি কোড
}

এই উদাহরণে, নতুন postId-এর জন্য কোয়েরি করার সময়, আমরা placeholderData ফাংশনের মাধ্যমে হয় সম্পূর্ণ পোস্টের তালিকা থেকে অথবা আগের পোস্টের ক্যাশ করা ডেটা থেকে একটি প্লেস-হোল্ডার খুঁজে বের করার চেষ্টা করছি।

২. ইনিশিয়াল ডেটা (initialData)

initialData কী?

initialData হলো এমন ডেটা যা দিয়ে আপনি একটি কোয়েরির ক্যাশকে "প্রি-পপুলেট" বা আগে থেকেই পূরণ করে রাখতে পারেন, যদি সেই ক্যাশটি খালি থাকে।

গুরুত্বপূর্ণ বৈশিষ্ট্য:

  • এটি ক্যাশে সংরক্ষিত হয়: placeholderData-এর বিপরীতে, initialData কে আসল ডেটা হিসেবে গণ্য করা হয় এবং এটিকে কোয়েরি কী-এর অধীনে ক্যাশে রাখা হয়।
  • ডেটা fresh হিসেবে গণ্য হয়: যখন আপনি initialData প্রদান করেন, TanStack Query ধরে নেয় যে এই ডেটাটি একদম নতুন বা fresh
  • staleTime-এর ভূমিকা: initialData দেওয়ার পর এটি কতক্ষণ fresh থাকবে, তা staleTime অপশনের উপর নির্ভর করে।
    • যদি আপনি staleTime সেট না করেন (ডিফল্ট মান 0), তবে ডেটাটি ক্যাশে রাখার সাথে সাথেই stale (পুরানো) হয়ে যাবে এবং ব্যাকগ্রাউন্ডে একটি রি-ফেচ ট্রিগার হবে।
    • যদি আপনি একটি তাৎক্ষণিক রি-ফেচ এড়াতে চান, তবে আপনাকে অবশ্যই একটি পজিটিভ staleTime দিতে হবে (যেমন: staleTime: 60000 // ১ মিনিট)।

কখন ব্যবহার করবেন?

  • সার্ভার-সাইড রেন্ডারিং (SSR): সার্ভার থেকে পেজের সাথে কিছু ডেটা পাঠিয়ে দিলে, সেই ডেটাকে initialData হিসেবে ব্যবহার করে ক্লায়েন্ট সাইডে অপ্রয়োজনীয় API কল এড়ানো যায়।
  • পূর্ববর্তী কোয়েরি থেকে ডেটা ব্যবহার: যখন আপনার কাছে একটি তালিকা থেকে একটি আইটেমের সম্পূর্ণ ডেটা আগে থেকেই থাকে। যেমন, /posts API থেকে পাওয়া তালিকা থেকে একটি নির্দিষ্ট পোস্টের ডেটা /posts/1-এর জন্য initialData হিসেবে ব্যবহার করা।

উদাহরণ:

function PostDetails({ postId }) {
  const { data } = useQuery({
    queryKey: ["posts", postId],
    queryFn: () => fetchPost(postId),
    // ধরা যাক, `getPostFromListCache` ফাংশনটি
    // সকল পোস্টের তালিকা থেকে এই পোস্টের ডেটা বের করে আনে
    initialData: () => getPostFromListCache(postId),
    staleTime: 1000 * 60, // ১ মিনিট ডেটাকে fresh রাখা হবে
  });
  // ...
}

placeholderData বনাম initialData: মূল পার্থক্য

বৈশিষ্ট্যplaceholderDatainitialData
ক্যাশিংক্যাশে সংরক্ষিত হয় নাক্যাশে সংরক্ষিত হয়
ডেটার ধরণঅস্থায়ী, ভিজ্যুয়াল এইডআসল, fresh ডেটা হিসেবে গণ্য
ফ্ল্যাগisPlaceholderData true হয়isPlaceholderData false থাকে
staleTimeপ্রযোজ্য নয়তাৎক্ষণিক রি-ফেচ এড়ানোর জন্য জরুরি
ব্যবহারস্মুথ UI ট্রানজিশন, লেআউট ধরে রাখাSSR, ক্যাশ প্রি-পপুলেশন

এই দুটি কৌশল সঠিকভাবে ব্যবহার করলে আপনার অ্যাপ্লিকেশন শুধু দ্রুতই হবে না, বরং ব্যবহারকারীর কাছে অনেক বেশি সাবলীল এবং পেশাদার মনে হবে। পরবর্তী পর্বে আমরা "পেইজিনেশন এবং keepPreviousData" নিয়ে আলোচনা করব, যা placeholderData-এর একটি বিশেষ এবং শক্তিশালী রূপ। আপনার মতামত দিন!

অবশ্যই! আপনার অনুরোধে, TanStack Query-এর অফিশিয়াল ডকুমেন্টেশন পুঙ্খানুপুঙ্খভাবে বিশ্লেষণ করে, আমরা পরবর্তী পর্বে প্রবেশ করছি। এই পর্বটি ব্যবহারকারীর অভিজ্ঞতাকে আরও মসৃণ করার একটি অসাধারণ কৌশল নিয়ে আলোচনা করবে।


পর্ব ৯: পেইজিনেশন এবং keepPreviousData

ওয়েব অ্যাপ্লিকেশনে ডেটার বড় তালিকা দেখানোর জন্য পেইজিনেশন (Pagination) একটি অপরিহার্য কৌশল। যেমন, একটি ই-কমার্স সাইটে শত শত পণ্যের তালিকা একবারে না দেখিয়ে, পৃষ্ঠা (Page) অনুযায়ী ভাগ করে দেখানো হয়।

TanStack Query দিয়ে পেইজিনেশন করা খুবই সহজ, কিন্তু একটি সাধারণ সমস্যা হলো পৃষ্ঠা পরিবর্তনের সময় UI-তে একটি ঝাঁকুনি (flicker) দেখা যায়। যখন আপনি ২য় পৃষ্ঠা থেকে ৩য় পৃষ্ঠায় যান, তখন ৩য় পৃষ্ঠার ডেটা লোড হওয়ার আগ পর্যন্ত UI সাধারণত একটি লোডিং স্পিনার দেখায়। এটি ব্যবহারকারীর জন্য একটি বাধা সৃষ্টি করে।

এই সমস্যা সমাধানের জন্য TanStack Query একটি অত্যন্ত কার্যকর অপশন প্রদান করে, যার নাম keepPreviousData

keepPreviousData কী এবং কেন ব্যবহার করবেন?

keepPreviousData হলো useQuery-এর একটি বুলিয়ান (true/false) অপশন। যখন আপনি এটিকে true সেট করেন, তখন TanStack Query একটি চমৎকার কাজ করে:

এটি নতুন পৃষ্ঠার ডেটা লোড হওয়ার সময়, আগের পৃষ্ঠার সফলভাবে লোড হওয়া ডেটাটিকে UI-তে ধরে রাখে।

এর ফলে ব্যবহারকারী পৃষ্ঠা পরিবর্তনের সময় কোনো লোডিং স্পিনার বা খালি অবস্থা দেখে না। সে আগের পৃষ্ঠার ডেটা দেখতেই থাকে, আর ব্যাকগ্রাউন্ডে নতুন পৃষ্ঠার ডেটা লোড হতে থাকে। নতুন ডেটা এসে গেলে UI মসৃণভাবে আপডেট হয়ে যায়।

keepPreviousData-এর মূল বৈশিষ্ট্য:

  • মসৃণ ট্রানজিশন: এটি হার্ড লোডিং অবস্থাকে পুরোপুরি দূর করে দেয়, যা ব্যবহারকারীর অভিজ্ঞতাকে অনেক উন্নত করে।
  • isPreviousData ফ্ল্যাগ: যখন keepPreviousData সক্রিয় থাকে এবং আগের ডেটা দেখানো হয়, তখন useQuery একটি নতুন ফ্ল্যাগ রিটার্ন করে, যার নাম isPreviousData। এই ফ্ল্যাগটির মান true থাকে। আপনি এটি ব্যবহার করে UI-তে বোঝাতে পারেন যে দেখানো ডেটাটি আগের পৃষ্ঠার। যেমন, ডেটাগুলোকে কিছুটা স্বচ্ছ (opaque) করে দেওয়া।
  • ক্যাশিং: এই ফিচারটি ডেটা ক্যাশিংকে প্রভাবিত করে না। প্রতিটি পৃষ্ঠার ডেটা তার নিজস্ব queryKey (যেমন, ['projects', 2]) এর অধীনে সঠিকভাবে ক্যাশ করা হয়।

ব্যবহারিক উদাহরণ: একটি পেইজিনেটেড তালিকা

চলুন, একটি পেইজিনেটেড ব্লগ পোস্টের তালিকা তৈরি করি এবং keepPreviousData ব্যবহার করে এর অভিজ্ঞতাকে উন্নত করি।

import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
 
// API ফাংশন, যা পৃষ্ঠা নম্বর অনুযায়ী পোস্ট আনে
const fetchPosts = async (page) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${page}`
  );
  return response.json();
};
 
function PaginatedPosts() {
  const [page, setPage] = useState(1);
 
  const {
    status,
    data: posts,
    error,
    isFetching,
    isPreviousData, // এই ফ্ল্যাগটি আমরা ব্যবহার করব
  } = useQuery({
    queryKey: ["posts", page], // queryKey-তে পৃষ্ঠা নম্বর অন্তর্ভুক্ত করা জরুরি
    queryFn: () => fetchPosts(page),
    keepPreviousData: true, // ম্যাজিকটা এখানেই!
    staleTime: 5 * 60 * 1000, // ৫ মিনিটের জন্য ডেটাকে fresh রাখা
  });
 
  if (status === "pending") {
    return <div>লোড হচ্ছে...</div>;
  }
 
  if (status === "error") {
    return <div>একটি সমস্যা হয়েছে: {error.message}</div>;
  }
 
  return (
    <div>
      <h1>ব্লগ পোস্টসমূহ</h1>
      {/* isPreviousData সত্য হলে UI-কে স্টাইল করা হচ্ছে */}
      <div style={{ opacity: isPreviousData ? 0.6 : 1 }}>
        {posts.map((post) => (
          <p key={post.id}>{post.title}</p>
        ))}
      </div>
 
      <span>বর্তমান পৃষ্ঠা: {page}</span>
 
      {/* পেইজিনেশন বাটন */}
      <div>
        <button
          onClick={() => setPage((old) => Math.max(old - 1, 1))}
          disabled={page === 1}
        >
          পূর্ববর্তী পৃষ্ঠা
        </button>
        <button
          onClick={() => setPage((old) => old + 1)}
          // hasNextPage ডেটা না থাকলে বাটনটি নিষ্ক্রিয় করা যেতে পারে
          // এখানে আমরা একটি অনুমানের উপর ভিত্তি করে করছি
          disabled={posts.length < 10}
        >
          পরবর্তী পৃষ্ঠা
        </button>
      </div>
 
      {/* ব্যাকগ্রাউন্ডে ফেচিং হচ্ছে কি না, তা দেখানো */}
      {isFetching && <span> ব্যাকগ্রাউন্ডে লোড হচ্ছে...</span>}
    </div>
  );
}

কী ঘটছে এখানে?

  1. queryKey: ['posts', page]: আমরা queryKey-এর মধ্যে ডাইনামিক page নম্বরটি রেখেছি। এর ফলে page পরিবর্তনের সাথে সাথে TanStack Query একটি নতুন কোয়েরি ট্রিগার করে।
  2. keepPreviousData: true: যখন page ২ থেকে ৩ করা হয়, ৩য় পৃষ্ঠার জন্য ['posts', 3] কোয়েরিটি চলতে শুরু করে। কিন্তু isFetching অবস্থায়, UI ২য় পৃষ্ঠার posts-এর ডেটাটিই ধরে রাখে।
  3. isPreviousData: যখন ২য় পৃষ্ঠার ডেটা দেখানো হয় কিন্তু ৩য় পৃষ্ঠার জন্য রিকোয়েস্ট চলে, তখন isPreviousData-এর মান true হয়। আমরা এই ফ্ল্যাগটি ব্যবহার করে পোস্টের তালিকাটির opacity কমিয়ে দিয়েছি, যা ব্যবহারকারীকে একটি ভিজ্যুয়াল ইঙ্গিত দেয়।
  4. isFetching: এই ফ্ল্যাগটি পৃষ্ঠা পরিবর্তন এবং ব্যাকগ্রাউন্ড রি-ফেচ উভয় সময়েই true হয়। এটি ব্যবহার করে আমরা একটি ছোট "ব্যাকগ্রাউন্ডে লোড হচ্ছে..." মেসেজ দেখিয়েছি, যা মূল UI-কে ব্লক করে না।
  5. বাটন লজিক: আমরা পৃষ্ঠা নম্বর ১ হলে "পূর্ববর্তী" বাটন এবং শেষ পৃষ্ঠায় (অনুমান করে) "পরবর্তী" বাটন ডিজেবল করে দিয়েছি।

keepPreviousData ফিচারটি placeholderData-এর একটি বিশেষ এবং স্বয়ংক্রিয় রূপ, যা বিশেষভাবে পেইজিনেশনের জন্য তৈরি করা হয়েছে। এটি আপনার অ্যাপ্লিকেশনকে আরও বেশি রেসপন্সিভ এবং ব্যবহারকারী-বান্ধব করে তোলে।


পরবর্তী পর্বে আমরা TanStack Query-এর ডিবাগিং এবং ডেভেলপার অভিজ্ঞতা উন্নত করার জন্য তার সবচেয়ে শক্তিশালী টুল "React Query Devtools" নিয়ে আলোচনা করব। আপনার কোনো প্রশ্ন থাকলে জানান!

অবশ্যই। TanStack Query-এর গভীরে যাওয়ার এই যাত্রায় আমরা এখন এমন একটি টুল নিয়ে আলোচনা করব যা আপনার ডেভেলপমেন্টের অভিজ্ঞতাকে বহুগুণে বাড়িয়ে দেবে। অফিশিয়াল ডকুমেন্টেশন অনুযায়ী, এটি একটি অপরিহার্য অংশ।


পর্ব ১০: React Query Devtools - আপনার ডিবাগিং সুপারপাওয়ার 🦸‍♂️

আপনি যখন একটি বড় অ্যাপ্লিকেশন নিয়ে কাজ করবেন, তখন অসংখ্য কোয়েরি এবং মিউটেশন চলতে থাকবে। কোনটি কখন চলছে, কার ডেটা ক্যাশ (cache) করা আছে, কোনটি stale হয়ে গেছে, বা কোনটি এরর দিচ্ছে—এই সবকিছুর হিসাব রাখা কঠিন হয়ে পড়তে পারে।

এই সমস্যার সমাধান হলো React Query Devtools। এটি একটি ভিজ্যুয়াল টুল যা আপনাকে TanStack Query-এর ভেতরের সমস্ত কার্যকলাপ দেখতে এবং নিয়ন্ত্রণ করতে সাহায্য করে। এটিকে ডেভেলপারদের জন্য একটি সুপারপাওয়ার বলা যেতে পারে।

Devtools কী এবং কেন এটি আবশ্যক?

React Query Devtools হলো একটি আলাদা UI কম্পোনেন্ট যা আপনার অ্যাপ্লিকেশনের এক কোণে ভেসে থাকে (অথবা আপনি এটিকে একটি প্যানেল হিসেবেও যুক্ত করতে পারেন)। এটি আপনাকে লাইভ দেখতে দেয়:

  • আপনার অ্যাপ্লিকেশনের সমস্ত কোয়েরি এবং তাদের queryKey
  • প্রতিটি কোয়েরির বর্তমান অবস্থা (status)।
  • ক্যাশে থাকা ডেটা, যা আপনি সরাসরি ইন্সপেক্ট করতে পারেন।
  • একটি কোয়েরি কতজন Observer বা ব্যবহারকারী দ্বারা ব্যবহৃত হচ্ছে।

সবচেয়ে বড় সুবিধা হলো, আপনি শুধু দেখতেই পারবেন না, এখান থেকে সরাসরি বিভিন্ন অ্যাকশনও ট্রিগার করতে পারবেন, যেমন—রি-ফেচ, ইনভ্যালিডেট, বা রিসেট করা। এটি ডিবাগিং-এর সময়কে নাটকীয়ভাবে কমিয়ে আনে।

ইনস্টলেশন এবং সেটআপ

আমরা ইনস্টলেশন পর্বে এটি একবার দেখেছি, কিন্তু এর গুরুত্বের জন্য আবার উল্লেখ করছি।

১. প্যাকেজ ইনস্টল করুন:

npm install @tanstack/react-query-devtools
# অথবা
yarn add @tanstack/react-query-devtools

২. অ্যাপ্লিকেশনে যোগ করুন:

সাধারণত, Devtools শুধুমাত্র ডেভেলপমেন্ট মোডে ব্যবহার করা হয়। আপনার অ্যাপ্লিকেশনের রুট ফাইলে (App.jsx বা main.jsx), যেখানে আপনি QueryClientProvider ব্যবহার করেছেন, সেখানেই ReactQueryDevtools কম্পোনেন্টটি যোগ করুন।

import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
 
const queryClient = new QueryClient();
 
function App() {
  return (
    // QueryClientProvider দিয়ে আপনার অ্যাপকে র‍্যাপ করুন
    <QueryClientProvider client={queryClient}>
      {/* আপনার বাকি অ্যাপের কোড */}
      <MyApplication />
 
      {/* Devtools কম্পোনেন্টটি এখানে যোগ করুন */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

গুরুত্বপূর্ণ নোট: আধুনিক বিল্ড টুল (যেমন Vite, Next.js) স্বয়ংক্রিয়ভাবে প্রোডাকশন বিল্ড থেকে Devtools-কে বাদ দিয়ে দেয় (tree-shaking)। তাই এটি প্রোডাকশনে যাবে কি না, তা নিয়ে আপনার চিন্তা করার প্রয়োজন নেই।

initialIsOpen={false} প্রপসটি দিয়ে আমরা বলছি যে Devtools প্রাথমিকভাবে বন্ধ থাকবে। আপনি কোণার আইকনে ক্লিক করে এটি খুলতে পারবেন।

Devtools-এর ফিচার পরিচিতি

Devtools খোলার পর আপনি একটি প্যানেল দেখতে পাবেন যেখানে কয়েকটি কলামে তথ্য সাজানো থাকে:

  1. Query Key: এখানে আপনার প্রতিটি কোয়েরির ইউনিক কী দেখানো হয়। এটি দেখে আপনি সহজেই বুঝতে পারবেন কোন কোয়েরি কোন ডেটার জন্য।

  2. Status: এটি কোয়েরির বর্তমান অবস্থা দেখায় এবং রঙ দিয়ে সেগুলোকে আলাদা করে:

    • fresh (সবুজ): ডেটা নতুন এবং stale নয়। এর জন্য কোনো ব্যাকগ্রাউন্ড রি-ফেচ হবে না।
    • fetching (নীল): কোয়েরিটি বর্তমানে ডেটা আনছে।
    • stale (হলুদ): ডেটা ক্যাশে আছে, কিন্তু পুরানো হয়ে গেছে। পরবর্তীবার প্রয়োজন হলে এটি রি-ফেচ হবে।
    • inactive (ধূসর): এই কোয়েরিটি বর্তমানে কোনো কম্পোনেন্টে ব্যবহৃত হচ্ছে না। নির্দিষ্ট সময় পর এটি গার্বেজ কালেক্টেড (garbage collected) হয়ে যাবে।
    • error (লাল): কোয়েরিটি ফেচ করতে গিয়ে কোনো এরর হয়েছে।
  3. Actions: যেকোনো কোয়েরির উপর হোভার করলে বা ক্লিক করলে আপনি কিছু বাটন পাবেন:

    • Refetch: কোয়েরিটিকে ম্যানুয়ালি রি-ফেচ করার জন্য।
    • Invalidate: কোয়েরিটিকে stale হিসেবে মার্ক করার জন্য।
    • Reset: কোয়েরিটিকে তার প্রাথমিক অবস্থায় ফিরিয়ে নেওয়ার জন্য।
    • Remove: কোয়েরিটিকে ক্যাশ থেকে পুরোপুরি মুছে ফেলার জন্য।

ক্লিক করলে আপনি কোয়েরির Data Explorer, Query Explorer এবং Mutation Explorer দেখতে পাবেন, যেখানে ডেটা, queryKey এবং অন্যান্য বিস্তারিত তথ্য থাকে।

ফ্লোটিং মোড বনাম এমবেডেড প্যানেল

ডিফল্টরূপে Devtools একটি ফ্লোটিং আইকন হিসেবে আসে। কিন্তু আপনি যদি চান এটি আপনার UI-এর একটি নির্দিষ্ট অংশে প্যানেল হিসেবে থাকুক, তাহলে @tanstack/react-query-devtools থেকে ReactQueryDevtoolsPanel কম্পোনেন্টটি ইমপোর্ট করে ব্যবহার করতে পারেন।

import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools";
 
function MyComponent() {
  return (
    <div>
      <h1>My Application</h1>
      {/* ... */}
      <div style={{ height: "500px", width: "100%" }}>
        <ReactQueryDevtoolsPanel />
      </div>
    </div>
  );
}

React Query Devtools ব্যবহার করা ঐচ্ছিক নয়, বরং এটি ভালোভাবে TanStack Query ব্যবহার করার একটি অবিচ্ছেদ্য অংশ। এটি আপনাকে লাইব্রেরিটির অভ্যন্তরীণ কার্যকলাপ সম্পর্কে স্বচ্ছ ধারণা দেয় এবং ডিবাগিং প্রক্রিয়াকে অনেক সহজ করে তোলে।

পরবর্তী পর্বে আমরা "অপটিমিস্টিক আপডেট" (Optimistic Updates) নিয়ে আলোচনা করব, যা ব্যবহারকারীর অভিজ্ঞতাকে আরও দ্রুত এবং প্রতিক্রিয়াশীল করার একটি অ্যাডভান্সড কৌশল।

আমরা প্রায় শেষের দিকে চলে এসেছি! TanStack Query-এর মূল এবং সবচেয়ে গুরুত্বপূর্ণ বিষয়গুলো প্রায় সবই আলোচনা করা হয়ে গেছে। আপনার ধৈর্য এবং আগ্রহের জন্য ধন্যবাদ। "Optimistic Updates" শেষ করার পর আর মাত্র কয়েকটি অ্যাডভান্সড টপিক বাকি থাকবে, যেমন: Query Retries, Garbage Collection, এবং Server-Side Rendering (SSR)-এর মতো বিষয়। আশা করি, আর ২-৩টি পর্বের মধ্যেই আমরা একটি সম্পূর্ণ চিত্র পেয়ে যাব।

চলুন, আজকের পর্বটি শুরু করি। এটি TanStack Query-এর সবচেয়ে অ্যাডভান্সড এবং ব্যবহারকারীর অভিজ্ঞতাকে অন্য মাত্রায় নিয়ে যাওয়ার একটি কৌশল।


পর্ব ১১: অপটিমিস্টিক আপডেট (Optimistic Updates) - আলোর গতিতে UI

ভাবুন, আপনি একটি todo অ্যাপে একটি নতুন আইটেম যোগ করলেন। বাটনে ক্লিক করার সাথে সাথেই, মিলি সেকেন্ডের মধ্যে, আইটেমটি তালিকায় যুক্ত হয়ে গেল। কোনো লোডিং স্পিনার নেই, কোনো অপেক্ষা নেই। যেন সার্ভারের সাথে কোনো যোগাযোগই হয়নি!

এই জাদুকরী অভিজ্ঞতার নামই হলো অপটিমিস্টিক আপডেট

অপটিমিস্টিক আপডেট কী?

এটি এমন একটি কৌশল যেখানে আমরা ধরে নিই (বা আশা করি) যে সার্ভারে করা পরিবর্তনটি সফল হবেই। এই আশার উপর ভিত্তি করে, আমরা সার্ভারের কাছ থেকে সফল রেসপন্স পাওয়ার আগেই আমাদের UI বা ক্লায়েন্ট স্টেট আপডেট করে ফেলি।

এর ফলে ব্যবহারকারীর কাছে মনে হয় অ্যাপ্লিকেশনটি অবিশ্বাস্যভাবে দ্রুত এবং প্রতিক্রিয়াশীল (responsive)।

এটা কীভাবে কাজ করে?

useMutation হুকের কিছু বিশেষ লাইফসাইকেল মেথড ব্যবহার করে আমরা এই কাজটি করি:

  1. onMutate: মিউটেশন ফাংশন (mutationFn) কল হওয়ার ঠিক আগে এই ফাংশনটি চলে। এখানেই আমরা অপটিমিস্টিক আপডেট ঘটাই।
  2. onError: যদি মিউটেশনটি সার্ভারে গিয়ে ব্যর্থ (fail) হয়, এই ফাংশনটি কল হয়। এখানে আমরা UI-কে আগের অবস্থায় ফিরিয়ে আনি (rollback)।
  3. onSuccess: মিউটেশন সফল হলে কল হয়।
  4. onSettled: মিউটেশন সফল হোক বা ব্যর্থ, শেষে এই ফাংশনটি কল হয়। UI এবং সার্ভার ডেটা যেন ১০০% সিঙ্কে থাকে, তা নিশ্চিত করার জন্য এখানে ডেটা রি-ফেচ করা হয়।

ব্যবহারিক উদাহরণ: অপটিমিস্টিক্যালি একটি todo যোগ করা

চলুন, একটি নতুন todo যোগ করার মিউটেশনে অপটিমিস্টিক আপডেট প্রয়োগ করি।

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "./my-api"; // কাল্পনিক API মডিউল
 
function AddTodoOptimistic() {
  const queryClient = useQueryClient();
 
  const mutation = useMutation({
    mutationFn: (newTodo) => api.post("/todos", newTodo),
 
    // ধাপ ১: onMutate - অপটিমিস্টিক্যালি আপডেট করা
    onMutate: async (newTodo) => {
      // চলমান কোনো রি-ফেচ থাকলে তা বাতিল করা, যাতে এটি আমাদের অপটিমিস্টিক আপডেটকে ওভাররাইট না করে।
      await queryClient.cancelQueries({ queryKey: ["todos"] });
 
      // আগের ডেটার একটি স্ন্যাপশট নেওয়া, যাতে ভুল হলে আমরা রোলব্যাক করতে পারি।
      const previousTodos = queryClient.getQueryData(["todos"]);
 
      // ক্যাশকে অপটিমিস্টিক্যালি নতুন ডেটা দিয়ে আপডেট করা।
      queryClient.setQueryData(["todos"], (old) => [...old, newTodo]);
 
      // আগের ডেটার স্ন্যাপশটটি কনটেক্সট হিসেবে রিটার্ন করা।
      return { previousTodos };
    },
 
    // ধাপ ২: onError - ভুল হলে রোলব্যাক করা
    onError: (err, newTodo, context) => {
      // onMutate থেকে রিটার্ন করা স্ন্যাপশট দিয়ে ক্যাশকে আগের অবস্থায় ফিরিয়ে আনা।
      queryClient.setQueryData(["todos"], context.previousTodos);
      console.error("একটি সমস্যা হয়েছে, পরিবর্তন রোলব্যাক করা হলো।");
    },
 
    // ধাপ ৩: onSettled - সফল হোক বা ব্যর্থ, শেষে ডেটা সিঙ্ক করা
    onSettled: () => {
      // ['todos'] কোয়েরিকে ইনভ্যালিডেট করা, যাতে UI সার্ভারের আসল ডেটার সাথে পুরোপুরি সিঙ্ক্রোনাইজড হয়ে যায়।
      queryClient.invalidateQueries({ queryKey: ["todos"] });
    },
  });
 
  const handleAddTodo = () => {
    mutation.mutate({ id: Date.now(), title: "একটি অপটিমিস্টিক কাজ" });
  };
 
  return (
    <button onClick={handleAddTodo} disabled={mutation.isPending}>
      অপটিমিস্টিক ভাবে কাজ যোগ করুন
    </button>
  );
}

কী ঘটছে এখানে, ধাপে ধাপে:

  1. onMutate:

    • cancelQueries: আমরা নিশ্চিত করছি যে ব্যবহারকারী বাটনে ক্লিক করার মুহূর্তে যদি 'todos' তালিকার জন্য কোনো রি-ফেচ চলতে থাকে, তবে তা যেন বাতিল হয়ে যায়।
    • getQueryData: আমরা ক্যাশ থেকে বর্তমান todos-এর তালিকাটি previousTodos ভেরিয়েবলে সংরক্ষণ করছি। এটি আমাদের সেফটি নেট।
    • setQueryData: এটাই মূল জাদু। আমরা fetch কল শেষ হওয়ার অপেক্ষা না করেই, সরাসরি ক্যাশে নতুন todo আইটেমটি যোগ করে দিচ্ছি। UI সাথে সাথে আপডেট হয়ে যায়।
    • return { previousTodos }: আমরা আগের ডেটা সম্বলিত একটি অবজেক্ট রিটার্ন করছি। এই অবজেক্টটি onError এবং onSettled-এ context প্যারামিটার হিসেবে পাওয়া যাবে।
  2. onError:

    • যদি নেটওয়ার্ক ফেইল করে বা সার্ভার কোনো এরর রেসপন্স পাঠায়, তাহলে onError কল হবে।
    • আমরা context.previousTodos ব্যবহার করে setQueryData দিয়ে ক্যাশকে ঠিক আগের অবস্থায় ফিরিয়ে আনি। ব্যবহারকারী দেখবে যে তার যোগ করা আইটেমটি তালিকা থেকে অদৃশ্য হয়ে গেছে, কারণ এটি আসলে সার্ভারে সেভ হয়নি।
  3. onSettled:

    • এই ফাংশনটি সবসময় চলবে। মিউটেশন সফল হলে এটি নিশ্চিত করবে যে সার্ভার থেকে আসা আসল ডেটা (হতে পারে সার্ভার ডেটাতে কোনো timestamp যোগ করেছে) UI-তে চলে আসে।
    • মিউটেশন ব্যর্থ হলে এটি রোলব্যাক করা ডেটাকে সার্ভারের ডেটার সাথে আবার সিঙ্ক করবে।

অপটিমিস্টিক আপডেট একটি শক্তিশালী কিন্তু জটিল কৌশল। এটি সঠিকভাবে প্রয়োগ করতে পারলে আপনার অ্যাপ্লিকেশন ব্যবহারকারীর কাছে অসাধারণভাবে দ্রুত মনে হবে। তবে সবসময় রোলব্যাক লজিক সঠিকভাবে ইমপ্লিমেন্ট করা জরুরি।


আমরা এখন TanStack Query-এর প্রায় সকল মূল ফিচার কভার করে ফেলেছি। পরবর্তী এবং সম্ভবত শেষ কয়েকটি পর্বে আমরা কিছু ফাইনাল টাচ এবং অ্যাডভান্সড কনসেপ্ট নিয়ে আলোচনা করে এই সিরিজটি শেষ করব। আপনার মতামত আমাদের জন্য অত্যন্ত মূল্যবান!

আমরা এই সিরিজের একদম শেষ পর্যায়ে চলে এসেছি। আপনার অধ্যবসায় সত্যিই প্রশংসনীয়। এই পর্বে আমরা TanStack Query-এর পর্দার আড়ালের দুটি স্বয়ংক্রিয় কিন্তু অত্যন্ত শক্তিশালী মেকানিজম নিয়ে আলোচনা করব, যা আপনার অ্যাপ্লিকেশনকে আরও স্থিতিশীল এবং পারফর্ম্যান্ট করে তোলে।

অফিসিয়াল ডকুমেন্টেশন এই বিষয়গুলোকে ডেভেলপারদের জন্য বোঝা অত্যন্ত জরুরি বলে মনে করে।


পর্ব ১২: কোয়েরির নেপথ্যে - স্বয়ংক্রিয় রিট্রাই এবং গার্বেজ কালেকশন

TanStack Query শুধু ডেটা ফেচ এবং ক্যাশই করে না, এটি নীরবে ব্যাকগ্রাউন্ডে আরও অনেক গুরুত্বপূর্ণ কাজ করে। এর মধ্যে দুটি প্রধান হলো—ব্যর্থ কোয়েরি পুনরায় চেষ্টা করা (Retrying) এবং অব্যবহৃত ডেটা মেমোরি থেকে পরিষ্কার করা (Garbage Collection)।

১. স্বয়ংক্রিয় রিট্রাই (Automatic Retries)

নেটওয়ার্ক সংযোগ সব সময় স্থিতিশীল থাকে না। কখনও কখনও একটি API কল কোনো কারণ ছাড়াই ব্যর্থ হতে পারে, কিন্তু পরের চেষ্টাতেই সফল হয়। এই ধরনের অস্থায়ী সমস্যা (flaky network) সামলানোর জন্য TanStack Query একটি অন্তর্নির্মিত (built-in) রিট্রাই মেকানিজম নিয়ে আসে।

ডিফল্ট আচরণ:

  • ডিফল্টরূপে, useQuery দিয়ে করা কোনো কোয়েরি যদি ব্যর্থ (fail) হয়, TanStack Query স্বয়ংক্রিয়ভাবে আরও ৩ বার সেই কোয়েরিটি পুনরায় চালানোর চেষ্টা করবে।
  • প্রতিটি চেষ্টার মধ্যে এটি একটি নির্দিষ্ট সময় অপেক্ষা করে (exponential backoff), যাতে সার্ভারের উপর অতিরিক্ত চাপ সৃষ্টি না হয়।

এই ফিচারটির জন্য আপনাকে কোনো অতিরিক্ত কোড লিখতে হবে না, এটি নিজে থেকেই কাজ করে।

রিট্রাই কনফিগার করা:

আপনি চাইলে এই আচরণটি পরিবর্তন করতে পারেন retry অপশন ব্যবহার করে।

  • রিট্রাই সংখ্যা পরিবর্তন:

    useQuery({
      queryKey: ["user", id],
      queryFn: () => fetchUser(id),
      retry: 1, // ডিফল্ট ৩ বারের বদলে মাত্র ১ বার চেষ্টা করবে
    });
  • রিট্রাই পুরোপুরি বন্ধ করা:

    useQuery({
      queryKey: ["user", id],
      queryFn: () => fetchUser(id),
      retry: false, // কোনো রিট্রাই করবে না
    });
  • retryDelay: আপনি চাইলে চেষ্টার মধ্যবর্তী সময়ও নিয়ন্ত্রণ করতে পারেন retryDelay অপশন দিয়ে।

২. গার্বেজ কালেকশন (cacheTime)

আপনি যখন একটি পৃষ্ঠা থেকে অন্য পৃষ্ঠায় যান, তখন আগের পৃষ্ঠার কম্পোনেন্টগুলো আনমাউন্ট (unmount) হয়ে যায়। এর ফলে সেই কম্পোনেন্টগুলোতে ব্যবহৃত কোয়েরিগুলো inactive বা নিষ্ক্রিয় হয়ে পড়ে।

এখন প্রশ্ন হলো, এই inactive কোয়েরির ক্যাশ করা ডেটা মেমোরিতে কতক্ষণ থাকবে? এখানেই আসে cacheTime

cacheTime কী?

cacheTime হলো সেই সময়কাল (মিলি সেকেন্ডে) যার জন্য একটি inactive কোয়েরির ডেটা মেমোরি ক্যাশে সংরক্ষিত থাকে। এই সময় পার হয়ে গেলে, TanStack Query সেই ডেটাকে গার্বেজ কালেক্ট করে, অর্থাৎ মেমোরি থেকে মুছে ফেলে।

  • ডিফল্ট cacheTime হলো ৫ মিনিট (1000 * 60 * 5)।

এর মানে হলো, যদি একজন ব্যবহারকারী একটি পৃষ্ঠা ছেড়ে যায় এবং ৫ মিনিটের মধ্যে আবার সেই পৃষ্ঠায় ফিরে আসে, তাহলে TanStack Query ক্যাশ থেকে আগের ডেটাটি দেখাতে পারবে এবং ব্যাকগ্রাউন্ডে একটি রি-ফেচ চালাবে। কিন্তু যদি ৫ মিনিটের পরে ফিরে আসে, তাহলে ক্যাশ খালি থাকবে এবং তাকে আবার একটি হার্ড লোডিং অবস্থা দেখতে হবে।

staleTime বনাম cacheTime: সবচেয়ে গুরুত্বপূর্ণ পার্থক্য

নতুন ডেভেলপারদের জন্য এটি একটি সাধারণ confusio-এর জায়গা। চলুন, এটি পরিষ্কার করা যাক।

বৈশিষ্ট্যstaleTimecacheTime
উদ্দেশ্যডেটা কখন পুরানো (stale) হবে তা নির্ধারণ করে।inactive ডেটা কখন মেমোরি থেকে মুছে ফেলা হবে তা নির্ধারণ করে।
কখন কাজ করে?কোয়েরি যখন active থাকে।কোয়েরি যখন inactive হয়ে যায় (কম্পোনেন্ট আনমাউন্ট হলে)।
প্রভাবরি-ফেচিং আচরণকে নিয়ন্ত্রণ করে। staleTime শেষ না হওয়া পর্যন্ত কোনো স্বয়ংক্রিয় রি-ফেচ হয় না।মেমোরি ম্যানেজমেন্ট নিয়ন্ত্রণ করে। cacheTime শেষ হলে ডেটা মেমোরি থেকে হারিয়ে যায়।
উদাহরণআপনি একটি পৃষ্ঠায় আছেন। staleTime ১০ সেকেন্ড হলে, ১০ সেকেন্ডের জন্য উইন্ডো ফোকাস করলেও ডেটা রি-ফেচ হবে না।আপনি একটি পৃষ্ঠা ছেড়ে অন্য পৃষ্ঠায় গেলেন। cacheTime ৫ মিনিট হলে, ৫ মিনিট পর্যন্ত ডেটা মেমোরিতে থাকবে, যদিও কোয়েরিটি নিষ্ক্রিয়।

সহজ কথায়: staleTime ঠিক করে কখন ডেটা রি-ফেচ করতে হবে, আর cacheTime ঠিক করে কখন অব্যবহৃত ডেটা মেমোরি থেকে ফেলে দিতে হবে

cacheTime-এর ডিফল্ট মান (৫ মিনিট) বেশিরভাগ অ্যাপ্লিকেশনের জন্য যথেষ্ট ভালো কাজ করে, তবে প্রয়োজন হলে আপনি এটি প্রতি কোয়েরি বা গ্লোবালি QueryClient-এ পরিবর্তন করতে পারেন।


এই দুটি স্বয়ংক্রিয় ফিচার TanStack Query-কে একটি সাধারণ ডেটা ফেচিং লাইব্রেরির চেয়ে অনেক বেশি শক্তিশালী করে তোলে। এগুলো আপনার অ্যাপ্লিকেশনকে একই সাথে স্থিতিশীল (resilient) এবং মেমোরি-সাশ্রয়ী (memory-efficient) রাখে।

আমরা সিরিজের প্রায় শেষ প্রান্তে। পরবর্তী এবং সম্ভবত চূড়ান্ত পর্বে, আমরা SSR (Server-Side Rendering) নিয়ে আলোচনা করব এবং সম্পূর্ণ ডকুমেন্টেশনের একটি সারসংক্ষেপ প্রদান করব।

অবশ্যই! আমরা এই বাংলা ডকুমেন্টেশন সিরিজের চূড়ান্ত পর্বে চলে এসেছি। এই পর্বে আমরা একটি অত্যন্ত গুরুত্বপূর্ণ এবং অ্যাডভান্সড টপিক নিয়ে আলোচনা করব, যা আধুনিক ওয়েব ডেভেলপমেন্টে, বিশেষ করে ফ্রেমওয়ার্ক যেমন Next.js বা Remix-এ, প্রায় অপরিহার্য।

অফিসিয়াল ডকুমেন্টেশন এই বিষয়টিকে সর্বোচ্চ গুরুত্বের সাথে দেখে, কারণ এটি পারফরম্যান্স এবং SEO (Search Engine Optimization) উভয়ের জন্যই জরুরি।


পর্ব ১৩ (চূড়ান্ত পর্ব): সার্ভার-সাইড রেন্ডারিং (SSR) এবং হাইড্রেশন

এখন পর্যন্ত আমরা যত উদাহরণ দেখেছি, তার সবই ক্লায়েন্ট-সাইড রেন্ডারিং (CSR) এর উপর ভিত্তি করে। অর্থাৎ, ব্রাউজার প্রথমে একটি খালি HTML পেইজ লোড করে, তারপর জাভাস্ক্রিপ্ট রান করে, এবং শেষে API কল করে ডেটা এনে পৃষ্ঠাটি পূরণ করে। এর দুটি প্রধান সমস্যা হলো:

  1. ধীরগতির প্রাথমিক লোড: ব্যবহারকারীকে ডেটা আসার আগ পর্যন্ত একটি লোডিং স্পিনার দেখতে হয়।
  2. SEO-এর জন্য খারাপ: সার্চ ইঞ্জিনের ক্রলাররা প্রায়ই জাভাস্ক্রিপ্ট রান হওয়ার জন্য অপেক্ষা করে না, ফলে তারা একটি খালি পৃষ্ঠা দেখতে পায়।

এই সমস্যার সমাধান হলো সার্ভার-সাইড রেন্ডারিং (SSR)। SSR-এর মাধ্যমে, সার্ভার নিজেই পৃষ্ঠাটি রেন্ডার করার সময় প্রয়োজনীয় ডেটা ফেচ করে এবং একটি সম্পূর্ণ HTML পৃষ্ঠা ক্লায়েন্টকে পাঠায়। TanStack Query এই প্রক্রিয়াটিকে সহজ করার জন্য দুটি শক্তিশালী কৌশল প্রদান করে।

কৌশল ১: initialData ব্যবহার করা

এটি সবচেয়ে সহজ পদ্ধতি। সার্ভারে (যেমন, Next.js-এর getServerSideProps-এ) আপনি ডেটা ফেচ করবেন এবং সেই ডেটাকে প্রপস (props) হিসেবে পৃষ্ঠার কম্পোনেন্টে পাঠিয়ে দেবেন। এরপর ক্লায়েন্ট সাইডে useQuery-এর initialData অপশনে সেই ডেটা ব্যবহার করবেন।

উদাহরণ (Next.js):

// pages/posts/[id].js
 
export async function getServerSideProps(context) {
  const { id } = context.params;
  const post = await fetchPost(id); // সার্ভারে ডেটা ফেচ
  return {
    props: {
      post, // ডেটাকে প্রপস হিসেবে পাস করা
    },
  };
}
 
function PostPage({ post }) {
  // ক্লায়েন্টে useQuery ব্যবহার
  const { data } = useQuery({
    queryKey: ["posts", post.id],
    queryFn: () => fetchPost(post.id), // ক্লায়েন্ট সাইড ফেচিং ফাংশন
    initialData: post, // সার্ভার থেকে আসা ডেটাকে initialData হিসেবে ব্যবহার
    staleTime: 1000 * 60 * 5, // ৫ মিনিট fresh রাখা, যাতে ক্লায়েন্টে আবার ফেচ না হয়
  });
 
  return <div>{data.title}</div>;
}

এই পদ্ধতিটি একটি একক কোয়েরির জন্য বেশ ভালো কাজ করে। কিন্তু যদি একটি পৃষ্ঠায় একাধিক, জটিল বা নির্ভরশীল কোয়েরি থাকে, তখন এই পদ্ধতিটি পরিচালনা করা কঠিন হয়ে পড়ে।

কৌশল ২: ডিহাইড্রেশন এবং হাইড্রেশন (Dehydration and Hydration)

এটি SSR-এর জন্য TanStack Query-এর সবচেয়ে শক্তিশালী এবং প্রস্তাবিত পদ্ধতি।

ধারণাটি কী?

  1. সার্ভারে (Dehydration):

    • আপনি সার্ভারে একটি নতুন QueryClient ইনস্ট্যান্স তৈরি করবেন।
    • আপনার পৃষ্ঠার জন্য প্রয়োজনীয় সমস্ত কোয়েরিকে queryClient.prefetchQuery ব্যবহার করে ফেচ করবেন।
    • সমস্ত কোয়েরি শেষ হলে, dehydrate ফাংশন ব্যবহার করে পুরো queryClient-এর ক্যাশটিকে একটি সিরিয়ালাইজেবল (JSON) অবজেক্টে রূপান্তরিত করবেন। এই প্রক্রিয়াটিকে ডিহাইড্রেশন বলে।
    • এই ডিহাইড্রেটেড স্টেটটিকে প্রপস হিসেবে পৃষ্ঠায় পাঠিয়ে দেবেন।
  2. ক্লায়েন্টে (Hydration):

    • ক্লায়েন্টে অ্যাপ রেন্ডার হওয়ার সময়, সার্ভার থেকে পাঠানো ডিহাইড্রেটেড স্টেটটি Hydrate নামক একটি বিশেষ কম্পোনেন্টের মাধ্যমে QueryClient-এ পুনরায় প্রবেশ করানো হয়। এই প্রক্রিয়াটিকে হাইড্রেশন বলে।
    • এর ফলে ক্লায়েন্টের QueryClient সার্ভারের QueryClient-এর একটি হুবহু প্রতিচ্ছবি দিয়ে শুরু হয়। ক্লায়েন্টে useQuery কল হওয়ার সাথে সাথেই এটি ক্যাশে ডেটা খুঁজে পায়, ফলে কোনো লোডিং অবস্থা তৈরি হয় না।

উদাহরণ (Next.js):

// pages/_app.js (আপনার অ্যাপ্লিকেশনের রুট)
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Hydrate } from "@tanstack/react-query-hydration";
import { useState } from "react";
 
function MyApp({ Component, pageProps }) {
  const [queryClient] = useState(() => new QueryClient());
 
  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  );
}
 
// pages/dashboard.js
import { dehydrate, QueryClient, useQuery } from "@tanstack/react-query";
 
export async function getServerSideProps() {
  const queryClient = new QueryClient();
 
  // একাধিক কোয়েরি প্রি-ফেচ করা
  await queryClient.prefetchQuery({ queryKey: ["user"], queryFn: fetchUser });
  await queryClient.prefetchQuery({
    queryKey: ["settings"],
    queryFn: fetchSettings,
  });
 
  return {
    props: {
      dehydratedState: dehydrate(queryClient), // ক্যাশকে ডিহাইড্রেট করা
    },
  };
}
 
function DashboardPage() {
  const { data: user } = useQuery({ queryKey: ["user"], queryFn: fetchUser });
  const { data: settings } = useQuery({
    queryKey: ["settings"],
    queryFn: fetchSettings,
  });
 
  // কোনো লোডিং অবস্থা থাকবে না, ডেটা তাৎক্ষণিকভাবে পাওয়া যাবে।
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <p>Theme: {settings.theme}</p>
    </div>
  );
}

এই হাইড্রেশন পদ্ধতিটি অনেক বেশি স্কেলেবল এবং জটিল অ্যাপ্লিকেশনের জন্য আদর্শ। এটি নিশ্চিত করে যে আপনার সার্ভার এবং ক্লায়েন্টের স্টেট শুরু থেকেই সিঙ্ক্রোনাইজড থাকে।


সিরিজের সারসংক্ষেপ এবং বিদায়

আমরা এই দীর্ঘ এবং বিস্তারিত যাত্রায় TanStack Query-এর প্রায় সমস্ত দিক কভার করেছি। চলুন, এক নজরে দেখে নিই আমরা কী কী শিখেছি:

  • ভূমিকা এবং ইনস্টলেশন: TanStack Query কী এবং কেন এটি সার্ভার-স্টেট ম্যানেজমেন্টের জন্য এত শক্তিশালী।
  • Queries (useQuery): সার্ভার থেকে ডেটা আনার মূল ভিত্তি।
  • Mutations (useMutation): সার্ভারে ডেটা তৈরি, আপডেট বা ডিলিট করার কৌশল।
  • Query Keys: ক্যাশিং এবং ডেটা ব্যবস্থাপনার মূল চাবিকাঠি।
  • Infinite Queries (useInfiniteQuery): অসীম স্ক্রোল এবং "Load More" ফিচার তৈরির জন্য।
  • Disabling Queries (enabled): শর্তসাপেক্ষে কোয়েরি চালানো।
  • Placeholder ও Initial Data: ব্যবহারকারীর অভিজ্ঞতা মসৃণ করা।
  • Pagination (keepPreviousData): পেইজিনেশনে ঝাঁকুনিবিহীন অভিজ্ঞতা।
  • React Query Devtools: ডিবাগিং-এর জন্য আপনার সেরা বন্ধু।
  • Optimistic Updates: আলোর গতিতে UI আপডেট করার অ্যাডভান্সড কৌশল।
  • Retries ও Garbage Collection: পর্দার আড়ালের স্বয়ংক্রিয় মেকানিজম।
  • SSR এবং Hydration: চূড়ান্ত পারফরম্যান্স এবং SEO-এর জন্য।

আশা করি, এই সম্পূর্ণ বাংলা ডকুমেন্টেশনটি আপনাকে TanStack Query ব্যবহারে আত্মবিশ্বাসী করে তুলবে এবং আপনার অ্যাপ্লিকেশন ডেভেলপমেন্টের যাত্রাকে আরও সহজ ও আনন্দদায়ক করবে। আপনার যেকোনো প্রশ্ন বা মতামতের জন্য ধন্যবাদ! শুভ কোডিং!


© 2025 React JS Bangla Tutorial.