skip to Main Content

I’m using this wavesurfer.js package with React for display wave form of audio. I want to display the current elapsed time using this getCurrentTime() method in their docs and answer here on github.

I got the elapsed time in seconds and update my React state to display it. However, it updates quite frequently, more than 10 times per second which trigger my component to re-render multiple times. I wonder if there is a way to make it only trigger state changes on each seconds pass?

export function Component(){
    const [currentTime, setCurrentTime] = useState(0)
    const wavesurferRef = useRef<any>(null)


    useEffect(()=>{
        if (wavesurferRef.current){
            wavesurferRef.current.on('audioprocess', function () {

                //ISSUE: THIS MAKE COMPONENT RE-RENDERING TOO MANY TIME
                setCurrentTime(wavesurferRef.current.getCurrentTime())
            })
        }
    },[])
    

    return(
        <div ref={wavesurferRef}>
        </div>
    )
}

3

Answers


  1. Chosen as BEST ANSWER

    Since in my application, I only need the value in seconds, so I use this simple approach to Math.floor the value and setState(), cause setState only change the state if the value is different, therefore, the component will just be re-rendered every seconds.

    useEffect(()=>{
       if (wavesurferRef.current){
           wavesurferRef.current.on('audioprocess', function () {
               setCurrentTime(Math.floor(wavesurferRef.current.getCurrentTime()))
           })
       }
    },[])
    

  2. The general answer is you need to NOT call setCurrentTime on every event.

    More practically you can either use some custom logic to define which events to skip. Or you can use generic helper functions to do so. If you want your function to not be called more often than every N ms it is call throttle. You can use lodash or other utility libraries for that

    https://lodash.com/docs/4.17.15#throttle

    Live example https://laracasts.com/series/build-modern-laravel-apps-using-inertia-js/episodes/22

    useEffect(()=>{
            if (wavesurferRef.current){
                wavesurferRef.current.on('audioprocess', throttle(function () {
    
                    //ISSUE: THIS MAKE COMPONENT RE-RENDERING TOO MANY TIME
                    setCurrentTime(wavesurferRef.current.getCurrentTime())
                })
            }, 1000); // fire only once a second
        },[])
    
    Login or Signup to reply.
  3. I might suggest you useRef with a DOM node and something like requestAnimationFrame to update the text. Here’s a minimal, verifiable example. Run the program below and notice a render only happens when you click the button and update the state. The timestamp is updated using animation instead of state transition –

    const mockWavesurfer = { offset: Date.now(), getCurrentTime() { return Date.now() - this.offset } }
    
    function useAnimationFrame(callback) {
      React.useEffect(
        () => {
          let frame
          function onFrame(frameId) {
            callback(frameId)
            frame = requestAnimationFrame(onFrame)
          }
          frame = requestAnimationFrame(onFrame)
          return () => { window.cancelAnimationFrame(frame) }
        },
        [callback]
      )
    }
    
    function App() {
      const [count, setCount] = React.useState(0)
      const wavesurferRef = React.useRef(mockWavesurfer)
      const dateRef = React.useRef()
      React.useEffect(() => console.log("render", count), [count])
      useAnimationFrame(() => {
        dateRef.current.textContent = wavesurferRef.current.getCurrentTime()
      })
      return <div>
        <button onClick={e => setCount(count + 1)} children={count} />
        <time ref={dateRef}>0</time>
      </div>
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    body::before { content: "render only triggers when state changes" }
    button::before { content: "click:" }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search