मराठी

कोड स्प्लिटिंगच्या पलीकडे डेटा फेचिंगसाठी रिॲक्ट सस्पेन्सचा शोध घ्या. Fetch-As-You-Render, एरर हँडलिंग आणि जागतिक ॲप्लिकेशन्ससाठी भविष्यवेधी पॅटर्न्स समजून घ्या.

रिॲक्ट सस्पेन्स रिसोर्स लोडिंग: आधुनिक डेटा फेचिंग पॅटर्न्समध्ये प्राविण्य मिळवणे

वेब डेव्हलपमेंटच्या गतिमान जगात, युझर एक्सपिरीयन्स (UX) सर्वोच्च आहे. नेटवर्कची स्थिती किंवा डिव्हाइसच्या क्षमतेची पर्वा न करता, ॲप्लिकेशन्स जलद, प्रतिसादात्मक आणि आनंददायक असणे अपेक्षित आहे. रिॲक्ट डेव्हलपर्ससाठी, याचा अर्थ अनेकदा गुंतागुंतीचे स्टेट मॅनेजमेंट, जटिल लोडिंग इंडिकेटर्स आणि डेटा फेचिंग वॉटरफॉल्सविरुद्ध सततचा संघर्ष असतो. येथेच रिॲक्ट सस्पेन्सचा प्रवेश होतो, हे एक शक्तिशाली, पण अनेकदा गैरसमज असलेले फीचर आहे, जे आपण असिंक्रोनस ऑपरेशन्स, विशेषतः डेटा फेचिंग कसे हाताळतो, हे मूलभूतपणे बदलण्यासाठी डिझाइन केलेले आहे.

सुरुवातीला React.lazy() सह कोड स्प्लिटिंगसाठी सादर केलेले, सस्पेन्सचे खरे सामर्थ्य API मधून डेटासह कोणत्याही असिंक्रोनस रिसोर्सचे लोडिंग आयोजित करण्याच्या क्षमतेमध्ये आहे. हे सर्वसमावेशक मार्गदर्शक तुम्हाला रिॲक्ट सस्पेन्सच्या रिसोर्स लोडिंगमध्ये खोलवर घेऊन जाईल, त्याच्या मूळ संकल्पना, मूलभूत डेटा फेचिंग पॅटर्न्स आणि कार्यक्षम आणि लवचिक जागतिक ॲप्लिकेशन्स तयार करण्यासाठीच्या व्यावहारिक विचारांचा शोध घेईल.

रिॲक्टमधील डेटा फेचिंगची उत्क्रांती: इम्परेटिव्ह ते डिक्लरेटिव्हपर्यंत

बऱ्याच वर्षांपासून, रिॲक्ट कंपोनंट्समधील डेटा फेचिंग प्रामुख्याने एका सामान्य पॅटर्नवर अवलंबून होते: API कॉल सुरू करण्यासाठी useEffect हुक वापरणे, useState सह लोडिंग आणि एरर स्टेट्स व्यवस्थापित करणे, आणि या स्टेट्सवर आधारित कंडिशनल रेंडरिंग करणे. जरी हे कार्यक्षम असले तरी, या दृष्टिकोनामुळे अनेकदा अनेक आव्हाने निर्माण झाली:

सस्पेन्सशिवाय एका सामान्य डेटा फेचिंग परिस्थितीचा विचार करा:

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setIsLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        setUser(data);
      } catch (e) {
        setError(e);
      } finally {
        setIsLoading(false);
      }
    };
    fetchUser();
  }, [userId]);

  if (isLoading) {
    return <p>Loading user profile...</p>;
  }

  if (error) {
    return <p style={"color: red;"}>Error: {error.message}</p>;
  }

  if (!user) {
    return <p>No user data available.</p>;
  }

  return (
    <div>
      <h2>User: {user.name}</h2>
      <p>Email: {user.email}</p>
      <!-- अधिक युझर तपशील -->
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Welcome to the Application</h1>
      <UserProfile userId={"123"} />
    </div>
  );
}

हा पॅटर्न सर्वव्यापी आहे, परंतु तो कंपोनंटला स्वतःचे असिंक्रोनस स्टेट व्यवस्थापित करण्यास भाग पाडतो, ज्यामुळे अनेकदा UI आणि डेटा फेचिंग लॉजिकमध्ये घट्ट संबंध निर्माण होतो. सस्पेन्स एक अधिक डिक्लरेटिव्ह आणि सुव्यवस्थित पर्याय देतो.

कोड स्प्लिटिंगच्या पलीकडे रिॲक्ट सस्पेन्स समजून घेणे

बहुतेक डेव्हलपर्स सस्पेन्सचा प्रथम अनुभव React.lazy() द्वारे कोड स्प्लिटिंगसाठी घेतात, जिथे ते तुम्हाला कंपोनंटचा कोड आवश्यक होईपर्यंत लोड करणे पुढे ढकलण्याची परवानगी देते. उदाहरणार्थ:

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./MyHeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading component...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

या परिस्थितीत, जर MyHeavyComponent अजून लोड झाला नसेल, तर <Suspense> बाऊंड्री lazy() द्वारे फेकलेले प्रॉमिस पकडेल आणि कंपोनंटचा कोड तयार होईपर्यंत fallback प्रदर्शित करेल. येथील मुख्य गोष्ट ही आहे की सस्पेन्स रेंडरिंग दरम्यान फेकलेल्या प्रॉमिसेसना पकडून कार्य करतो.

