Home > Net >  listen to deeply nested react state
listen to deeply nested react state

Time:09-02

I am currently working on a music player in React. So far I have a Context Provider with a music element stored with the useState hook.

const [currentSong, setCurrentSong] = useState(null);

useEffect(() => {
    fetchSong();
}, []);

const fetchSong = () => {
    const songAudio = new Audio(`localhost/song/13/audio`)
    songAudio.onloadeddata = () => {
    songAudio.play();
    setCurrentSong(songAudio);
    }
}

After that the currentSong Object looks something like this

<audio preload="auto" src="http://localhost/song/13/audio">
{...}
duration: 239.081
currentTime: 113.053
​{...}
<prototype>: HTMLAudioElementPrototype { … }

Because the song is playing the currentTime gets updated automatically.

My question is if it is possible to trigger a rerender every time currentTime changes so that I can update a span element with that number. The span is in a seperate file and consumes the Context Provider which provides the currentSong object.

const { currentSong, {...} } = useMusicContext();

{...}

return (
<span className='...'>
  {currentSong? currentSong.currentTime: "0:00"}
</span>
)

The problem is that the component does not know that the currentTime value changed and only updates the text if a rerender is triggered by something else.

CodePudding user response:

Add an event listener to the audio element for timeupdate events and use those to update your state (or whatever).

Here's a quick demo implementation. Source included below for easier reference.

// Audio component to handle attaching the listener
import { useEffect, useRef } from "react";

export function Audio({ onTimeUpdate, ...props }) {
  const audioRef = useRef();

  useEffect(() => {
    const { current } = audioRef;
    current?.addEventListener("timeupdate", onTimeUpdate);

    return () => current?.removeEventListener("timeupdate", onTimeUpdate);
  }, [audioRef, onTimeUpdate]);

  return (
    <audio ref={audioRef} {...props} />
  );
}
export default function App() {
  const [time, setTime] = useState();

  const onTimeUpdate = (e) => {
    setTime(e.target.currentTime);
  };

  return (
    <div className="App">
      <Audio onTimeUpdate={onTimeUpdate} controls src="./audio-sample.mp3" />
      <div>{time}</div>
    </div>
  );
}

CodePudding user response:

Tough to exactly say what to do here - would need more info/code, but I do believe that passing down currentTime as a prop would work.

If that is not possible, or you don't want to keep passing down props, you may want to look into the react hook called useContext.

Alternatively, perhaps you could use useEffect to trigger re-renders in the component you want to update. Not exactly sure how you would trigger this re-render/what you would put in the dependency array without more info.

  • Related