0

I have a VideoPlayer component with crop functionality that's not working well after migration to functional over class component.

The VideoPlayer has a isVideoPlaying state (useState). It also contains a function toggleOrChangeIsVideoPlaying:

const togglePlayPauseVideo = (toPlay) => {
  if (toPlay !== undefined) {
    if (toPlay) {
      playVideo()
    } else {
      pauseVideo()
    }
  } else {
    if (!isVideoPlaying) {
      playVideo()
    } else {
      pauseVideo()
    }
  }
}

It renders:

<div>
  <Crop onPlayPauseVideo={togglePlayPauseVideo} ...restofTheProps>
    <Video ...someProps />
  </Crop>
</div>

Using useEffect & console.log() I verified that 'togglePlayPauseVideo' function is changing (and causing a bug), probably because VideoPlayer is re-rendered.

I've tried wrapping 'togglePlayPauseVideo' with useCallback. The problem is that it must have 'isVideoPlaying' state as a dependency (otherwise there's another bug), but when it does, it changes again more than it should.

Any ideas how to break this cycle?

BTW 1: 'isVideoPlaying' state is needed to keep track of the actual element state, that changes in playVideo() and pauseVideo() via ref.

BTW 2: VideoPlayer worked ok when it was a class component.

1
  • This seems to be a design issue. Why is togglePlayPauseVideo updating an issue? useCallback, like useMemo is a performance optimization and NOT a semantic guarantee. Commented Nov 7, 2022 at 1:40

2 Answers 2

1

Found the answer myself, posting to help others that will see this.

Of course there was some bad code there, but I'm working mainly on migrating class components to functional components, so I didn't want to do too much refactoring. But this time it was needed. The toggle function is actually 3 functions. toggle, play & pause. I this specific design, it caused a dependency circle. All I did was to break this function into 3 separate ones and it all worked. Maybe if the "code smell" is so obvious we should try to always refactor and don't leave it for later.

Thanks for the help :)

Sign up to request clarification or add additional context in comments.

Comments

0

well, you're on the right track. you're trying to use hooks and it seems like you're wrestling with how to use them well.

If toPlay is a reference to the video, isPlaying is a flag that indicates whether or not the video is playing, and togglePlayPauseVideo is watching for state changes of toPlay and isVideoPlaying, then I think that you should repurpose togglePlayPauseVideo and take advantage of the hook useEffect. You could do something like...

const [videoState, setVideoState] = useState({
    isPlaying: false,
    toPlay: null
})

useEffect(() => {
    const { isPlaying, toPlay } = videoState
    if(isPlaying && toPlay) playVideo()
    else pauseVideo()
}, [videoState.isPlaying, videoState.toPlay])

const togglePlayPauseVideo = () => setVideoState(state => ({
    ...state,
    isPlaying: !state.isPlaying
}))

note: My preference would be to remove toPlay from useEffect and disable the button click if the video isn't there. Why run useEffect simply because a video is available? Also, playVideo and pauseVideo don't modify state anymore, instead the user does it directly.

1 Comment

Actually toPlay is just a boolean. Ended up finding the answer, but thanks for your help :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.