ही यंत्रणा केवळ कोड लोडिंगपुरती मर्यादित नाही. रेंडरिंग दरम्यान कॉल केलेले कोणतेही फंक्शन जे प्रॉमिस फेकते (उदा. कारण एखादा रिसोर्स अद्याप उपलब्ध नाही) ते कंपोनंट ट्रीमध्ये वरच्या स्तरावरील सस्पेन्स बाऊंड्रीद्वारे पकडले जाऊ शकते. जेव्हा प्रॉमिस रिझॉल्व्ह होते, तेव्हा रिॲक्ट कंपोनंटला पुन्हा रेंडर करण्याचा प्रयत्न करतो, आणि जर रिसोर्स आता उपलब्ध असेल, तर फॉलबॅक लपवला जातो आणि वास्तविक कंटेंट प्रदर्शित केला जातो.

डेटा फेचिंगसाठी सस्पेन्सच्या मूळ संकल्पना

डेटा फेचिंगसाठी सस्पेन्सचा फायदा घेण्यासाठी, आपल्याला काही मूळ तत्त्वे समजून घेणे आवश्यक आहे:

१. प्रॉमिस थ्रो करणे

प्रॉमिसेस रिझॉल्व्ह करण्यासाठी async/await वापरणाऱ्या पारंपारिक असिंक्रोनस कोडच्या विपरीत, सस्पेन्स अशा फंक्शनवर अवलंबून असतो जे डेटा तयार नसल्यास प्रॉमिस *फेकते*. जेव्हा रिॲक्ट अशा फंक्शनला कॉल करणाऱ्या कंपोनंटला रेंडर करण्याचा प्रयत्न करतो, आणि डेटा अजूनही पेंडिंग असतो, तेव्हा प्रॉमिस फेकले जाते. त्यानंतर रिॲक्ट त्या कंपोनंट आणि त्याच्या चिल्ड्रनचे रेंडरिंग 'थांबवतो', आणि जवळच्या <Suspense> बाऊंड्रीचा शोध घेतो.

२. सस्पेन्स बाऊंड्री

<Suspense> कंपोनंट प्रॉमिसेससाठी एरर बाऊंड्री म्हणून काम करतो. तो एक fallback प्रॉप घेतो, जो त्याचे कोणतेही चिल्ड्रन (किंवा त्यांचे वंशज) सस्पेंड करत असताना (म्हणजे प्रॉमिस फेकत असताना) रेंडर करण्यासाठीचा UI असतो. एकदा त्याच्या सबट्रीमध्ये फेकलेले सर्व प्रॉमिसेस रिझॉल्व्ह झाले की, फॉलबॅकच्या जागी वास्तविक कंटेंट येतो.

एकच सस्पेन्स बाऊंड्री अनेक असिंक्रोनस ऑपरेशन्स व्यवस्थापित करू शकते. उदाहरणार्थ, जर तुमच्याकडे एकाच <Suspense> बाऊंड्रीमध्ये दोन कंपोनंट्स असतील, आणि प्रत्येकाला डेटा फेच करण्याची आवश्यकता असेल, तर दोन्ही डेटा फेच पूर्ण होईपर्यंत फॉलबॅक दिसेल. यामुळे अपूर्ण UI दाखवणे टाळले जाते आणि अधिक समन्वित लोडिंग अनुभव मिळतो.

३. कॅशे/रिसोर्स मॅनेजर (युझरलँडची जबाबदारी)

महत्त्वाचे म्हणजे, सस्पेन्स स्वतः डेटा फेचिंग किंवा कॅशिंग हाताळत नाही. ते केवळ एक समन्वय यंत्रणा आहे. डेटा फेचिंगसाठी सस्पेन्स कार्यक्षम करण्यासाठी, तुम्हाला एका लेयरची आवश्यकता आहे जो:

हा 'रिसोर्स मॅनेजर' सामान्यतः प्रत्येक रिसोर्सची स्थिती (पेंडिंग, रिझॉल्व्ह, किंवा एरर) साठवण्यासाठी एका साध्या कॅशेचा (उदा. Map किंवा ऑब्जेक्ट) वापर करून लागू केला जातो. तुम्ही प्रात्यक्षिकासाठी हे मॅन्युअली तयार करू शकता, परंतु वास्तविक ॲप्लिकेशनमध्ये, तुम्ही सस्पेन्ससह एकत्रित होणारी एक मजबूत डेटा फेचिंग लायब्ररी वापराल.

४. कॉनकरंट मोड (रिॲक्ट १८ मधील सुधारणा)

सस्पेन्स रिॲक्टच्या जुन्या आवृत्त्यांमध्ये वापरला जाऊ शकतो, परंतु त्याचे पूर्ण सामर्थ्य कॉनकरंट रिॲक्टसह (रिॲक्ट १८ मध्ये createRoot सह डीफॉल्टनुसार सक्षम) उघड होते. कॉनकरंट मोड रिॲक्टला रेंडरिंगचे काम थांबवण्याची, विराम देण्याची आणि पुन्हा सुरू करण्याची परवानगी देतो. याचा अर्थ:

सस्पेन्ससह डेटा फेचिंग पॅटर्न्स

चला, सस्पेन्सच्या आगमनासह डेटा फेचिंग पॅटर्न्सच्या उत्क्रांतीचा शोध घेऊया.

