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

# React Integration

> How to integrate Entri with React applications

Learn how to integrate Entri into your React application with proper event handling and state management.

## Prerequisites

* Your `applicationId` from the Entri dashboard
* A valid JWT token fetched from your server
* Basic understanding of React hooks

## Installation

<Tabs>
  <Tab title="NPM Package">
    ```bash theme={null}
    npm install entrijs
    ```

    Import the functions you need:

    ```jsx theme={null}
    import { showEntri, close, checkDomain } from 'entrijs';
    ```

    <Note>
      NPM functions are **async** - the package automatically loads the Entri SDK when first called.
    </Note>

    ### Quick Start (NPM)

    ```jsx theme={null}
    import { useEffect } from 'react';
    import { showEntri } from 'entrijs';

    function DomainConnect({ applicationId, token }) {
      useEffect(() => {
        const handleClose = (event) => {
          console.log('Entri closed:', event.detail);
        };

        window.addEventListener('onEntriClose', handleClose);
        return () => window.removeEventListener('onEntriClose', handleClose);
      }, []);

      const handleConnect = async () => {
        await showEntri({
          applicationId,
          token,
          dnsRecords: [
            {
              type: 'CNAME',
              host: 'www',
              value: 'your-service.com',
              ttl: 300,
            },
          ],
        });
      };

      return <button onClick={handleConnect}>Connect Domain</button>;
    }
    ```

    ### Custom Hook (NPM)

    ```jsx theme={null}
    // hooks/useEntri.js
    import { useState, useEffect, useCallback } from 'react';
    import { showEntri, close } from 'entrijs';

    export function useEntri() {
      const [isOpen, setIsOpen] = useState(false);
      const [result, setResult] = useState(null);
      const [error, setError] = useState(null);

      useEffect(() => {
        const handleSuccess = (event) => {
          setResult(event.detail);
        };

        const handleClose = (event) => {
          setIsOpen(false);

          if (event.detail?.error) {
            setError(event.detail.error);
          }
        };

        window.addEventListener('onSuccess', handleSuccess);
        window.addEventListener('onEntriClose', handleClose);

        return () => {
          window.removeEventListener('onSuccess', handleSuccess);
          window.removeEventListener('onEntriClose', handleClose);
        };
      }, []);

      const openEntri = useCallback(async (config) => {
        setIsOpen(true);
        setError(null);
        setResult(null);

        await showEntri({
          applicationId: config.applicationId,
          token: config.token,
          dnsRecords: config.dnsRecords,
          prefilledDomain: config.prefilledDomain,
          userId: config.userId,
        });
      }, []);

      const closeEntri = useCallback(async () => {
        await close();
      }, []);

      return {
        openEntri,
        closeEntri,
        isOpen,
        result,
        error,
      };
    }
    ```

    ### With TypeScript (NPM)

    The package exports types you can use:

    ```typescript theme={null}
    import { 
      showEntri, 
      close,
      type EntriConfig,
      type EntriCloseEventDetail,
      type EntriSuccessEventDetail,
    } from 'entrijs';
    ```
  </Tab>

  <Tab title="Script Tag">
    Add to your `index.html`:

    ```html theme={null}
    <script src="https://cdn.goentri.com/entri.js"></script>
    ```

    The SDK will be available globally as `window.entri`.

    ### Quick Start (Script Tag)

    ```jsx theme={null}
    import { useEffect } from 'react';

    function DomainConnect({ applicationId, token }) {
      useEffect(() => {
        const handleClose = (event) => {
          console.log('Entri closed:', event.detail);
        };

        window.addEventListener('onEntriClose', handleClose);
        return () => window.removeEventListener('onEntriClose', handleClose);
      }, []);

      const handleConnect = () => {
        window.entri.showEntri({
          applicationId,
          token,
          dnsRecords: [
            {
              type: 'CNAME',
              host: 'www',
              value: 'your-service.com',
              ttl: 300,
            },
          ],
        });
      };

      return <button onClick={handleConnect}>Connect Domain</button>;
    }
    ```

    ### Custom Hook (Script Tag)

    ```jsx theme={null}
    // hooks/useEntri.js
    import { useState, useEffect, useCallback } from 'react';

    export function useEntri() {
      const [isOpen, setIsOpen] = useState(false);
      const [result, setResult] = useState(null);
      const [error, setError] = useState(null);

      useEffect(() => {
        const handleSuccess = (event) => {
          setResult(event.detail);
        };

        const handleClose = (event) => {
          setIsOpen(false);

          if (event.detail?.error) {
            setError(event.detail.error);
          }
        };

        window.addEventListener('onSuccess', handleSuccess);
        window.addEventListener('onEntriClose', handleClose);

        return () => {
          window.removeEventListener('onSuccess', handleSuccess);
          window.removeEventListener('onEntriClose', handleClose);
        };
      }, []);

      const openEntri = useCallback((config) => {
        setIsOpen(true);
        setError(null);
        setResult(null);

        window.entri.showEntri({
          applicationId: config.applicationId,
          token: config.token,
          dnsRecords: config.dnsRecords,
          prefilledDomain: config.prefilledDomain,
          userId: config.userId,
        });
      }, []);

      const closeEntri = useCallback(() => {
        window.entri.close();
      }, []);

      return {
        openEntri,
        closeEntri,
        isOpen,
        result,
        error,
      };
    }
    ```

    ### With TypeScript (Script Tag)

    Define your own types or use the ones from the NPM package:

    ```typescript theme={null}
    interface EntriConfig {
      applicationId: string;
      token: string;
      dnsRecords: DNSRecord[];
      prefilledDomain?: string;
      userId?: string;
    }

    interface DNSRecord {
      type: 'A' | 'AAAA' | 'CNAME' | 'TXT' | 'MX' | 'NS';
      host: string;
      value: string;
      ttl: number;
      priority?: number;
    }

    interface EntriCloseEventDetail {
      domain: string | null;
      success: boolean;
      setupType: 'automatic' | 'manual' | 'sharedLogin' | 'purchase' | null;
      provider: string;
      lastStatus: string;
      error?: {
        code: string;
        title: string;
        details: string;
      };
    }

    interface EntriSuccessEventDetail {
      domain: string;
      success: true;
      setupType: 'automatic' | 'manual';
      provider: string;
      jobId: string;
    }

    // Extend Window type
    declare global {
      interface Window {
        entri: {
          showEntri: (config: EntriConfig) => void;
          close: () => void;
          checkDomain: (domain: string, config: EntriConfig) => Promise<any>;
        };
      }
    }
    ```
  </Tab>
