@atlaskit/renderer
Version:
Renderer component
98 lines (93 loc) • 5.31 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import React, { useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { MediaClientContext, getMediaClient } from '@atlaskit/media-client-react';
import { useProviderFactory, useProviderLayout } from '@atlaskit/editor-common/provider-factory';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
export var EditorMediaClientProvider = function EditorMediaClientProvider(_ref) {
var children = _ref.children,
ssr = _ref.ssr;
var _useState = useState(function () {
return expValEquals('platform_editor_media_reliability_enhancements', 'isEnabled', true) ? ssr === null || ssr === void 0 ? void 0 : ssr.config : undefined;
}),
_useState2 = _slicedToArray(_useState, 2),
mediaClientConfig = _useState2[0],
setMediaClientConfig = _useState2[1];
var providerFactory = useProviderFactory();
var mediaProvider = useProviderLayout('mediaProvider');
/**
* Whether this renderer has its own media provider and should never inherit
* the mediaClient from a parent renderer's context.
*
* We use providerFactory.hasProvider() rather and checking the mediaProvider
* state value, because useProviderLayout subscribes via useLayoutEffect —
* meaning mediaProvider state is always undefined on the first render, even
* if the ProviderFactory already has a provider registered. This would cause
* shouldSkipContext to be false on the first render, incorrectly allowing the
* inner renderer to inherit the outer renderer's MediaClientContext (which
* carries the wrong media token for this page).
*
* hasProvider() is synchronous and correct from render 1, closing that window.
*/
var shouldSkipContext = expValEquals('platform_editor_media_reliability_enhancements', 'isEnabled', true) ? Boolean((ssr === null || ssr === void 0 ? void 0 : ssr.config) || providerFactory.hasProvider('mediaProvider') || mediaProvider) : Boolean((ssr === null || ssr === void 0 ? void 0 : ssr.config) || mediaProvider);
var contextMediaClient = useContext(MediaClientContext);
// MediaClientProvider currently requires a mediaClientConfig
// And inserting the MediaClientProvider will cause a re-render
// We should use MediaClientProvider once it no longer requires a config
var mediaClient = useMemo(function () {
return mediaClientConfig ? getMediaClient(mediaClientConfig) : undefined;
}, [mediaClientConfig]);
// Consumers can override the mediaClient for renderer,
// by not providing both SSR config and mediaProvider,
// and provide a top level mediaClient context
// This is useful for testing and creating examples.
// When the experiment is enabled, use useEffect instead of useLayoutEffect because:
// - For the ssr.config branch: useState is already initialised with ssr.config, so this
// effect is a no-op on first render — the "before paint" guarantee is irrelevant.
// - For the mediaProvider branch: the actual work happens inside a Promise callback which
// resolves asynchronously, so it can never run before paint regardless of which hook
// schedules it — useLayoutEffect's guarantee is equally irrelevant here.
// The legacy path keeps useLayoutEffect to preserve existing behaviour when the experiment is off.
//
// The two hooks below are mutually exclusive — only one runs per render — so there is no
// actual chaining of state updates at runtime. The lint rule cannot statically prove this.
useEffect(function () {
if (!expValEquals('platform_editor_media_reliability_enhancements', 'isEnabled', true)) {
return;
}
if (ssr !== null && ssr !== void 0 && ssr.config) {
// eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates
setMediaClientConfig(ssr.config);
} else if (mediaProvider) {
var cancelled = false;
// Cancellation flag prevents setMediaClientConfig from being called after
// unmount or when mediaProvider changes mid-flight (stale promise fix).
// No .catch() is needed — the media provider is not expected to reject,
// and a catch handler would be a no-op anyway.
mediaProvider.then(function (provider) {
if (!cancelled) {
setMediaClientConfig(provider.viewMediaClientConfig);
}
});
return function () {
cancelled = true;
};
}
}, [mediaProvider, ssr === null || ssr === void 0 ? void 0 : ssr.config]);
// Legacy path (experiment off): keep useLayoutEffect to preserve existing behaviour.
// remove this when clean up platform_editor_media_reliability_enhancements
useLayoutEffect(function () {
if (expValEquals('platform_editor_media_reliability_enhancements', 'isEnabled', true)) {
return;
}
if (ssr !== null && ssr !== void 0 && ssr.config) {
setMediaClientConfig(ssr.config);
} else if (mediaProvider) {
mediaProvider.then(function (provider) {
setMediaClientConfig(provider.viewMediaClientConfig);
});
}
}, [mediaProvider, ssr === null || ssr === void 0 ? void 0 : ssr.config]);
return /*#__PURE__*/React.createElement(MediaClientContext.Provider, {
value: shouldSkipContext ? mediaClient : contextMediaClient
}, children);
};