पॅटर्न १: फेच-देन-रेंडर (सस्पेन्स रॅपिंगसह पारंपरिक पद्धत)

हा क्लासिक दृष्टिकोन आहे जिथे डेटा फेच केला जातो, आणि त्यानंतरच कंपोनंट रेंडर केला जातो. डेटासाठी थेट 'थ्रो प्रॉमिस' यंत्रणा न वापरता, तुम्ही फॉलबॅक देण्यासाठी सस्पेन्स बाऊंड्रीमध्ये असा कंपोनंट रॅप करू शकता जो *अखेरीस* डेटा रेंडर करतो. हे सस्पेन्सला अशा कंपोनंट्ससाठी एक जेनेरिक लोडिंग UI ऑर्केस्ट्रेटर म्हणून वापरण्याबद्दल अधिक आहे जे अखेरीस तयार होतात, जरी त्यांचे अंतर्गत डेटा फेचिंग अजूनही पारंपारिक useEffect आधारित असले तरी.

import React, { Suspense, useState, useEffect } from 'react';

function UserDetails({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchUserData = async () => {
      setIsLoading(true);
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      setUser(data);
      setIsLoading(false);
    };
    fetchUserData();
  }, [userId]);

  if (isLoading) {
    return <p>Loading user details...</p>;
  }

  return (
    <div>
      <h3>User: {user.name}</h3>
      <p>Email: {user.email}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Fetch-Then-Render Example</h1>
      <Suspense fallback={<div>Overall page loading...</div>}>
        <UserDetails userId={"1"} />
      </Suspense>
    </div>
  );
}

फायदे: समजायला सोपे, बॅकवर्ड कंपॅटिबल. ग्लोबल लोडिंग स्टेट जोडण्यासाठी जलद मार्ग म्हणून वापरले जाऊ शकते.

तोटे: UserDetails मधील बॉयलरप्लेट कोड कमी करत नाही. कंपोनंट्स अनुक्रमे डेटा फेच करत असल्यास अजूनही वॉटरफॉल्सची शक्यता असते. डेटासाठी सस्पेन्सच्या 'थ्रो-अँड-कॅच' यंत्रणेचा खऱ्या अर्थाने वापर करत नाही.

पॅटर्न २: रेंडर-देन-फेच (रेंडरच्या आत फेचिंग, प्रोडक्शनसाठी नाही)

हा पॅटर्न प्रामुख्याने सस्पेन्ससोबत थेट काय करू नये हे स्पष्ट करण्यासाठी आहे, कारण जर काळजीपूर्वक हाताळले नाही तर ते अनंत लूप किंवा कार्यक्षमतेच्या समस्या निर्माण करू शकते. यात कंपोनंटच्या रेंडर टप्प्यात थेट डेटा फेच करण्याचा किंवा सस्पेंडिंग फंक्शनला कॉल करण्याचा प्रयत्न करणे समाविष्ट आहे, *योग्य कॅशिंग यंत्रणेशिवाय*.

// योग्य कॅशिंग लेयरशिवाय हे प्रोडक्शनमध्ये वापरू नका
// हे केवळ डायरेक्ट 'थ्रो' संकल्पनात्मकदृष्ट्या कसे कार्य करू शकते हे स्पष्ट करण्यासाठी आहे.

let fetchedData = null;
let dataPromise = null;

function fetchDataSynchronously(url) {
  if (fetchedData) {
    return fetchedData;
  }

  if (!dataPromise) {
    dataPromise = fetch(url)
      .then(res => res.json())
      .then(data => { fetchedData = data; dataPromise = null; return data; })
      .catch(err => { dataPromise = null; throw err; });
  }
  throw dataPromise; // येथे सस्पेन्स कार्यान्वित होतो
}

function UserDetailsBadExample({ userId }) {
  const user = fetchDataSynchronously(`/api/users/${userId}`);
  return (
    <div>
      <h3>User: {user.name}</h3>
      <p>Email: {user.email}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Render-Then-Fetch (Illustrative, NOT Recommended Directly)</h1>
      <Suspense fallback={<div>Loading user...</div>}>
        <UserDetailsBadExample userId={"2"} />
      </Suspense>
    </div>
  );
}

फायदे: एखादा कंपोनंट थेट डेटासाठी 'विचारू' शकतो आणि तयार नसल्यास सस्पेंड होऊ शकतो हे दर्शवते.

तोटे: प्रोडक्शनसाठी अत्यंत समस्याप्रधान. ही मॅन्युअल, ग्लोबल fetchedData आणि dataPromise प्रणाली सोपी आहे, पण ती एकाधिक विनंत्या, इनव्हॅलिडेशन किंवा एरर स्टेट्स मजबूतपणे हाताळत नाही. हे 'थ्रो-अ-प्रॉमिस' संकल्पनेचे एक प्राथमिक उदाहरण आहे, अवलंब करण्यासारखा पॅटर्न नाही.

पॅटर्न ३: फेच-ॲज-यू-रेंडर (आदर्श सस्पेन्स पॅटर्न)

