React / Next.js
Installation
npm install @wmt-fanid/widget
Initialize in your app root
'use client'
import { useEffect } from 'react'
export default function RootLayout({ children }) {
useEffect(() => {
import('@wmt-fanid/widget').then(({ default: FanIDWidget }) => {
FanIDWidget.init({ baseUrl: 'https://your-org.fanid.co' })
})
}, [])
return <html><body>{children}</body></html>
}
Or for Create React App / Vite:
import FanIDWidget from '@wmt-fanid/widget'
import '@wmt-fanid/widget/dist/widget.css'
FanIDWidget.init({ baseUrl: 'https://your-org.fanid.co' })
Using the Custom Element in JSX
React supports custom elements natively:
function ExclusiveArticle({ content }) {
return (
<article>
<h1>{content.title}</h1>
<wmt-fanid-protected>
<div dangerouslySetInnerHTML={{ __html: content.body }} />
</wmt-fanid-protected>
</article>
)
}
Custom hook: useFanIDAuth
A reactive hook that tracks authentication state and provides the user profile:
import { useState, useEffect, useCallback } from 'react'
function useFanIDAuth() {
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [profile, setProfile] = useState(null)
const refresh = useCallback(async () => {
const auth = await FanIDWidget.isAuthenticated()
const p = await FanIDWidget.getProfile()
setIsAuthenticated(auth)
setProfile(p ?? null)
}, [])
useEffect(() => {
FanIDWidget.on('auth:login', refresh)
FanIDWidget.on('auth:logout', refresh)
FanIDWidget.on('ready', refresh)
return () => {
FanIDWidget.off('auth:login', refresh)
FanIDWidget.off('auth:logout', refresh)
FanIDWidget.off('ready', refresh)
}
}, [refresh])
return { isAuthenticated, profile }
}
Usage:
function UserGreeting() {
const { isAuthenticated, profile } = useFanIDAuth()
if (!isAuthenticated) {
return <p>Please sign in</p>
}
return <p>Welcome, {profile?.firstName}!</p>
}
Custom hook: useFanIDGate
A hook that gates a DOM element via useRef, with proper cleanup and ready event handling:
import { useRef, useEffect } from 'react'
function useFanIDGate(options) {
const ref = useRef(null)
useEffect(() => {
const el = ref.current
if (!el) return
const applyGate = () => FanIDWidget.gate(el, options)
if (FanIDWidget.isInitialized()) {
applyGate()
} else {
FanIDWidget.on('ready', applyGate)
}
return () => {
FanIDWidget.off('ready', applyGate)
FanIDWidget.ungate(el)
}
}, [])
return { ref }
}
Usage:
function PremiumStats() {
const { ref } = useFanIDGate({
placeholder: { title: 'Premium Stats' },
style: 'gradient',
})
return (
<div ref={ref}>
<table>...</table>
</div>
)
}
TypeScript
Add custom element types to avoid JSX warnings:
declare namespace JSX {
interface IntrinsicElements {
'wmt-fanid-protected': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement> & {
'placeholder-title'?: string
'placeholder-message'?: string
'placeholder-button-text'?: string
'style-mode'?: 'blur' | 'gradient' | 'cover'
'teaser-height'?: string
'min-height'?: string
},
HTMLElement
>
}
}
Next.js SSR note
The widget is safe to import in SSR environments. The module defers all DOM access (Custom Element registration,
event listeners, document/window references) until init() is called on the client.
Static imports work in server components and layouts without crashing. Just make sure init() runs client-side only:
'use client'
import FanIDWidget from '@wmt-fanid/widget'
import { useEffect } from 'react'
export default function RootLayout({ children }) {
useEffect(() => { FanIDWidget.init({ baseUrl: 'https://your-org.fanid.co' }) }, [])
return <html><body>{children}</body></html>
}
Custom Elements render as empty during SSR and hydrate on the client. The FOUC prevention style keeps content hidden until hydration completes:
wmt-fanid-protected:not(:defined) {
visibility: hidden;
}