Giả sử ta có component sau:

function VideoPlayer({ src, isPlaying }) {
	return <video src={src} />
}

Component này sử dụng thẻ <video> có sẵn trong trình duyệt và ta muốn truyền vào props isPlaying từ parent component để kiểm soát khi nào thì video được phép phát:

import { useState } from 'react'
 
function App() {
    const [isPlaying, setIsPlaying] = useState(false)
    return (
        <>
            <button onClick={() => setIsPlaying(!isPlaying)}>
                {isPlaying ? 'Pause' : 'Play'}
            </button>
            <VideoPlayer
                isPlaying={isPlaying}
                src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
            />
        </>
    )
}

Tuy nhiên, thẻ <video> không có thuộc tính isPlaying nên ta chỉ có thể gọi phương thức play() hoặc pause() thông qua DOM node của nó:

function VideoPlayer({ src, isPlaying }) {
	const ref = useRef(null)
	
	if (isPlaying) {
		ref.current.play()  // Calling these while rendering isn't allowed.
	} else {
		ref.current.pause() // Also, this crashes.
	}
	
	return <video ref={ref} src={src} loop playsInline />
}

Trong đoạn code trên, DOM node của thẻ <video> được tham chiếu bởi một reference object bằng cách sử dụng hook useRef.

Tuy nhên, đoạn code ở trên không thể hoạt động. Lý do là vì khi chúng ta thực hiện truy cập đến thẻ <video> trong quá trình render, nó vẫn chưa được tạo ra nên ta không thể gọi phương thức play() hay pause().

Giải pháp cho vấn đề này là sử dụng useEffect để gọi hai phương thức cần gọi sau khi DOM node đã được tạo ra như sau:

function VideoPlayer({ src, isPlaying }) {
    const ref = useRef(null)
 
    useEffect(() => {
        if (isPlaying) {
            ref.current.play()
        } else {
            ref.current.pause()
        }
    })
 
    return <video ref={ref} src={src} loop playsInline />
}
list
from [[Wrapping the DOM Update in useEffect]]
sort file.ctime asc

Resources