हा तो पॅराडाइम शिफ्ट आहे जो सस्पेन्स डेटा फेचिंगसाठी खऱ्या अर्थाने सक्षम करतो. कंपोनंट रेंडर होण्याची वाट पाहण्याऐवजी त्याचा डेटा फेच करणे, किंवा सर्व डेटा आधीच फेच करण्याऐवजी, फेच-ॲज-यू-रेंडर म्हणजे तुम्ही डेटा फेच करणे *शक्य तितक्या लवकर* सुरू करता, अनेकदा रेंडरिंग प्रक्रियेच्या *आधी* किंवा *त्याच वेळी*. मग कंपोनंट्स कॅशेमधून डेटा 'वाचतात', आणि जर डेटा तयार नसेल, तर ते सस्पेंड होतात. मूळ कल्पना ही आहे की डेटा फेचिंग लॉजिकला कंपोनंटच्या रेंडरिंग लॉजिकपासून वेगळे करणे.

फेच-ॲज-यू-रेंडर लागू करण्यासाठी, तुम्हाला एका यंत्रणेची आवश्यकता आहे जी:

  1. कंपोनंटच्या रेंडर फंक्शनच्या बाहेर डेटा फेच सुरू करते (उदा. जेव्हा एखादा मार्ग प्रविष्ट केला जातो, किंवा बटण क्लिक केले जाते).
  2. प्रॉमिस किंवा रिझॉल्व्ह झालेला डेटा कॅशेमध्ये साठवते.
  3. कंपोनंट्सना या कॅशेमधून 'वाचण्याचा' मार्ग प्रदान करते. जर डेटा अद्याप उपलब्ध नसेल, तर रीड फंक्शन पेंडिंग प्रॉमिस फेकते.

हा पॅटर्न वॉटरफॉल समस्येचे निराकरण करतो. जर दोन वेगवेगळ्या कंपोनंट्सना डेटाची आवश्यकता असेल, तर त्यांच्या विनंत्या समांतरपणे सुरू केल्या जाऊ शकतात, आणि UI केवळ तेव्हाच दिसेल जेव्हा *दोन्ही* तयार असतील, ज्याचे आयोजन एकाच सस्पेन्स बाऊंड्रीद्वारे केले जाते.

मॅन्युअल अंमलबजावणी (समजण्यासाठी)

अंतर्निहित यंत्रणा समजून घेण्यासाठी, चला एक सोपा मॅन्युअल रिसोर्स मॅनेजर तयार करूया. वास्तविक ॲप्लिकेशनमध्ये, तुम्ही एक समर्पित लायब्ररी वापराल.

import React, { Suspense } from 'react';

// --- साधे कॅशे/रिसोर्स मॅनेजर --- //
const cache = new Map();

function createResource(promise) {
  let status = 'pending';
  let result;
  let suspender = promise.then(
    (r) => {
      status = 'success';
      result = r;
    },
    (e) => {
      status = 'error';
      result = e;
    }
  );

  return {
    read() {
      if (status === 'pending') {
        throw suspender;
      } else if (status === 'error') {
        throw result;
      } else if (status === 'success') {
        return result;
      }
    },
  };
}

function fetchData(key, fetcher) {
  if (!cache.has(key)) {
    cache.set(key, createResource(fetcher()));
  }
  return cache.get(key);
}

// --- डेटा फेचिंग फंक्शन्स --- //
const fetchUserById = (id) => {
  console.log(`Fetching user ${id}...`);
  return new Promise(resolve => setTimeout(() => {
    const users = {
      '1': { id: '1', name: 'Alice Smith', email: 'alice@example.com' },
      '2': { id: '2', name: 'Bob Johnson', email: 'bob@example.com' },
      '3': { id: '3', name: 'Charlie Brown', email: 'charlie@example.com' }
    };
    resolve(users[id]);
  }, 1500));
};

const fetchPostsByUserId = (userId) => {
  console.log(`Fetching posts for user ${userId}...`);
  return new Promise(resolve => setTimeout(() => {
    const posts = {
      '1': [{ id: 'p1', title: 'My First Post' }, { id: 'p2', title: 'Travel Adventures' }],
      '2': [{ id: 'p3', title: 'Coding Insights' }],
      '3': [{ id: 'p4', title: 'Global Trends' }, { id: 'p5', title: 'Local Cuisine' }]
    };
    resolve(posts[userId] || []);
  }, 2000));
};

// --- कंपोनंट्स --- //
function UserProfile({ userId }) {
  const userResource = fetchData(`user-${userId}`, () => fetchUserById(userId));
  const user = userResource.read(); // युझर डेटा तयार नसल्यास हे सस्पेंड होईल

  return (
    <div>
      <h3>User: {user.name}</h3>
      <p>Email: {user.email}</p>
    </div>
  );
}

function UserPosts({ userId }) {
  const postsResource = fetchData(`posts-${userId}`, () => fetchPostsByUserId(userId));
  const posts = postsResource.read(); // पोस्ट्स डेटा तयार नसल्यास हे सस्पेंड होईल

  return (
    <div>
      <h4>Posts by {userId}:</h4>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
        {posts.length === 0 && <li>No posts found.</li>}
      </ul>
    </div>
  );
}

// --- ॲप्लिकेशन --- //
let initialUserResource = null;
let initialPostsResource = null;

function prefetchDataForUser(userId) {
  initialUserResource = fetchData(`user-${userId}`, () => fetchUserById(userId));
  initialPostsResource = fetchData(`posts-${userId}`, () => fetchPostsByUserId(userId));
}

// App कंपोनंट रेंडर होण्यापूर्वीच काही डेटा प्री-फेच करा
prefetchDataForUser('1');

