बिना ज़रूरत वाले कंपोनेंट्स की परतों के माध्यम से प्रॉप्स पास करना थका देने वाला काम है। एक दिन आप कोई फीचर शिप कर रहे होते हैं, और अगले ही दिन सिर्फ एक प्रॉप का नाम बदलने के लिए आपको छह फाइलें एडिट करनी पड़ती हैं। Prop drilling संक्षेप में यही है: एक पैरेंट के पास डेटा है, एक गहरे चाइल्ड को उसकी ज़रूरत है, और उनके बीच का हर कंपोनेंट एक कूरियर बन जाता है। ऐप अभी भी चलता है, लेकिन कोडबेस नाजुक (brittle) हो जाता है। एक बीच की लेयर हटा दें, और आधा ट्री ढह जाता है। एक टाइप बदलें, और TypeScript तीन अलग-अलग डायरेक्टरीज़ में शिकायत करने लगता है। React Context API इन बिचौलियों को पूरी तरह से हटाने के लिए बनाया गया है।

Prop Drilling वास्तव में कैसा दिखता है

एक स्टैंडर्ड ऐप शेल की कल्पना करें। आपके पास एक App कंपोनेंट है जो वर्तमान यूजर को फेच करता है। App के अंदर एक Layout रहता है, Layout के अंदर एक Sidebar होता है, Sidebar के अंदर Navigation नेस्टेड है, और अंत में Navigation के अंदर आपको UserAvatar मिलता है जिसे वास्तव में यूजर ऑब्जेक्ट की ज़रूरत है।

आपका कोड कुछ ऐसा दिखता है:

function App() {
  const user = { name: 'Aarav', role: 'admin' };
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  return <Navigation user={user} />;
}

function Navigation({ user }) {
  return <UserAvatar user={user} />;
}

Layout, Sidebar, और Navigation उस यूजर ऑब्जेक्ट के साथ कुछ नहीं करते सिवाय उसे आगे पास करने के। वे ऐसे प्रॉप्स इकट्ठा करते हैं जिनके वे मालिक नहीं हैं, उनके इंटरफेस बढ़ जाते हैं, और उन्हें टेस्ट करने के लिए ऐसे डेटा को मॉक (mock) करना पड़ता है जिसे वे कभी छूते भी नहीं हैं। असली समस्या यह है कि यह कितनी तेज़ी से फैलता है। एक isLoggedIn फ्लैग, एक locale स्ट्रिंग, या एक theme वैल्यू जोड़ें, और वही सिलसिला दोहराया जाता है।

Context API गेम को कैसे बदल देता है

Context API को एक WiFi राउटर की तरह समझें। हर डिवाइस तक पहुँचने के लिए हर कमरे में लंबी केबल बिछाने के बजाय, राउटर हवा के माध्यम से सिग्नल भेजता है। रेंज में मौजूद कोई भी डिवाइस सीधे कनेक्ट हो सकता है। React के संदर्भ में, राउटर 'Provider' है, सिग्नल आपका स्टेट या डेटा है, और डिवाइस कोई भी नेस्टेड कंपोनेंट है जो useContext को कॉल करता है।

इसके सेटअप में तीन मुख्य भाग होते हैं:

  • React.createContext() डेटा चैनल बनाता है।
  • Provider आपके ट्री के एक हिस्से को रैप करता है और एक वैल्यू ट्रांसमिट करता है।
  • useContext हुक वंशजों (descendants) को बीच के प्रॉप्स को छुए बिना उस वैल्यू को प्राप्त करने की अनुमति देता है।

आपके पास अभी भी एक ट्री है, लेकिन रूट और लीफ (पत्ती) के बीच की शाखाओं को अब कूरियर कॉन्ट्रैक्ट पर सहमत होने की ज़रूरत नहीं है।

शुरुआत से Context बनाना

आइए थीम सेटिंग्स के साथ एक ठोस उदाहरण बनाते हैं, क्योंकि अधिकांश ऐप्स को किसी न किसी समय लाइट या डार्क मोड की आवश्यकता होती है।

सबसे पहले, कॉन्टेक्स्ट ऑब्जेक्ट बनाएं। यह पाइप की तरह है:

import { createContext, useState, useMemo } from 'react';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export default ThemeContext;