</Tabs>

## Using the Hook

Once you have the hook, use it in your components:

```jsx theme={null}
// components/DomainSetup.jsx
import { useEntri } from '../hooks/useEntri';

export function DomainSetup({ applicationId, token, userId }) {
  const { openEntri, isOpen, result, error } = useEntri();

  const handleConnect = () => {
    openEntri({
      applicationId,
      token,
      userId,
      dnsRecords: [
        {
          type: 'CNAME',
          host: 'www',
          value: 'your-service.com',
          ttl: 300,
        },
      ],
    });
  };

  if (result?.success) {
    return (
      <div className="success">
        Domain {result.domain} connected successfully!
      </div>
    );
  }

  return (
    <div>
      <button onClick={handleConnect} disabled={isOpen}>
        {isOpen ? 'Connecting...' : 'Connect Domain'}
      </button>

      {error && (
        <p className="error">
          Error: {error.title || 'An error occurred'}
        </p>
      )}
    </div>
  );
}
```

## Event Handling

Entri emits events via `window`. Set up listeners in `useEffect` **before** calling `showEntri()`.

<Note>
  Events work the same way for both NPM and Script Tag installations.
</Note>

### onSuccess

Triggered when the user successfully completes domain setup.

```jsx theme={null}
useEffect(() => {
  const handleSuccess = (event) => {
    const { domain, setupType, provider, jobId } = event.detail;

    console.log(`Domain ${domain} configured via ${setupType}`);
    // Store jobId for webhook correlation
  };

  window.addEventListener('onSuccess', handleSuccess);
  return () => window.removeEventListener('onSuccess', handleSuccess);
}, []);
```

### onEntriClose

Triggered when the modal is closed, whether successful or not.

```jsx theme={null}
useEffect(() => {
  const handleClose = (event) => {
    const { domain, success, setupType, lastStatus, error } = event.detail;

    if (success) {
      console.log(`${domain} configured successfully`);
    } else if (error) {
      console.log(`Error: ${error.code} - ${error.title}`);
    } else {
      console.log(`User exited at: ${lastStatus}`);
    }
  };

  window.addEventListener('onEntriClose', handleClose);
  return () => window.removeEventListener('onEntriClose', handleClose);
}, []);
```

### onEntriStepChange

Triggered when the user moves between screens. Useful for analytics.