function App() {
  return (
    <div>
      <h1>Fetch-As-You-Render with Suspense</h1>
      <p>This demonstrates how data fetching can happen in parallel, coordinated by Suspense.</p>

      <Suspense fallback={<div>Loading user profile and posts...</div>}>
        <UserProfile userId={"1"} />
        <UserPosts userId={"1"} />
      </Suspense>

      <h2>Another Section</h2>
      <Suspense fallback={<div>Loading different user...</div>}>
        <UserProfile userId={"2"} />
      </Suspense>
    </div>
  );
}

या उदाहरणात:

फेच-ॲज-यू-रेंडरसाठी लायब्ररीज

मजबूत रिसोर्स मॅनेजर मॅन्युअली तयार करणे आणि त्याची देखभाल करणे गुंतागुंतीचे आहे. सुदैवाने, अनेक परिपक्व डेटा फेचिंग लायब्ररीजनी सस्पेन्स स्वीकारला आहे किंवा स्वीकारत आहेत, ज्यामुळे युद्ध-परीक्षित सोल्यूशन्स मिळतात:

या लायब्ररीज रिसोर्सेस तयार करणे आणि व्यवस्थापित करणे, कॅशिंग हाताळणे, रिव्हॅलिडेशन, ऑप्टिमिस्टिक अपडेट्स आणि एरर हँडलिंग यांसारख्या गुंतागुंतीच्या गोष्टींना सोपे करतात, ज्यामुळे फेच-ॲज-यू-रेंडर लागू करणे खूप सोपे होते.

पॅटर्न ४: सस्पेन्स-अवेअर लायब्ररीजसह प्रीफेचिंग

प्रीफेचिंग एक शक्तिशाली ऑप्टिमायझेशन आहे जिथे तुम्ही युझरला भविष्यात आवश्यक असलेला डेटा सक्रियपणे फेच करता, तो स्पष्टपणे विनंती करण्यापूर्वीच. यामुळे जाणवणारी कामगिरी (perceived performance) लक्षणीयरीत्या सुधारू शकते.

सस्पेन्स-अवेअर लायब्ररीजसह, प्रीफेचिंग अखंड होते. तुम्ही युझरच्या अशा परस्परसंवादांवर डेटा फेच ट्रिगर करू शकता जे तात्काळ UI बदलत नाहीत, जसे की लिंकवर हॉवर करणे किंवा बटणावर माउस फिरवणे.

import React, { Suspense } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';

// समजा हे तुमचे API कॉल्स आहेत
const fetchProductById = async (id) => {
  console.log(`Fetching product ${id}...`);
  return new Promise(resolve => setTimeout(() => {
    const products = {
      'A001': { id: 'A001', name: 'Global Widget X', price: 29.99, description: 'A versatile widget for international use.' },
      'B002': { id: 'B002', name: 'Universal Gadget Y', price: 149.99, description: 'Cutting-edge gadget, loved worldwide.' },
    };
    resolve(products[id]);
  }, 1000));
};

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true, // सर्व क्वेरीजसाठी डीफॉल्टनुसार सस्पेन्स सक्षम करा
    },
  },
});

function ProductDetails({ productId }) {
  const { data: product } = useQuery({
    queryKey: ['product', productId],
    queryFn: () => fetchProductById(productId),
  });

  return (
    <div style={{"border": "1px solid #ccc", "padding": "15px", "margin": "10px 0"}}>
      <h3>{product.name}</h3>
      <p>Price: ${product.price.toFixed(2)}</p>
      <p>{product.description}</p>
    </div>
  );
}

function ProductList() {
  const handleProductHover = (productId) => {
    // युझर एखाद्या प्रोडक्ट लिंकवर हॉवर करतो तेव्हा डेटा प्रीफेच करा
    queryClient.prefetchQuery({
      queryKey: ['product', productId],
      queryFn: () => fetchProductById(productId),
    });
    console.log(`Prefetching product ${productId}`);
  };

  return (
    <div>
      <h2>Available Products:</h2>
      <ul>
        <li>
          <a href="#" onMouseEnter={() => handleProductHover('A001')}
             onClick={(e) => { e.preventDefault(); /* नेव्हिगेट करा किंवा तपशील दाखवा */ }}
          >Global Widget X (A001)</a>
        </li>
        <li>
          <a href="#" onMouseEnter={() => handleProductHover('B002')}
             onClick={(e) => { e.preventDefault(); /* नेव्हिगेट करा किंवा तपशील दाखवा */ }}
          >Universal Gadget Y (B002)</a>
        </li>
      </ul>
      <p>Hover over a product link to see prefetching in action. Open network tab to observe.</p>
    </div>
  );
}

function App() {
  const [showProductA, setShowProductA] = React.useState(false);
  const [showProductB, setShowProductB] = React.useState(false);

  return (
    <QueryClientProvider client={queryClient}>
      <h1>Prefetching with React Suspense (React Query)</h1>
      <ProductList />

      <button onClick={() => setShowProductA(true)}>Show Global Widget X</button>
      <button onClick={() => setShowProductB(true)}>Show Universal Gadget Y</button>

      {showProductA && (
        <Suspense fallback={<p>Loading Global Widget X...</p>}>
          <ProductDetails productId="A001" />
        </Suspense>
      )}
      {showProductB && (
        <Suspense fallback={<p>Loading Universal Gadget Y...</p>}>
          <ProductDetails productId="B002" />
        </Suspense>
      )}
    </QueryClientProvider>
  );
}

