useEffectEvent, and survive StrictMode’s double mount. Pass your widgetId and an assetId such as 1.
load-centaur.js
let loading;
export function loadCentaur() {
if (window.CentaurWidgets?.v1) return Promise.resolve(window.CentaurWidgets.v1);
loading ??= new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://widgets.centaur.io/v1/sdk.js';
script.crossOrigin = 'anonymous';
script.onload = () => {
if (window.CentaurWidgets?.v1) resolve(window.CentaurWidgets.v1);
else failed();
};
script.onerror = failed;
function failed() {
loading = undefined;
script.remove();
reject(new Error('The Centaur SDK could not load.'));
}
document.head.append(script);
});
return loading;
}
- Feed
- Positioning
AssetFeed.jsx
'use client';
import { useEffect, useEffectEvent, useRef, useState } from 'react';
import { loadCentaur } from './load-centaur.js';
export function AssetFeed({ assetId, widgetId }) {
const container = useRef(null);
const widget = useRef(null);
const [error, setError] = useState(null);
// Reads the current assetId without remounting when it changes.
const mount = useEffectEvent((sdk) => {
widget.current = sdk.mount(container.current, {
kind: 'feed',
mode: 'live',
widgetId,
selection: { assetIds: [assetId] },
});
});
// Mount once per widget ID, and clean up on unmount.
useEffect(() => {
setError(null);
let active = true;
loadCentaur()
.then((sdk) => {
if (active) mount(sdk);
})
.catch((error) => {
if (active) setError(error);
});
return () => {
active = false;
widget.current?.destroy();
widget.current = null;
};
}, [widgetId]);
// Switch assets in place.
useEffect(() => {
widget.current?.update({ selection: { assetIds: [assetId] } });
}, [assetId]);
return (
<>
{error && <p role="alert">{error.message}</p>}
<div ref={container} />
</>
);
}
AssetPositioning.jsx
'use client';
import { useEffect, useEffectEvent, useRef, useState } from 'react';
import { loadCentaur } from './load-centaur.js';
export function AssetPositioning({ assetId, widgetId }) {
const container = useRef(null);
const widget = useRef(null);
const [error, setError] = useState(null);
// Reads the current assetId without remounting when it changes.
const mount = useEffectEvent((sdk) => {
widget.current = sdk.mount(container.current, {
kind: 'positioning',
mode: 'live',
widgetId,
selection: { assetId },
});
});
// Mount once per widget ID, and clean up on unmount.
useEffect(() => {
setError(null);
let active = true;
loadCentaur()
.then((sdk) => {
if (active) mount(sdk);
})
.catch((error) => {
if (active) setError(error);
});
return () => {
active = false;
widget.current?.destroy();
widget.current = null;
};
}, [widgetId]);
// Switch assets in place. Pass a new viewActivityUrl here too if you set one.
useEffect(() => {
widget.current?.update({ selection: { assetId } });
}, [assetId]);
return (
<>
{error && <p role="alert">{error.message}</p>}
<div ref={container} />
</>
);
}