फिर अपने एप्लिकेशन को प्रोवाइडर में रैप करें। आमतौर पर यह रूट के पास होता है:

import { ThemeProvider } from './ThemeContext';

function App() {
  return (
    <ThemeProvider>
      <Layout />
    </ThemeProvider>
  );
}

अब कोई भी वंशज सीधे सिग्नल प्राप्त कर सकता है। यहाँ UI के बहुत अंदर दबा हुआ एक टॉगल बटन है:

import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={() => setTheme(prev => prev === 'light' ? 'dark' : 'light')}
    >
      Current theme: {theme}
    </button>
  );
}

ध्यान दें कि Layout, Sidebar, और Navigation कभी भी theme प्रॉप को नहीं देखते हैं। वे सामान्य रूप से रेंडर होते हैं, और ThemeToggle सीधे कॉन्टेक्स्ट से वह लेता है जिसकी उसे ज़रूरत है। इसकी वायरिंग बाहर से अदृश्य है, और यही इसका मुख्य उद्देश्य है।

Context वास्तव में कहाँ फिट बैठता है

Context उस डेटा के लिए सबसे अच्छा काम करता है जिसे कई दूर स्थित कंपोनेंट्स साझा करते हैं लेकिन कोई भी एक पैरेंट उसे पूरी तरह से मैनेज नहीं करता। अच्छे उम्मीदवारों में शामिल हैं:

  • थीम सेटिंग्स जैसे लाइट या डार्क मोड, एक्सेंट कलर्स, या फ़ॉन्ट स्केलिंग।
  • Authentication state जैसे वर्तमान यूजर ऑब्जेक्ट, लॉगिन स्टेटस, या सेशन एक्सपायरी।
  • भाषा और locale सेटिंग्स इंटरनेशनलइज़ेशन के लिए।
  • Shopping cart data जिसे हेडर बैज, मिनी-कार्ट ड्रॉपडाउन और चेकआउट पेज पर सिंक रहना चाहिए।

हर लोकल स्टेट को Context में डालने की इच्छा से बचें। दो लेवल नीचे स्थित एक फॉर्म इनपुट को ग्लोबल ब्रॉडकास्ट की आवश्यकता नहीं है। Context को वास्तविक क्रॉस-कटिंग कंसर्न्स (cross-cutting concerns) के लिए रखें, और बाकी को साधारण प्रॉप्स के रूप में रहने दें।

परफॉरमेंस ट्रैप्स और उनसे कैसे बचें

Context मुफ़्त नहीं है। जब कोई कॉन्टेक्स्ट वैल्यू अपडेट होती है, तो उस कॉन्टेक्स्ट से जुड़ा हर कंपोनेंट री-रेंडर होता है, भले ही वैल्यू का वह हिस्सा न बदला हो जिसकी उन्हें परवाह है। क्लासिक गलती हर पैरेंट रेंडर पर प्रोवाइडर में एक नया ऑब्जेक्ट लिटरल (object literal) डालना है।

हमारे थीम उदाहरण में, हर बार जब ThemeProvider री-रेंडर होता है क्योंकि उसका अपना पैरेंट अपडेट हुआ है, तो एक्सप्रेशन { theme, setTheme } एक बिल्कुल नया ऑब्जेक्ट बनाता है। React एक नया रेफरेंस देखता है, और हर कंज्यूमर अपडेट हो जाता है। यदि आपकी थीम शायद ही कभी बदलती है लेकिन आपके ऐप का स्टेट अक्सर बदलता है, तो आप उन रेंडर्स के लिए भुगतान कर रहे हैं जिनकी आपको ज़रूरत नहीं है।

इसका समाधान दोतरफा है।

अपने कॉन्टेक्स्ट को अपडेट की आवृत्ति (frequency) के आधार पर विभाजित करें। एक UserContext जो लॉगिन के प्रति बार बदलता है, उसे NotificationContext के साथ प्रोवाइडर साझा नहीं करना चाहिए जो हर कुछ सेकंड में अपडेट होता है। उन्हें अलग रखें ताकि स्टैटिक डेटा उसी री-रेंडर ट्रेन पर न सवार हो जाए जिस पर वोलेटाइल (volatile) डेटा सवार है।

