Hooks / React.useState cause flicker issue when used in onProgress
Current Behavior
v2 - Using hooks along with state within onProgress callback creates a re-render loop. Player flickers endlessly.
Expected Behavior
Setting a separate piece of state that the player is not dependent on should not re render ReactPlayer
Steps to Reproduce
- Create a functional component to use ReactPlayer in
- Add a piece of state using hooks pattern ex.
const [played, setPlayed] = React.useState(false); - Create a handler function that updates state base on object passed ex.
const handleProgress = (playedObject) => {
if (!seeking) {
setPlayed(playedObject.played);
}
};
- Use handler inside onProgress method on ReactPlayer ex.
onProgress={handleProgress}
Example provided:
Uncomment setPlayed inside handleProgress to see issue
https://codesandbox.io/s/strange-sun-cw9oq?file=/src/App.js:301-311
Environment
- URL attempting to play: any (example uses https://res.cloudinary.com/filmsupply/video/upload/ac_none,vc_auto/v1606326468/static/Home/home-hero.mp4)
- Browser: Chrome
- Operating system: macOS Big Sur
- Codesanbox example: https://codesandbox.io/s/strange-sun-cw9oq?file=/src/App.js:301-311
Other Information
If we convert this to a class component or revert to v1 the issue is not present. Also state must be set to the passed value on the progress handler (using a static value to set state does not cause flicker)
I have the exact same issue. I've found that this only occurs with nested functions. Given the time between updates on this project, I don't have much fate in this being resolved anytime soon.
Therefore, a workaround is putting the player directly in the return statement in the export default function of a certain page. I'm using Next.js (which has a React-based front-end), and this seems to work.
However, it's still a bug. I'd like to hear from someone about a solution that isn't a workaround like this!
Edit: by putting the player directly in the return statement of a default function, I mean the literal <ReactPlayer/> component.
This has to be due to the lack of memoization. Any state updates will cause the player to re-render. Using refs to control playback wouldn't have had this effect.
In using functional components to toggle the prop passed to the player element, we'll probably want to use memoization to make sure the player only re-renders when significant pieces of state change.
I'm looking into this now, and it may be that there will need to be changes to the react player api itself, or it could be just an implementation detail.
can you also let me know which player you guys are using?
For me, it ended up being cleaner to just have a one return statement with some conditional rendering inside. That also solved the problem of it flickering because it was inside a nested function's return statement. I'm using react-player to play video files stored in a GCP storage bucket currently. I'm using the useState React hook for states like playing, started, progress etc.
Uncomment setPlayed inside handleProgress to see issue https://codesandbox.io/s/strange-sun-cw9oq?file=/src/App.js:301-311
Notice the flickering stops when you change url={['file.mp4']} to url='file.mp4'. ReactPlayer is re-rendering because url={['file.mp4']} passes in a newly instantiated array as the url every time your state updates.
I can take a closer look when I get more time. Spare time is not something I have much of at the moment.
@LongLiveCHIEF we were upgrading to the same version in the example 2.9.0 and using the file player. @wouter-deen thanks for the suggestion we will give that a shot. @cookpete Thanks for looking into it man!
I can take a closer look when I get more time. Spare time is not something I have much of at the moment.
My FT job is working with this quite a bit ATM, and this is one that falls in scope, so I can def help.
It seems like we can update the title of this one to indicate only the file player is affected?
Notice the flickering stops when you change url={['file.mp4']} to url='file.mp4'. ReactPlayer is re-rendering because url={['file.mp4']} passes in a newly instantiated array as the url every time your state updates.
In this case, then we can advise user to use useMemo:
const sourceFiles = ['file.mp4', 'another.mp4']
const files = useMemo(() => {[...sourceFiles]}, [sourceFiles])
return (
<>
<ReactPlayer
url={files}
/>
</>
)
Yep, or just tell ReactPlayer not to re-render or reload if the url is an array and the items have not changed. Maybe.
I might be missing something here, but my url prop was literally just url="fileurl.mp4". It had nothing to do with an array and the player would still immediately flicker upon preloading.
@wouter-deen If you can create something that reproduces the issue, I can take a look.
I rewrote my code to use conditional rendering, hence fixing the issue. I don't have any code sample, but I think it had to do with the fact that I used the useState hook to save playing, volume, pip, timestamp etc. states.
For anyone having this issue, I suggest you try to do what I said in my first comment in this thread. If that doesn't resolve the issue, I suggest you work out what the issue is by simply eliminating certain parts (like hooks, fetching the last timestamp from your database etc.) until the problem doesn't pop up.
@wouter-deen Can you be more about how you solved it?
any other solutions?
@ferrero1987 Do you have a reproducible example of this happening? Does anyone?