```jsx theme={null}
useEffect(() => {
  const handleStepChange = (event) => {
    const { step, domain, provider } = event.detail;
    analytics.track('entri_step', { step, domain, provider });
  };

  window.addEventListener('onEntriStepChange', handleStepChange);
  return () => window.removeEventListener('onEntriStepChange', handleStepChange);
}, []);
```

### onEntriManualSetupDocumentationClick

Triggered when the user clicks the manual setup guide link.

```jsx theme={null}
useEffect(() => {
  const handleManualClick = () => {
    openSupportChat();
  };

  window.addEventListener('onEntriManualSetupDocumentationClick', handleManualClick);
  return () => window.removeEventListener('onEntriManualSetupDocumentationClick', handleManualClick);
}, []);
```

## Troubleshooting

### Event listeners not firing

Make sure you're adding listeners **before** calling `showEntri()`. In React, use `useEffect` to set up listeners on mount:

```jsx theme={null}
// ✅ Correct - listeners added on mount via useEffect
useEffect(() => {
  window.addEventListener('onEntriClose', handler);
  return () => window.removeEventListener('onEntriClose', handler);
}, []);

// ❌ Wrong - listener added after showEntri
const handleClick = () => {
  showEntri(config); // or window.entri.showEntri(config)
  window.addEventListener('onEntriClose', handler); // Too late!
};
```

### Stale state in event handlers

React's closures can capture stale state. Use refs or functional updates:

```jsx theme={null}
// ✅ Use functional update to avoid stale state
setResult(prevResult => ({
  ...prevResult,
  ...event.detail
}));

// ✅ Or use a ref for values needed in event handlers
const configRef = useRef(config);
useEffect(() => {
  configRef.current = config;
}, [config]);
```

### Multiple event handlers firing

Ensure you clean up listeners when the component unmounts:

```jsx theme={null}
useEffect(() => {
  const handler = (event) => { /* ... */ };

  window.addEventListener('onEntriClose', handler);

  return () => {
    window.removeEventListener('onEntriClose', handler);
  };
}, []);
```

## Focus Trap Conflicts

When using Entri with React UI libraries that implement their own focus management (Reach UI Dialog, Radix UI), you may experience focus conflicts where tab navigation breaks.

### The Problem

Both Entri and your modal library compete for focus control. Common symptoms:

* Tab key not cycling through elements correctly
* Focus jumping unexpectedly
* Screen reader navigation issues

### Solution 1: Embedded Mode (Recommended)

Mount Entri inside your existing dialog using embedded mode:

```jsx theme={null}
import { Dialog, DialogContent } from '@reach/dialog';
import { useEffect, useState } from 'react';

function DomainSetupDialog() {
  const [isOpen, setIsOpen] = useState(false);

  useEffect(() => {
    if (!isOpen) return;

    window.entri.showEntri({
      applicationId: 'your-app-id',
      token: 'your-jwt-token',
      dnsRecords: [/* your records */],
      whiteLabel: {
        customProperties: {
          general: {
            embeddedMode: {
              selector: '#entri-container',
              containerStyles: {
                boxShadow: 'none',
                borderRadius: '0px',
                width: '100%',
                height: '100%',
              },
            },
          },
        },
      },
    });
  }, [isOpen]);

  return (
    <Dialog isOpen={isOpen} onDismiss={() => setIsOpen(false)}>
      <DialogContent>
        <div id="entri-container" />
      </DialogContent>
    </Dialog>
  );
}
```

### Solution 2: Disable Focus Lock When Entri is Active

Use a state flag to disable your focus trap while Entri is open:

```jsx theme={null}
import FocusLock from 'react-focus-lock';
import { useState, useEffect } from 'react';

function CustomModal() {
  const [entriActive, setEntriActive] = useState(false);

  useEffect(() => {
    const handleClose = () => setEntriActive(false);
    window.addEventListener('onEntriClose', handleClose);
    return () => window.removeEventListener('onEntriClose', handleClose);
  }, []);

  const launchEntri = () => {
    setEntriActive(true);
    window.entri.showEntri({ /* config */ });
  };

  return (
    <FocusLock disabled={entriActive}>
      <button onClick={launchEntri}>Setup Domain</button>
    </FocusLock>
  );
}
```

<Note>
  This issue has been observed with Reach UI Dialog specifically, but may occur with other libraries that implement aggressive focus locking.
</Note>

## Next Steps

* [API Reference](/api-reference#entri-showentri-config) - Full configuration options
* [Webhooks](/webhooks) - Server-side event handling
* [Getting Started](/getting-started) - Dashboard setup and token generation
