fix(react): memoize Provider context value (#1787)

Co-authored-by: James <james@BlainWebDesign.com>
This commit is contained in:
rahim
2026-07-01 11:57:08 -07:00
committed by GitHub
co-authored by James
parent 561a2f2778
commit 6f6657a198
2 changed files with 42 additions and 6 deletions
+6 -5
View File
@@ -16,7 +16,7 @@ import type { InferStoreState } from '@videojs/store';
import { combine, createStore } from '@videojs/store';
import { useStore } from '@videojs/store/react';
import type { FC, ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useDestroy } from '../utils/use-destroy';
import { Container, PlayerContextProvider, useMedia, usePlayerContext } from './context';
@@ -91,11 +91,12 @@ export function createPlayer(config: CreatePlayerConfig<AnyPlayerFeature[]>): Cr
return store.attach({ media, container });
}, [media, container, store]);
return (
<PlayerContextProvider value={{ store, media, setMedia, container, setContainer, popupGroup }}>
{children}
</PlayerContextProvider>
const value = useMemo(
() => ({ store, media, setMedia, container, setContainer, popupGroup }),
[store, media, container, popupGroup]
);
return <PlayerContextProvider value={value}>{children}</PlayerContextProvider>;
}
if (__DEV__ && config.displayName) {
@@ -2,7 +2,7 @@ import { act, render, renderHook } from '@testing-library/react';
import type { PlayerStore } from '@videojs/core/dom';
import { defineSlice } from '@videojs/store';
import type { ReactNode } from 'react';
import { StrictMode } from 'react';
import { StrictMode, useState } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { usePlayerContext } from '../context';
import { createPlayer } from '../create-player';
@@ -188,6 +188,41 @@ describe('createPlayer', () => {
expect(container.querySelector('[data-testid="child"]')).toBeTruthy();
});
it('provides a stable context value across parent re-renders (fix for #1296)', () => {
const { Provider } = createPlayer({ features: [mockSlice] });
const receivedValues: unknown[] = [];
function ContextConsumer() {
const ctx = usePlayerContext();
receivedValues.push(ctx);
return null;
}
let forceParentRerender!: () => void;
function Parent() {
const [, setTick] = useState(0);
forceParentRerender = () => setTick((t) => t + 1);
return (
<Provider>
<ContextConsumer />
</Provider>
);
}
render(<Parent />);
const valueAfterMount = receivedValues[receivedValues.length - 1];
act(() => forceParentRerender());
act(() => forceParentRerender());
act(() => forceParentRerender());
const valueAfterRerenders = receivedValues[receivedValues.length - 1];
expect(valueAfterRerenders).toBe(valueAfterMount);
});
});
describe('usePlayer', () => {