या उदाहरणात, प्रोडक्ट लिंकवर हॉवर केल्यावर `queryClient.prefetchQuery` ट्रिगर होते, जे बॅकग्राउंडमध्ये डेटा फेच सुरू करते. जर युझर नंतर प्रोडक्ट तपशील पाहण्यासाठी बटणावर क्लिक करतो, आणि डेटा आधीच प्रीफेचमधून कॅशेमध्ये असेल, तर कंपोनंट सस्पेंड न होता त्वरित रेंडर होईल. जर प्रीफेच अजूनही प्रगतीत असेल किंवा सुरू झाला नसेल, तर डेटा तयार होईपर्यंत सस्पेन्स फॉलबॅक प्रदर्शित करेल.

सस्पेन्स आणि एरर बाऊंड्रीजसह एरर हँडलिंग

सस्पेन्स 'लोडिंग' स्टेटला फॉलबॅक दाखवून हाताळतो, परंतु तो थेट 'एरर' स्टेट्स हाताळत नाही. जर सस्पेंडिंग कंपोनंटने फेकलेले प्रॉमिस रिजेक्ट झाले (म्हणजे, डेटा फेचिंग अयशस्वी झाले), तर ही एरर कंपोनंट ट्रीमध्ये वरच्या दिशेने पसरेल. या एरर्सना व्यवस्थित हाताळण्यासाठी आणि योग्य UI प्रदर्शित करण्यासाठी, तुम्हाला एरर बाऊंड्रीज वापरण्याची आवश्यकता आहे.

एरर बाऊंड्री हा एक रिॲक्ट कंपोनंट आहे जो componentDidCatch किंवा static getDerivedStateFromError लाईफसायकल मेथड्स लागू करतो. तो त्याच्या चाइल्ड कंपोनंट ट्रीमध्ये कुठेही जावास्क्रिप्ट एरर्स पकडतो, ज्यात सस्पेन्सद्वारे सामान्यतः पकडले जाणारे प्रॉमिसेस फेकलेल्या एरर्सचा समावेश असतो जर ते पेंडिंग असते.

import React, { Suspense, useState } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';

// --- एरर बाऊंड्री कंपोनंट --- //
class MyErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    // स्टेट अपडेट करा जेणेकरून पुढील रेंडर फॉलबॅक UI दर्शवेल.
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // तुम्ही एरर रिपोर्टिंग सर्व्हिसला एरर लॉग देखील करू शकता
    console.error("Caught an error:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // तुम्ही कोणतेही कस्टम फॉलबॅक UI रेंडर करू शकता
      return (
        <div style={{"border": "2px solid red", "padding": "20px", "margin": "20px 0", "background": "#ffe0e0"}}>
          <h2>Something went wrong!</h2>
          <p>{this.state.error && this.state.error.message}</p>
          <p>Please try refreshing the page or contact support.</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>Try Again</button>
        </div>
      );
    }
    return this.props.children;
  }
}

// --- डेटा फेचिंग (एररच्या संभाव्यतेसह) --- //
const fetchItemById = async (id) => {
  console.log(`Attempting to fetch item ${id}...`);
  return new Promise((resolve, reject) => setTimeout(() => {
    if (id === 'error-item') {
      reject(new Error('Failed to load item: Network unreachable or item not found.'));
    } else if (id === 'slow-item') {
      resolve({ id: 'slow-item', name: 'Delivered Slowly', data: 'This item took a while but arrived!', status: 'success' });
    } else {
      resolve({ id, name: `Item ${id}`, data: `Data for item ${id}` });
    }
  }, id === 'slow-item' ? 3000 : 800));
};

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
      retry: false, // प्रात्यक्षिकासाठी, रिट्राय अक्षम करा जेणेकरून एरर त्वरित येईल
    },
  },
});

function DisplayItem({ itemId }) {
  const { data: item } = useQuery({
    queryKey: ['item', itemId],
    queryFn: () => fetchItemById(itemId),
  });

  return (
    <div>
      <h3>Item Details:</h3>
      <p>ID: {item.id}</p>
      <p>Name: {item.name}</p>
      <p>Data: {item.data}</p>
    </div>
  );
}

function App() {
  const [fetchType, setFetchType] = useState('normal-item');

  return (
    <QueryClientProvider client={queryClient}>
      <h1>Suspense and Error Boundaries</h1>

      <div>
        <button onClick={() => setFetchType('normal-item')}>Fetch Normal Item</button>
        <button onClick={() => setFetchType('slow-item')}>Fetch Slow Item</button>
        <button onClick={() => setFetchType('error-item')}>Fetch Error Item</button>
      </div>

      <MyErrorBoundary>
        <Suspense fallback={<p>Loading item via Suspense...</p>}>
          <DisplayItem itemId={fetchType} />
        </Suspense>
      </MyErrorBoundary>
    </QueryClientProvider>
  );
}

