Skip to content

fix(react): memoize Provider context value#1541

Closed
R-Delfino95 wants to merge 1 commit into
videojs:mainfrom
mav2287:fix/1296-provider-value-attach-ref-churn
Closed

fix(react): memoize Provider context value#1541
R-Delfino95 wants to merge 1 commit into
videojs:mainfrom
mav2287:fix/1296-provider-value-attach-ref-churn

Conversation

@R-Delfino95

@R-Delfino95 R-Delfino95 commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Related to #1296

What changed

Diff: 2 files changed, 58 insertions(+), 7 deletions(-).

  • packages/react/src/player/create-player.tsx — Provider context value wrapped in useMemo keyed on [store, media, container, popupGroup]. setMedia and setContainer are omitted (identity-stable useState setters). popupGroup (added on main since the original PR was opened) is included in both the value and the dep array.

  • packages/react/src/player/tests/create-player.test.tsx — one regression test added: mounts a usePlayerContext() consumer, forces parent re-renders three times, asserts context value identity is preserved (toBe).

Test results in packages/react:

Step Result
Baseline (untouched main) 17 files, 237 passing
With fix applied 17 files, 238 passing (+1 new)
Source reverted, new test kept 1 new test fails (fail-before/pass-after contract)
Source re-applied 238/238 passing
pnpm typecheck (root) Clean
pnpm lint on the 2 changed files Clean

The attachMediaElement WeakMap fix has been fully discarded. Worth noting that #1376 has since landed on main, which introduced useAttachMedia (a useCallback-based hook keyed on [media]), so the ref-identity stability that fix #2 was guarding against is now handled at the hook level — fix #2 isn't just unwanted, it's obsolete against current main.


Note

Low Risk
Low risk: scoped to React context value memoization and adds a regression test; behavior should be unchanged except fewer unnecessary re-renders and attach/detach churn.

Overview
Fixes createPlayer().Provider to memoize the PlayerContext value via useMemo, so consumers don’t re-render (and trigger media detach/reattach) on unrelated parent renders (regression #1296).

Adds a test asserting the context value identity remains stable across repeated parent re-renders.

Reviewed by Cursor Bugbot for commit 9e29157. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented May 13, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Mux Team on Vercel.

A member of the Team first needs to authorize it.

@netlify

netlify Bot commented May 13, 2026

Copy link
Copy Markdown

‼️ Deploy request for vjs10-site rejected.

Name Link
🔨 Latest commit 9e29157

@R-Delfino95

Copy link
Copy Markdown
Collaborator Author

Hi @mav2287! I created the PR with your changes, as external PRs are currently restricted.
Regarding the fixes, only the useMemo adjustment (fix # 1) is necessary; the rest can be discarded.

Would you mind: 1. Updating the branch with upstream main, 2. Applying only that change and discarding the rest, 3. Updating the PR description?

Let me know if that works! Otherwise, I can pick up your contribution from where you left off.
Best!

The `Provider` returned by `createPlayer()` was building a fresh context
value object on every render. Every `useContext(PlayerContext)` consumer
therefore re-rendered on any parent re-render — even when none of the
value's members changed. Combined with callback-ref usage in media
components (`HlsVideo`, `DashVideo`, etc.), this caused the underlying
media to detach and re-attach on every parent state change. The most
visible symptom: `onEnded` fires, parent state updates, the video
rewinds to t=0 and plays again.

Wrap the value object in `useMemo` keyed on `[store, media, container,
popupGroup]`. `setMedia` and `setContainer` are `useState` setters and
are identity-stable; `store` and `popupGroup` are also initialized once
via `useState(() => ...)` and never change for the lifetime of the
Provider, but they're listed in the dep array to satisfy Biome's
`useExhaustiveDependencies`.

Adds a regression test that asserts context value identity is preserved
across forced parent re-renders.

Signed-off-by: James <james@BlainWebDesign.com>
@mav2287 mav2287 force-pushed the fix/1296-provider-value-attach-ref-churn branch from 22df048 to 9e29157 Compare May 13, 2026 22:54
@mav2287

mav2287 commented May 13, 2026

Copy link
Copy Markdown

@R-Delfino95 done — branch rebased and reduced to just the useMemo change, per your request. Force-pushed to mav2287:fix/1296-provider-value-attach-ref-churn. Summary of what's now on the branch:

Diff: 2 files changed, 58 insertions(+), 7 deletions(-).

  • packages/react/src/player/create-player.tsx — Provider context value wrapped in useMemo keyed on [store, media, container, popupGroup]. setMedia and setContainer are omitted (identity-stable useState setters). popupGroup (added on main since the original PR was opened) is included in both the value and the dep array.

  • packages/react/src/player/tests/create-player.test.tsx — one regression test added: mounts a usePlayerContext() consumer, forces parent re-renders three times, asserts context value identity is preserved (toBe).

Test results in packages/react:

Step Result
Baseline (untouched main) 17 files, 237 passing
With fix applied 17 files, 238 passing (+1 new)
Source reverted, new test kept 1 new test fails (fail-before/pass-after contract)
Source re-applied 238/238 passing
pnpm typecheck (root) Clean
pnpm lint on the 2 changed files Clean

The attachMediaElement WeakMap fix has been fully discarded. Worth noting that #1376 has since landed on main, which introduced useAttachMedia (a useCallback-based hook keyed on [media]), so the ref-identity stability that fix #2 was guarding against is now handled at the hook level — fix #2 isn't just unwanted, it's obsolete against current main.

Commit is DCO-signed.

I don't have permission to edit the PR title/body myself (only you can as the PR author). When you get a chance, would you mind updating them to reflect the single-change scope? Happy to draft a new description if helpful — let me know.

Thanks again for picking this up on our behalf!

@R-Delfino95 R-Delfino95 marked this pull request as ready for review May 14, 2026 14:39
@R-Delfino95 R-Delfino95 changed the title fix(react): memoize Provider context value and cache attachMediaElement refs fix(react): memoize Provider context value May 14, 2026

@mihar-22 mihar-22 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Just some comment clean up would be great :)

Comment on lines +116 to +120
// Without memoization, the Provider rebuilds its context value on every
// render. Every `useContext(PlayerContext)` consumer then re-renders on
// any parent re-render, which (combined with callback-ref usage in
// media components) detaches and re-attaches the underlying media on
// every parent state change. At end-of-stream this rewinds the video.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete this, safe to assume common React knowledge and inferred by the test title.

Comment on lines +85 to +93
// Memoize the context value so its identity only changes when one of its
// members actually changes. Without this, every render of a component
// above the Provider rebuilds the value object, which forces every
// `useContext(PlayerContext)` consumer to re-render even when nothing
// it depends on changed. `setMedia` and `setContainer` are `useState`
// setters and are identity-stable; `store` and `popupGroup` are also
// initialized once via `useState(() => ...)` and never change for the
// lifetime of the Provider. They're listed in the dep array to satisfy
// `useExhaustiveDependencies`. See #1296.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete this, usage of useMemo already makes it clear and the rest should be common React knowledge.

@mihar-22

mihar-22 commented Jul 1, 2026

Copy link
Copy Markdown
Member

Created replacement PR #1787 from a videojs/v10 branch because this fork has maintainer edits disabled and rejected the comment-cleanup push. #1787 preserves the original fix, rebases onto current main, and includes the requested comment cleanup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants