Frameworks

React / Next.js

Integrate FanID gated content into React and Next.js applications.

Installation

npm install @wmt-fanid/widget

Initialize in your app root

app/layout.tsx
'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:

main.tsx
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:

types/custom-elements.d.ts
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:

app/layout.tsx
'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;
}
Copyright © 2026