तुमच्या सस्पेन्स बाऊंड्रीला (किंवा सस्पेंड होऊ शकणाऱ्या कंपोनंट्सना) एरर बाऊंड्रीने रॅप करून, तुम्ही खात्री करता की डेटा फेचिंग दरम्यान नेटवर्क अयशस्वी झाल्यास किंवा सर्व्हर एरर्स आल्यास ते व्यवस्थित पकडले जातील आणि हाताळले जातील, ज्यामुळे संपूर्ण ॲप्लिकेशन क्रॅश होण्यापासून वाचते. हे एक मजबूत आणि युझर-फ्रेंडली अनुभव प्रदान करते, ज्यामुळे युझर्सना समस्या समजून घेण्याची आणि संभाव्यतः पुन्हा प्रयत्न करण्याची संधी मिळते.

सस्पेन्ससह स्टेट मॅनेजमेंट आणि डेटा इनव्हॅलिडेशन

हे स्पष्ट करणे महत्त्वाचे आहे की रिॲक्ट सस्पेन्स प्रामुख्याने असिंक्रोनस रिसोर्सेसच्या सुरुवातीच्या लोडिंग स्थितीला संबोधित करतो. ते मूळतः क्लायंट-साइड कॅशे व्यवस्थापित करत नाही, डेटा इनव्हॅलिडेशन हाताळत नाही, किंवा म्युटेशन्स (क्रिएट, अपडेट, डिलीट ऑपरेशन्स) आणि त्यांचे त्यानंतरचे UI अपडेट्स आयोजित करत नाही.

येथेच सस्पेन्स-अवेअर डेटा फेचिंग लायब्ररीज (React Query, SWR, Apollo Client, Relay) अपरिहार्य बनतात. ते सस्पेन्सला पूरक ठरतात आणि खालील गोष्टी प्रदान करतात:

एक मजबूत डेटा फेचिंग लायब्ररीशिवाय, या वैशिष्ट्यांची अंमलबजावणी मॅन्युअल सस्पेन्स रिसोर्स मॅनेजरवर करणे हे एक मोठे काम असेल, ज्यासाठी तुम्हाला स्वतःचे डेटा फेचिंग फ्रेमवर्क तयार करावे लागेल.

व्यावहारिक विचार आणि सर्वोत्तम पद्धती

डेटा फेचिंगसाठी सस्पेन्सचा अवलंब करणे हा एक महत्त्वाचा आर्किटेक्चरल निर्णय आहे. जागतिक ॲप्लिकेशनसाठी येथे काही व्यावहारिक विचार आहेत:

१. सर्व डेटासाठी सस्पेन्सची आवश्यकता नाही

सस्पेन्स अशा महत्त्वपूर्ण डेटासाठी आदर्श आहे जो कंपोनंटच्या सुरुवातीच्या रेंडरिंगवर थेट परिणाम करतो. गैर-महत्वपूर्ण डेटा, बॅकग्राउंड फेचेस किंवा ज्या डेटाला मजबूत व्हिज्युअल प्रभावाशिवाय आळशीपणे लोड केले जाऊ शकते, त्यासाठी पारंपारिक useEffect किंवा प्री-रेंडरिंग अजूनही योग्य असू शकते. सस्पेन्सचा अतिवापर केल्यास कमी ग्रॅन्युलर लोडिंग अनुभव येऊ शकतो, कारण एकच सस्पेन्स बाऊंड्री तिच्या *सर्व* चिल्ड्रनना रिझॉल्व्ह होण्याची वाट पाहते.

२. सस्पेन्स बाऊंड्रीजची ग्रॅन्युलॅरिटी

तुमच्या <Suspense> बाऊंड्रीज विचारपूर्वक ठेवा. तुमच्या ॲप्लिकेशनच्या शीर्षस्थानी एकच, मोठी बाऊंड्री संपूर्ण पेज एका स्पिनरच्या मागे लपवू शकते, जे निराशाजनक असू शकते. लहान, अधिक ग्रॅन्युलर बाऊंड्रीज तुमच्या पेजच्या वेगवेगळ्या भागांना स्वतंत्रपणे लोड करण्याची परवानगी देतात, ज्यामुळे अधिक प्रगतीशील आणि प्रतिसादात्मक अनुभव मिळतो. उदाहरणार्थ, युझर प्रोफाइल कंपोनंटभोवती एक बाऊंड्री, आणि शिफारस केलेल्या उत्पादनांच्या सूचीभोवती दुसरी.

<div>
  <h1>Product Page</h1>
  <Suspense fallback={<p>Loading main product details...</p>}>
    <ProductDetails id="prod123" />
  </Suspense>

  <hr />

  <h2>Related Products</h2>
  <Suspense fallback={<p>Loading related products...</p>}>
    <RelatedProducts category="electronics" />
  </Suspense>
</div>

या दृष्टिकोनाचा अर्थ असा आहे की संबंधित उत्पादने अजूनही लोड होत असली तरीही युझर्स मुख्य उत्पादनाचे तपशील पाहू शकतात.

३. सर्वर-साइड रेंडरिंग (SSR) आणि स्ट्रीमिंग HTML

रिॲक्ट 18 चे नवीन स्ट्रीमिंग SSR APIs (renderToPipeableStream) सस्पेन्ससह पूर्णपणे एकत्रित आहेत. यामुळे तुमच्या सर्व्हरला HTML तयार होताच पाठवण्याची परवानगी मिळते, जरी पेजचे काही भाग (जसे की डेटा-अवलंबून कंपोनंट्स) अजूनही लोड होत असले तरीही. सर्व्हर एक प्लेसहोल्डर (सस्पेन्स फॉलबॅकमधून) स्ट्रीम करू शकतो आणि नंतर डेटा रिझॉल्व्ह झाल्यावर वास्तविक कंटेंट स्ट्रीम करू शकतो, ज्यासाठी पूर्ण क्लायंट-साइड री-रेंडरची आवश्यकता नसते. हे विविध नेटवर्क परिस्थितींवर जागतिक युझर्ससाठी जाणवणारी लोडिंग कामगिरी लक्षणीयरीत्या सुधारते.