जब वैल्यू एक ऑब्जेक्ट या ऐरे हो, तो उसे useMemo में रैप करें। React को एक स्टेबल रेफरेंस दें:

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const value = useMemo(() => ({ theme, setTheme }), [theme]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

अब ऑब्जेक्ट आइडेंटिटी (object identity) तभी बदलती है जब theme वास्तव में बदलता है। वंशज घटक (descendant components) जो कॉन्टेक्स्ट पर निर्भर हैं लेकिन आगे React.memo द्वारा सुरक्षित हैं, वे अतिरिक्त काम करने से बच जाएंगे।

वे गलतियाँ जो आपका समय बर्बाद करती हैं

वे दो त्रुटियाँ जो अभी भी प्रोडक्शन कोड में दिखाई देती हैं, उन्हें रोकना आसान है।

पहली बात, कॉन्टेक्स्ट को ही एक्सपोर्ट करना भूल जाना। यदि आप केवल Provider wrapper को एक्सपोर्ट करते हैं और context ऑब्जेक्ट को प्राइवेट रखते हैं, तो नया फीचर लिखने वाला डेवलपर आपके मॉड्यूल को रिफैक्टर (refactor) किए बिना useContext को कॉल नहीं कर सकता। कॉन्टेक्स्ट को एक्सपोर्ट करें ताकि उपभोक्ता (consumers) प्रोवाइडर और कंज्यूमर हुक दोनों को आसानी से इम्पोर्ट कर सकें।

दूसरी बात, संबंधित Provider के बाहर useContext को कॉल करना। यदि ThemeToggle ट्री की ऐसी शाखा (branch) में रेंडर होता है जो ThemeProvider में रैप नहीं है, तो हुक createContext को पास की गई डिफॉल्ट वैल्यू लौटाता है, या यदि आपने कुछ भी पास नहीं किया है तो undefined लौटाता है। इससे cannot read property of undefined जैसी साइलेंट फेलियर (silent failures) हो सकती हैं। आप एक उचित डिफॉल्ट मान असाइन करके या हुक कॉल के दौरान ही एक स्पष्ट त्रुटि (error) थ्रो करके इससे बच सकते हैं।

Context बनाम Redux: इसे सरल रखें

आपको हमेशा Redux की आवश्यकता नहीं होती है। मध्यम आकार के प्रोजेक्ट्स के लिए, useState या useReducer के साथ जोड़ा गया Context, स्टेट शेयरिंग (state sharing) के अधिकांश हिस्से को कवर कर लेता है। Redux तब काम आता है जब आपको time-travel debugging, जटिल middleware, या वैश्विक लेनदेन (global transactions) की आवश्यकता होती है जिन्हें क्रम में रोलबैक (roll back) करना हो। यदि आपकी पूरी स्टेट की कहानी एक user object, एक theme string और एक cart array है, तो एक store library अतिरिक्त boilerplate जोड़ देगी जिसका आप कभी लाभ नहीं उठा पाएंगे।

हालांकि, Context अपने आप में एक पूर्ण स्टेट मैनेजमेंट सिस्टम नहीं है। यह आपको एक एकल वैश्विक स्नैपशॉट (global snapshot) नहीं देता है, और यह असंबंधित कॉन्टेक्स्ट के बीच अपडेट को बैच (batch) नहीं करता है। इसका उपयोग prop drilling के विकल्प के रूप में करें, न कि अपने पूरे डेटा लेयर के ऑपरेटिंग सिस्टम के रूप में।

मुख्य निष्कर्ष

उन कंपोनेंट्स के माध्यम से props को पास करना बंद करें जिन्हें उनकी आवश्यकता नहीं है। उस डेटा के लिए केंद्रित कॉन्टेक्स्ट (focused contexts) बनाएं जो वास्तव में आपके ट्री में फैला हुआ है, उपभोक्ताओं को कवर करने के लिए प्रोवाइडर्स को पर्याप्त ऊंचाई पर रैप करें, और जब आप कलेक्शन या फंक्शन पास कर रहे हों तो हमेशा वैल्यू ऑब्जेक्ट को स्थिर (stabilize) रखें। Context API आपके React कोड को सीधा रखता है: props स्थानीय (local) रहते हैं, वैश्विक डेटा वायरलेस तरीके से यात्रा करता है, और आपकी कंपोनेंट सीमाएं (component boundaries) साफ रहती हैं।