Skip to content

React Integration

Because <spartan-login> is a native web component, it requires explicit lifecycle management in React — useEffect for attaching event listeners and key-based remounting for attribute changes.

import '@masonitestudios/spartanauth-widgets';
import { useRef, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
// Extend JSX types so TypeScript recognises the custom element
declare global {
namespace JSX {
interface IntrinsicElements {
'spartan-login': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
domain?: string;
sector?: string;
'start-mode'?: string;
locale?: string;
redirect?: string;
styles?: string;
};
}
}
}
export function LoginPage() {
const navigate = useNavigate();
const widgetRef = useRef<HTMLElement>(null);
const [locale] = useState(() => navigator.language.slice(0, 2) || 'en');
const [widgetKey, setWidgetKey] = useState(0);
useEffect(() => {
const el = widgetRef.current;
if (!el) return;
function handleLogin() {
navigate('/app');
}
el.addEventListener('spartan-login', handleLogin);
return () => {
el.removeEventListener('spartan-login', handleLogin);
};
// Re-run this effect when widgetKey changes (remount scenario)
}, [widgetKey, navigate]);
return (
<spartan-login
key={widgetKey}
ref={widgetRef}
domain={import.meta.env.VITE_SPARTANAUTH_DOMAIN || 'https://api.spartanauth.com'}
sector={import.meta.env.VITE_SPARTANAUTH_SECTOR}
start-mode="password"
locale={locale}
redirect="">
</spartan-login>
);
}

The web component element doesn’t exist before the component mounts. useEffect with widgetRef.current runs after the DOM is ready.

The useEffect return function removes the event listener when the component unmounts (or when the effect re-runs). Without it, listeners accumulate across re-renders.

return () => {
el.removeEventListener('spartan-login', handleLogin);
};

3. Use key to force remount on attribute changes

Section titled “3. Use key to force remount on attribute changes”

React, like Vue, reuses DOM nodes. If you need the widget to reinitialize (e.g., when locale changes), increment the key prop to force React to destroy and recreate the element:

const [widgetKey, setWidgetKey] = useState(0);
// Force remount when locale changes
function changeLocale(newLocale: string) {
setLocale(newLocale);
setWidgetKey(k => k + 1);
}

React doesn’t know about custom elements by default. Extend JSX.IntrinsicElements (shown in the example above) to get proper TypeScript support.

MistakeFix
Using document.getElementById instead of useRefRefs are the idiomatic React way to access DOM elements
Missing useEffect cleanupAlways return a cleanup function that calls removeEventListener
Referencing stale closures in event handlersInclude all dependencies in the useEffect dependency array
Not providing TypeScript type declarationsExtend JSX.IntrinsicElements with the custom element’s attributes