Refs के साथ DOM में बदलाव करना
React स्वचालित रूप से DOM को आपके रेंडर आउटपुट के अनुसार अपडेट करता है, जिससे आपके कौम्पोनॅन्टस को अक्सर इसे मैन्युपुलेट करने की आवश्यकता नहीं होती। हालांकि, कभी-कभी आपको React द्वारा मैनेज किए गए DOM एलिमेंट्स तक पहुंचने की आवश्यकता हो सकती है—जैसे किसी नोड को फोकस करना, स्क्रॉल करना, या उसका आकार और स्थिति मापना। React में इन चीज़ों के लिए कोई बिल्ट-इन तरीका नहीं है, इसलिए आपको DOM नोड के लिए एक ref की आवश्यकता होगी।
You will learn
- React द्वारा मैनेज किए गए DOM नोड तक
ref
एट्रिब्यूट के साथ कैसे पहुंचें ref
JSX एट्रिब्यूट काuseRef
हुक से क्या संबंध है- किसी दूसरे कौम्पोनॅन्ट के DOM नोड तक कैसे पहुंचें
- किन मामलों में React द्वारा मैनेज किए गए DOM को बदलना सुरक्षित है
नोड के लिए ref प्राप्त करना
React द्वारा मैनेज किए गए DOM नोड तक पहुंचने के लिए, सबसे पहले useRef
हुक को इम्पोर्ट करें:
import { useRef } from 'react';
फिर, इसे अपने कौम्पोनॅन्ट के अंदर एक ref डिक्लेअर करने के लिए उपयोग करें:
const myRef = useRef(null);
अंत में, अपने ref को उस JSX टैग के ref
एट्रिब्यूट के रूप में पास करें जिसके लिए आप DOM नोड प्राप्त करना चाहते हैं:
<div ref={myRef}>
useRef
हुक एक ऑब्जेक्ट रिटर्न करता है जिसमें एकमात्र प्रॉपर्टी होती है, जिसे current
कहा जाता है। शुरुआत में, myRef.current
का मान null
होगा। जब React इस <div>
के लिए एक DOM नोड बनाएगा, तो React इस नोड का रेफरेंस myRef.current
में डाल देगा। इसके बाद आप इस DOM नोड को अपने इवेंट हैंडलर्स से एक्सेस कर सकते हैं और उसकी बिल्ट-इन ब्राउज़र APIs का उपयोग कर सकते हैं।
// आप किसी भी ब्राउज़र API का उपयोग कर सकते हैं, उदाहरण के लिए:
myRef.current.scrollIntoView();
उदाहरण: टेक्स्ट इनपुट पर फोकस करना
इस उदाहरण में, बटन पर क्लिक करने से इनपुट पर फोकस होगा:
import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> इनपुट पर फोकस करें </button> </> ); }
इसका उपयोग करने के लिए:
useRef
हुक के साथinputRef
डिक्लेअर करें।- इसे
<input ref={inputRef}>
के रूप में पास करें। यह React को यह बताता है कि इस<input>
के DOM नोड कोinputRef.current
में डालें। handleClick
फंक्शन में, इनपुट DOM नोड कोinputRef.current
से पढ़ें औरfocus()
कोinputRef.current.focus()
के साथ कॉल करें।handleClick
इवेंट हैंडलर को<button>
मेंonClick
के साथ पास करें।
हालांकि DOM मैनिपुलेशन रेफ्स के लिए सबसे आम उपयोग है, useRef
हुक का उपयोग React के बाहर अन्य चीजों, जैसे टाइमर IDs, को स्टोर करने के लिए भी किया जा सकता है। स्टेट की तरह, रेफ्स रेंडर के बीच बने रहते हैं। रेफ्स स्टेट वेरिएबल्स की तरह हैं, लेकिन इन्हें सेट करने पर री-रेंडर नहीं होता। रेफ्स के बारे में अधिक जानने के लिए पढ़ें Referencing Values with Refs.
उदाहरण: किसी एलिमेंट पर स्क्रॉल करना
एक कौम्पोनॅन्ट में एक से अधिक रेफ्स हो सकते हैं। इस उदाहरण में, तीन इमेजेस का कैरोसेल है। प्रत्येक बटन एक इमेज को केंद्रित करता है, इसके लिए ब्राउज़र के scrollIntoView()
मेथड को संबंधित DOM नोड पर कॉल किया जाता है:
import { useRef } from 'react'; export default function CatFriends() { const firstCatRef = useRef(null); const secondCatRef = useRef(null); const thirdCatRef = useRef(null); function handleScrollToFirstCat() { firstCatRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } function handleScrollToSecondCat() { secondCatRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } function handleScrollToThirdCat() { thirdCatRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } return ( <> <nav> <button onClick={handleScrollToFirstCat}> Neo </button> <button onClick={handleScrollToSecondCat}> Millie </button> <button onClick={handleScrollToThirdCat}> Bella </button> </nav> <div> <ul> <li> <img src="https://placecats.com/neo/300/200" alt="Neo" ref={firstCatRef} /> </li> <li> <img src="https://placecats.com/millie/200/200" alt="Millie" ref={secondCatRef} /> </li> <li> <img src="https://placecats.com/bella/199/200" alt="Bella" ref={thirdCatRef} /> </li> </ul> </div> </> ); }
Deep Dive
उपरोक्त उदाहरणों में, रिफ्स की संख्या पहले से तय होती है। लेकिन कभी-कभी आपको लिस्ट में प्रत्येक आइटम के लिए एक रिफ की ज़रूरत हो सकती है, और आपको पता नहीं होता कि कितने आइटम होंगे। ऐसा कुछ काम नहीं करेगा:
<ul>
{items.map((item) => {
// काम नहीं करेगा!
const ref = useRef(null);
return <li ref={ref} />;
})}
</ul>
ऐसा इसलिए है क्योंकि हुक्स को केवल आपके कौम्पोनॅन्ट के शीर्ष स्तर पर ही कॉल किया जाना चाहिए। आप useRef
को किसी लूप, कंडीशन, या map()
कॉल के अंदर नहीं कॉल कर सकते।
इस समस्या को हल करने का एक तरीका यह है कि उनके पैरेंट एलिमेंट के लिए एक रिफ लें और फिर DOM मैनिपुलेशन विधियों जैसे querySelectorAll
का उपयोग करके व्यक्तिगत चाइल्ड नोड्स “खोजें”। लेकिन यह तरीका नाजुक है और आपके DOM स्ट्रक्चर के बदलने पर टूट सकता है।
एक और समाधान है कि ref
एट्रिब्यूट को एक फ़ंक्शन पास करें। इसे ref
कॉलबैक कहा जाता है। React आपके रिफ कॉलबैक को DOM नोड के साथ तब कॉल करेगा जब रिफ सेट करना हो, और null
के साथ जब रिफ को क्लियर करना हो। यह आपको अपनी खुद की Map या ऐरे बनाए रखने देता है और किसी रिफ तक इसकी इंडेक्स या किसी प्रकार के ID के माध्यम से पहुंचने देता है।
यह उदाहरण दिखाता है कि आप इस तरीके का उपयोग करके किसी लंबी लिस्ट में किसी भी नोड को स्क्रॉल कैसे कर सकते हैं:
import { useRef, useState } from "react"; export default function CatFriends() { const itemsRef = useRef(null); const [catList, setCatList] = useState(setupCatList); function scrollToCat(cat) { const map = getMap(); const node = map.get(cat); node.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center", }); } function getMap() { if (!itemsRef.current) { // पहली बार उपयोग पर Map को इनिशियलाइज़ करें। itemsRef.current = new Map(); } return itemsRef.current; } return ( <> <nav> <button onClick={() => scrollToCat(catList[0])}>Neo</button> <button onClick={() => scrollToCat(catList[5])}>Millie</button> <button onClick={() => scrollToCat(catList[9])}>Bella</button> </nav> <div> <ul> {catList.map((cat) => ( <li key={cat} ref={(node) => { const map = getMap(); map.set(cat, node); return () => { map.delete(cat); }; }} > <img src={cat} /> </li> ))} </ul> </div> </> ); } function setupCatList() { const catList = []; for (let i = 0; i < 10; i++) { catList.push("https://loremflickr.com/320/240/cat?lock=" + i); } return catList; }
इस उदाहरण में, itemsRef
एक ही DOM नोड को नहीं रखता है। इसके बजाय, यह एक Map रखता है, जो आइटम ID से DOM नोड को मैप करता है। (Refs किसी भी वैल्यू को रख सकते हैं!) प्रत्येक लिस्ट आइटम पर ref
कॉलबैक Map को अपडेट करने का ध्यान रखता है:
<li
key={cat.id}
ref={node => {
const map = getMap();
// Map में जोड़ें
map.set(cat, node);
return () => {
// Map से हटाएं
map.delete(cat);
};
}}
>
यह आपको बाद में Map से व्यक्तिगत DOM नोड्स पढ़ने की अनुमति देता है।
किसी अन्य कौम्पोनॅन्ट के DOM नोड्स तक पहुंचना
आप पैरेंट कौम्पोनॅन्ट से चाइल्ड कौम्पोनॅन्टस तक refs को किसी अन्य प्रॉप की तरह पास कर सकते हैं।
import { useRef } from 'react';
function MyInput({ ref }) {
return <input ref={ref} />;
}
function MyForm() {
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
}
ऊपर दिए गए उदाहरण में, एक ref पैरेंट कौम्पोनॅन्ट MyForm
में बनाया गया है और चाइल्ड कौम्पोनॅन्ट MyInput
को पास किया गया है। MyInput
फिर इस ref को <input>
को पास करता है। चूंकि <input>
एक बिल्ट-इन कौम्पोनॅन्ट है, React ref की .current
प्रॉपर्टी को <input>
DOM एलिमेंट पर सेट करता है।
MyForm
में बनाया गया inputRef
अब MyInput
द्वारा रिटर्न किए गए <input>
DOM एलिमेंट को पॉइंट करता है। MyForm
में बनाया गया एक क्लिक हैंडलर inputRef
को एक्सेस कर सकता है और <input>
पर फोकस सेट करने के लिए focus()
को कॉल कर सकता है।
import { useRef } from 'react'; function MyInput({ ref }) { return <input ref={ref} />; } export default function MyForm() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <MyInput ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
Deep Dive
ऊपर दिए गए उदाहरण में, MyInput
को पास किया गया ref ऑरिजिनल DOM इनपुट एलिमेंट तक पास होता है। यह पैरेंट कौम्पोनॅन्ट को इस पर focus()
कॉल करने की अनुमति देता है। हालांकि, इससे पैरेंट कौम्पोनॅन्ट को कुछ और भी करने की अनुमति मिलती है—उदाहरण के लिए, इसकी CSS स्टाइल्स को बदलना।
कुछ असामान्य मामलों में, आप एक्सपोज़ की गई कार्यक्षमता को सीमित करना चाह सकते हैं। आप यह useImperativeHandle
का उपयोग करके कर सकते हैं:
import { useRef, useImperativeHandle } from "react"; function MyInput({ ref }) { const realInputRef = useRef(null); useImperativeHandle(ref, () => ({ // केवल focus को एक्सपोज करें और कुछ नहीं focus() { realInputRef.current.focus(); }, })); return <input ref={realInputRef} />; }; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <MyInput ref={inputRef} /> <button onClick={handleClick}>Focus the input</button> </> ); }
यहां, MyInput
के अंदर realInputRef
असली इनपुट DOM नोड को होल्ड करता है। हालांकि, useImperativeHandle
React को निर्देश देता है कि वह पैरेंट कौम्पोनॅन्ट को एक कस्टम ऑब्जेक्ट को रिफ के रूप में प्रोवाइड करे।
इसलिए, Form
कौम्पोनॅन्ट के अंदर inputRef.current
केवल focus
मेथड तक ही पहुंच प्रदान करेगा। इस मामले में, रिफ “हैंडल” DOM नोड नहीं है, बल्कि वह कस्टम ऑब्जेक्ट है जिसे आप useImperativeHandle
कॉल के अंदर बनाते हैं।
जब React रेफ्स को जोड़ता है
React में, प्रत्येक अपडेट दो चरणों में बाँटा जाता है:
- रेंडर के दौरान, React आपके कौम्पोनॅन्टस को कॉल करता है ताकि यह पता लगा सके कि स्क्रीन पर क्या दिखना चाहिए।
- कमीट के दौरान, React DOM में बदलाव लागू करता है।
सामान्यतः, आप रेंडर के दौरान रेफ्स तक पहुँचने की कोशिश नहीं करना चाहते हैं। यह उन रेफ्स के लिए भी जाता है जो DOM नोड्स को होल्ड करते हैं। पहले रेंडर के दौरान, DOM नोड्स अभी तक बनाए नहीं गए होते हैं, इसलिए ref.current
null
होगा। और अपडेट के दौरान रेंडर करते समय, DOM नोड्स अभी तक अपडेट नहीं हुए होते हैं। इसलिए, उन्हें पढ़ने के लिए यह बहुत जल्दी होता है।
React ref.current
को कमीट के दौरान सेट करता है। DOM को अपडेट करने से पहले, React प्रभावित ref.current
मानों को null
पर सेट करता है। DOM को अपडेट करने के बाद, React इन्हें तुरंत संबंधित DOM नोड्स पर सेट कर देता है।
आमतौर पर, आप रेफ्स तक पहुँचने के लिए इवेंट हैंडलर्स का उपयोग करेंगे। अगर आप किसी रेफ के साथ कुछ करना चाहते हैं, लेकिन ऐसा कोई विशेष इवेंट नहीं है जिसमें इसे करना हो, तो आपको एक Effect की आवश्यकता हो सकती है। हम अगले पृष्ठों पर Effects के बारे में चर्चा करेंगे।
Deep Dive
ऐसे कोड पर विचार करें, जो एक नया टूडू जोड़ता है और स्क्रीन को लिस्ट के आखिरी चाइल्ड तक स्क्रॉल करता है। ध्यान दें कि, किसी कारणवश, यह हमेशा उस टूडू तक स्क्रॉल करता है जो अभी हाल ही में जोड़ा गया था:
import { useState, useRef } from 'react'; export default function TodoList() { const listRef = useRef(null); const [text, setText] = useState(''); const [todos, setTodos] = useState( initialTodos ); function handleAdd() { const newTodo = { id: nextId++, text: text }; setText(''); setTodos([ ...todos, newTodo]); listRef.current.lastChild.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } return ( <> <button onClick={handleAdd}> Add </button> <input value={text} onChange={e => setText(e.target.value)} /> <ul ref={listRef}> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); } let nextId = 0; let initialTodos = []; for (let i = 0; i < 20; i++) { initialTodos.push({ id: nextId++, text: 'Todo #' + (i + 1) }); }
समस्या इन दो लाइनों में है:
setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView();
React में, state अपडेट्स कतारबद्ध होते हैं. सामान्यतः, यही वह चीज़ है जो आप चाहते हैं। हालांकि, यहां यह एक समस्या पैदा करता है क्योंकि setTodos
तुरंत DOM को अपडेट नहीं करता। इसलिए, जब आप लिस्ट को उसके आखिरी एलिमेंट तक स्क्रॉल करते हैं, तब तक टूडू अभी तक जोड़ा नहीं गया होता है। यही कारण है कि स्क्रॉल हमेशा एक आइटम “पीछे” होता है।
इस समस्या को ठीक करने के लिए, आप React को DOM को सिंक्रोनसली अपडेट करने के लिए मजबूर कर सकते हैं (“flush” कर सकते हैं)। ऐसा करने के लिए, react-dom
से flushSync
को इम्पोर्ट करें और state अपडेट को flushSync
कॉल में लपेटें:
flushSync(() => {
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();
यह React को निर्देश देगा कि वह flushSync
में लिपटे कोड के निष्पादन के तुरंत बाद DOM को सिंक्रोनसली अपडेट करे। इसके परिणामस्वरूप, आखिरी टूडू DOM में पहले ही मौजूद होगा जब आप उसे स्क्रॉल करने की कोशिश करेंगे:
import { useState, useRef } from 'react'; import { flushSync } from 'react-dom'; export default function TodoList() { const listRef = useRef(null); const [text, setText] = useState(''); const [todos, setTodos] = useState( initialTodos ); function handleAdd() { const newTodo = { id: nextId++, text: text }; flushSync(() => { setText(''); setTodos([ ...todos, newTodo]); }); listRef.current.lastChild.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } return ( <> <button onClick={handleAdd}> Add </button> <input value={text} onChange={e => setText(e.target.value)} /> <ul ref={listRef}> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); } let nextId = 0; let initialTodos = []; for (let i = 0; i < 20; i++) { initialTodos.push({ id: nextId++, text: 'Todo #' + (i + 1) }); }
DOM मैनिपुलेशन के लिए सर्वोत्तम प्रथाएँ
Refs एक “escape hatch” हैं। आपको इन्हें केवल तब उपयोग करना चाहिए जब आपको “React से बाहर कदम रखना हो”। इसके सामान्य उदाहरणों में फोकस, स्क्रॉल पोजीशन को मैनेज करना, या उन ब्राउज़र APIs को कॉल करना शामिल है जिन्हें React एक्सपोज़ नहीं करता।
अगर आप नष्ट न करने वाली क्रियाएँ जैसे कि फोकस करना और स्क्रॉल करना करते हैं, तो आपको कोई समस्या नहीं होनी चाहिए। हालांकि, अगर आप DOM को मैन्युअली संशोधित करने की कोशिश करते हैं, तो आप React द्वारा किए जा रहे परिवर्तनों से टकरा सकते हैं।
इस समस्या को स्पष्ट करने के लिए, इस उदाहरण में एक स्वागत संदेश और दो बटन हैं। पहला बटन अपनी उपस्थिति को conditional rendering और state का उपयोग करके टॉगल करता है, जैसा कि आप सामान्य रूप से React में करते हैं। दूसरा बटन remove()
DOM API का उपयोग करके इसे React के नियंत्रण से बाहर मजबूरी से हटा देता है।
“Toggle with setState” को कुछ बार दबाने का प्रयास करें। संदेश को गायब और फिर से दिखाई देना चाहिए। फिर “Remove from the DOM” दबाएँ। यह इसे मजबूरी से हटा देगा। अंत में, “Toggle with setState” दबाएँ:
import { useState, useRef } from 'react'; export default function Counter() { const [show, setShow] = useState(true); const ref = useRef(null); return ( <div> <button onClick={() => { setShow(!show); }}> Toggle with setState </button> <button onClick={() => { ref.current.remove(); }}> Remove from the DOM </button> {show && <p ref={ref}>Hello world</p>} </div> ); }
एक बार जब आपने मैन्युअली DOM तत्व को हटा दिया, तो setState
का उपयोग करके उसे फिर से दिखाने की कोशिश करने से क्रैश हो जाएगा। इसका कारण यह है कि आपने DOM को बदल दिया है, और React को यह नहीं पता होता कि इसे सही तरीके से कैसे प्रबंधित किया जाए।
React द्वारा प्रबंधित DOM नोड्स को बदलने से बचें। ऐसे तत्वों से बच्चों को संशोधित करना, जोड़ना, या हटाना जो React द्वारा प्रबंधित हैं, असंगत दृश्य परिणामों या ऊपर दिए गए जैसे क्रैश का कारण बन सकता है।
हालाँकि, इसका मतलब यह नहीं है कि आप इसे बिलकुल भी नहीं कर सकते। यह सतर्कता की आवश्यकता होती है। आप उन DOM भागों को सुरक्षित रूप से संशोधित कर सकते हैं जिन्हें React को अपडेट करने का कोई कारण नहीं होता। उदाहरण के लिए, यदि कोई <div>
JSX में हमेशा खाली है, तो React को इसके बच्चों की सूची को छेड़ने का कोई कारण नहीं होगा। इसलिए, वहां तत्वों को मैन्युअली जोड़ना या हटाना सुरक्षित है।
Recap
- रिफ़्स एक सामान्य अवधारणा हैं, लेकिन सामान्यत: आप इन्हें DOM तत्वों को रखने के लिए उपयोग करेंगे।
- आप React को
myRef.current
में एक DOM नोड डालने के लिए<div ref={myRef}>
का उपयोग करके निर्देशित करते हैं। - सामान्यत: आप रिफ़्स का उपयोग गैर-हानिकारक क्रियाओं के लिए करेंगे जैसे कि फोकस करना, स्क्रॉल करना, या DOM तत्वों का मापना।
- एक कौम्पोनॅन्ट डिफ़ॉल्ट रूप से अपने DOM नोड्स को एक्सपोज़ नहीं करता। आप
forwardRef
का उपयोग करके और दूसरेref
तर्क को एक विशिष्ट नोड तक भेजकर DOM नोड को एक्सपोज़ करने का विकल्प चुन सकते हैं। - React द्वारा प्रबंधित DOM नोड्स को बदलने से बचें।
- यदि आप React द्वारा प्रबंधित DOM नोड्स को बदलते हैं, तो केवल उन हिस्सों को बदलें जिन्हें React अपडेट करने का कोई कारण नहीं है।
Challenge 1 of 4: वीडियो को प्ले और पॉज करें
इस उदाहरण में, बटन एक राज्य वेरिएबल को टॉगल करता है ताकि यह प्ले और पॉज्ड स्थिति के बीच स्विच कर सके। हालांकि, वीडियो को वास्तव में प्ले या पॉज करने के लिए, केवल राज्य को टॉगल करना पर्याप्त नहीं है। आपको <video>
के DOM तत्व पर play()
और pause()
को भी कॉल करना होगा। इसके लिए इसे एक ref जोड़ें, और बटन को काम करने योग्य बनाएं।
import { useState, useRef } from 'react'; export default function VideoPlayer() { const [isPlaying, setIsPlaying] = useState(false); function handleClick() { const nextIsPlaying = !isPlaying; setIsPlaying(nextIsPlaying); } return ( <> <button onClick={handleClick}> {isPlaying ? 'Pause' : 'Play'} </button> <video width="250"> <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" /> </video> </> ) }
एक अतिरिक्त चुनौती के लिए, “Play” बटन को वीडियो के प्ले होने की स्थिति के साथ सिंक में रखें, भले ही उपयोगकर्ता वीडियो पर राइट-क्लिक करके और ब्राउज़र के बिल्ट-इन मीडिया कंट्रोल का उपयोग करके वीडियो को प्ले करें। ऐसा करने के लिए, आप वीडियो पर onPlay
और onPause
इवेंट्स को सुनना चाहेंगे।