> ## Documentation Index
> Fetch the complete documentation index at: https://docs.centaur.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Use it in React

> Components that load the SDK once, follow the asset, and clean up on unmount.

Add the loader once, then use either component. They need React 19.2 or later for `useEffectEvent`, and survive StrictMode's double mount. Pass your `widgetId` and an `assetId` such as `1`.

```js load-centaur.js theme={null}
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**

```jsx AssetFeed.jsx theme={null}
'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} />
    </>
  );
}
```

**Positioning**

```jsx AssetPositioning.jsx theme={null}
'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} />
    </>
  );
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.