४. टप्प्याटप्प्याने अवलंब

सस्पेन्स वापरण्यासाठी तुम्हाला तुमचे संपूर्ण ॲप्लिकेशन पुन्हा लिहिण्याची गरज नाही. तुम्ही ते टप्प्याटप्प्याने सादर करू शकता, नवीन वैशिष्ट्ये किंवा कंपोनंट्सपासून सुरुवात करून ज्यांना त्याच्या डिक्लरेटिव्ह लोडिंग पॅटर्न्सचा सर्वाधिक फायदा होईल.

५. टूलिंग आणि डीबगिंग

सस्पेन्स कंपोनंट लॉजिकला सोपे करत असले तरी, डीबगिंग वेगळे असू शकते. रिॲक्ट डेव्हटूल्स सस्पेन्स बाऊंड्रीज आणि त्यांच्या स्थितीबद्दल माहिती देतात. तुमच्या निवडलेल्या डेटा फेचिंग लायब्ररीने तिची अंतर्गत स्थिती कशी उघड केली आहे याच्याशी परिचित व्हा (उदा. React Query Devtools).

६. सस्पेन्स फॉलबॅक्ससाठी टाइमआउट्स

खूप जास्त लोडिंग वेळेसाठी, तुम्ही तुमच्या सस्पेन्स फॉलबॅकमध्ये टाइमआउट लावू शकता, किंवा ठराविक विलंबानंतर अधिक तपशीलवार लोडिंग इंडिकेटरवर स्विच करू शकता. रिॲक्ट 18 मधील useDeferredValue आणि useTransition हुक्स या अधिक सूक्ष्म लोडिंग स्थिती व्यवस्थापित करण्यात मदत करू शकतात, ज्यामुळे नवीन डेटा फेच होत असताना तुम्हाला UI ची 'जुनी' आवृत्ती दाखवण्याची किंवा गैर-तातडीचे अपडेट्स पुढे ढकलण्याची परवानगी मिळते.

रिॲक्टमधील डेटा फेचिंगचे भविष्य: रिॲक्ट सर्वर कंपोनंट्स आणि त्यापुढील

रिॲक्टमधील डेटा फेचिंगचा प्रवास क्लायंट-साइड सस्पेन्सवर थांबत नाही. रिॲक्ट सर्वर कंपोनंट्स (RSC) एक महत्त्वपूर्ण उत्क्रांती दर्शवतात, जे क्लायंट आणि सर्व्हरमधील रेषा पुसून टाकण्याचे आणि डेटा फेचिंगला आणखी ऑप्टिमाइझ करण्याचे वचन देतात.

रिॲक्ट जसजसा परिपक्व होत जाईल, तसतसे सस्पेन्स अत्यंत कार्यक्षम, युझर-फ्रेंडली आणि देखरेख करण्यायोग्य ॲप्लिकेशन्स तयार करण्यासाठी कोड्याच्या वाढत्या केंद्रीय भागांपैकी एक असेल. हे डेव्हलपर्सना असिंक्रोनस ऑपरेशन्स हाताळण्याच्या अधिक डिक्लरेटिव्ह आणि लवचिक मार्गाकडे ढकलते, ज्यामुळे गुंतागुंत वैयक्तिक कंपोनंट्समधून एका चांगल्या व्यवस्थापित डेटा लेयरमध्ये हलवली जाते.

निष्कर्ष

रिॲक्ट सस्पेन्स, सुरुवातीला कोड स्प्लिटिंगसाठी एक वैशिष्ट्य, डेटा फेचिंगसाठी एका परिवर्तनीय साधनात विकसित झाले आहे. फेच-ॲज-यू-रेंडर पॅटर्नचा स्वीकार करून आणि सस्पेन्स-अवेअर लायब्ररीजचा फायदा घेऊन, डेव्हलपर्स त्यांच्या ॲप्लिकेशन्सचा युझर अनुभव लक्षणीयरीत्या सुधारू शकतात, लोडिंग वॉटरफॉल्स दूर करू शकतात, कंपोनंट लॉजिक सोपे करू शकतात आणि सहज, समन्वित लोडिंग स्टेट्स प्रदान करू शकतात. मजबूत एरर हँडलिंगसाठी एरर बाऊंड्रीज आणि रिॲक्ट सर्वर कंपोनंट्सच्या भविष्यातील वचनासह, सस्पेन्स आपल्याला असे ॲप्लिकेशन्स तयार करण्यास सक्षम करतो जे केवळ कार्यक्षम आणि लवचिक नाहीत तर जगभरातील युझर्ससाठी मूळतः अधिक आनंददायक आहेत. सस्पेन्स-चालित डेटा फेचिंग पॅराडाइममध्ये बदल करण्यासाठी संकल्पनात्मक समायोजनाची आवश्यकता आहे, परंतु कोड स्पष्टता, कार्यक्षमता आणि युझर समाधानाच्या बाबतीत मिळणारे फायदे भरीव आहेत आणि गुंतवणुकीस पात्र आहेत.