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>
    </>
  );
}

इसका उपयोग करने के लिए:

  1. useRef हुक के साथ inputRef डिक्लेअर करें।
  2. इसे <input ref={inputRef}> के रूप में पास करें। यह React को यह बताता है कि इस <input> के DOM नोड को inputRef.current में डालें।
  3. handleClick फंक्शन में, इनपुट DOM नोड को inputRef.current से पढ़ें और focus() को inputRef.current.focus() के साथ कॉल करें।
  4. 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

लिस्ट ऑफ़ रिफ्स को ref कॉलबैक का उपयोग करके प्रबंधित करना

उपरोक्त उदाहरणों में, रिफ्स की संख्या पहले से तय होती है। लेकिन कभी-कभी आपको लिस्ट में प्रत्येक आइटम के लिए एक रिफ की ज़रूरत हो सकती है, और आपको पता नहीं होता कि कितने आइटम होंगे। ऐसा कुछ काम नहीं करेगा:

<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 नोड्स पढ़ने की अनुमति देता है।

Note

जब Strict Mode सक्षम होता है, तो डेवेलपमेंट में रिफ कॉलबैक दो बार चलेंगे।

[Ref] कॉलबैक को फिर से चलाने से बग कैसे ठीक होते हैं, इसके बारे में अधिक जानें। यहां पढ़ें

किसी अन्य कंपोनेंट के DOM नोड्स तक पहुंचना

Pitfall

Refs एक अंतिम उपाय हैं। किसी दूसरे कंपोनेंट के 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

API के एक सबसेट को एक इम्पेरेटिव हैंडल के साथ एक्सपोज करना

ऊपर दिए गए उदाहरण में, 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 मैनिपुलेशन के लिए सर्वोत्तम प्रथाएँ (Best practices for DOM manipulation with refs)

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 इवेंट्स को सुनना चाहेंगे।