Documentation

React SDK

Seamless integration for React and Next.js apps ⚛️

The React SDK provides a seamless integration with hooks, context, and automatic cleanup for React and Next.js applications.

Installation

npm install @twenty1tools/whatif

Basic Setup

Wrap Your App

App.tsx
import { WhatIfProvider } from '@twenty1tools/whatif';

function App() {
  return (
    <WhatIfProvider
      projectId="your-project-id"
      user={{
        id: 'user_123',        // Required for smart filtering
        name: 'John Doe',      // Optional display data
      }}
      org={{
        name: 'Acme Corp'      // Optional organization tracking
      }}
    >
      <YourApp />
    </WhatIfProvider>
  );
}

Use the Hook

components/FeedbackButton.tsx
import { useWhatIf } from '@twenty1tools/whatif';

export function FeedbackButton() {
  const { ask } = useWhatIf();
  
  return (
    <button onClick={() => ask('question-id')}>
      Give Feedback
    </button>
  );
}

Create Providers Component

app/providers.tsx
'use client';

import { WhatIfProvider } from '@twenty1tools/whatif';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WhatIfProvider
      projectId="your-project-id"
      user={{
        id: 'user_123',        // Required for smart filtering
        name: 'John Doe',      // Optional display data
      }}
      org={{
        name: 'Acme Corp'      // Optional organization tracking
      }}
    >
      {children}
    </WhatIfProvider>
  );
}

Add to Layout

app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Use in Components

app/components/FeedbackButton.tsx
'use client';

import { useWhatIf } from '@twenty1tools/whatif';

export function FeedbackButton() {
  const { ask } = useWhatIf();
  return <button onClick={() => ask('question-id')}>Feedback</button>;
}

Wrap in _app.tsx

pages/_app.tsx
import { WhatIfProvider } from '@twenty1tools/whatif';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <WhatIfProvider
      projectId="your-project-id"
      user={{
        id: 'user_123',        // Required for smart filtering
        name: 'John Doe',      // Optional display data
      }}
      org={{
        name: 'Acme Corp'      // Optional organization tracking
      }}
    >
      <Component {...pageProps} />
    </WhatIfProvider>
  );
}

Use in Pages

pages/index.tsx
import { useWhatIf } from '@twenty1tools/whatif';

export default function Home() {
  const { ask } = useWhatIf();
  
  return (
    <button onClick={() => ask('question-id')}>
      Give Feedback
    </button>
  );
}

Data Inheritance & Overrides

The provider sets default user/org/context data that gets automatically used by all questions, but you can override it per call:

// Provider sets defaults
<WhatIfProvider
  projectId="project-id"
  user={{ id: 'default-user', name: 'John', email: 'john@example.com', avatar: 'avatar.jpg' }}
  org={{ name: 'Acme Corp', logo: 'logo.png' }}
  context={{ app: 'my-app', version: '1.0' }}
>
  <App />
</WhatIfProvider>

// In your components
function FeedbackButton() {
  const { ask } = useWhatIf();

  return (
    <>
      {/* Uses all provider defaults */}
      <button onClick={() => ask('general-feedback')}>
        General Feedback
      </button>

      {/* Overrides context for this specific question */}
      <button onClick={() => ask('checkout-feedback', {
        context: { page: 'checkout', step: 'completed' }
      })}>
        Checkout Feedback
      </button>

      {/* Complete override for admin context */}
      <button onClick={() => ask('admin-feedback', {
        user: { id: 'admin-user', name: 'Admin' },
        context: { role: 'admin', page: 'admin-panel' }
      })}>
        Admin Feedback
      </button>
    </>
  );
}

With Theming

Add theme configuration to the provider:

import { WhatIfProvider, type ThemeConfig } from '@twenty1tools/whatif';

const theme: ThemeConfig = {
  darkMode: 'auto',
  colors: {
    primary: '#3b82f6',
  },
  typography: {
    fontFamily: 'Inter, sans-serif',
  },
  position: 'bottom-right',
};

function App() {
  return (
    <WhatIfProvider
      projectId="your-project-id"
      user={{
        id: 'user_123',        // Required for smart filtering
        name: 'John Doe',      // Optional display data
      }}
      org={{
        name: 'Acme Corp'      // Optional organization tracking
      }}
      theme={theme}
    >
      <YourApp />
    </WhatIfProvider>
  );
}

API Reference

WhatIfProvider

Provider component that initializes the SDK.

Props:

PropTypeRequiredDescription
projectIdstring✅Your project ID from dashboard
baseUrlstring❌API base URL (default: "/api")
userUser❌Default user data for all questions
orgOrg❌Default organization data for all questions
contextRecord<string, unknown>❌Default custom metadata for all questions
themeThemeConfig❌Theme configuration
childrenReactNode✅Your app components

User & Organization Properties

User Object:

{
  id: string;        // ⚠️ REQUIRED for question filtering - prevents repeated questions
  name?: string;     // Optional - displays in dashboard user cards
  email?: string;    // Optional - displays in dashboard user cards
  avatar?: string;   // Optional - displays avatar in dashboard user cards
}

Organization Object:

{
  id?: string;       // Optional - for organization tracking/identification
  name: string;      // Required when org provided - displays in dashboard
  logo?: string;     // Optional - displays logo in dashboard organization cards
}

Context Object:

{
  [key: string]: unknown; // Any custom metadata for your business logic
}

useWhatIf Hook

Hook that provides access to the SDK.

Returns:

PropertyTypeDescription
ask(questionId: string, options?: AskOptions) => Promise<void>Trigger a question
setTheme(theme: ThemeConfig) => voidUpdate theme dynamically

Ask Options:

{
  user?: {
    id: string;        // Required: User's unique identifier for filtering
    name?: string;     // Optional: User's display name for dashboard
    email?: string;    // Optional: User's email for dashboard
    avatar?: string;   // Optional: URL to user's avatar for dashboard
  };
  org?: {
    id?: string;       // Optional: Organization's unique identifier
    name: string;      // Required: Organization's display name for dashboard
    logo?: string;     // Optional: URL to organization's logo for dashboard
  };
  context?: {
    // Any custom metadata for your business logic
    [key: string]: unknown;
  };
  userFingerprint?: string;             // Anonymous user ID
}

Automatic Data: WhatIf automatically captures timestamp, browser, device, page info, and previous URL with every response. See the Context Guide for details.

Common Patterns

Trigger on Event

function CheckoutComplete({ user }) {
  const { ask } = useWhatIf();

  useEffect(() => {
    // Ask after checkout completes
    if (orderCompleted) {
      ask('checkout-satisfaction', {
        user: {
          id: user.id,
          name: user.name
        },
        context: {
          orderId: order.id,
          total: order.total
        }
      });
    }
  }, [orderCompleted, ask]);

  return <div>Thank you for your order!</div>;
}

Trigger on User Action

function FeatureButton() {
  const { ask } = useWhatIf();
  
  const handleClick = async () => {
    await performAction();
    
    // Ask for feedback after action
    ask('feature-feedback', {
      context: { feature: 'new-dashboard' }
    });
  };
  
  return <button onClick={handleClick}>Try New Feature</button>;
}

Exit Intent

function ExitSurvey() {
  const { ask } = useWhatIf();

  useEffect(() => {
    const handleMouseLeave = (e: MouseEvent) => {
      if (e.clientY <= 0) {
        ask('exit-survey');
      }
    };

    document.addEventListener('mouseleave', handleMouseLeave);
    return () => document.removeEventListener('mouseleave', handleMouseLeave);
  }, [ask]);

  return null;
}

Time-based

function TimedFeedback() {
  const { ask } = useWhatIf();

  useEffect(() => {
    // Ask after 30 seconds on page
    const timer = setTimeout(() => {
      ask('page-experience');
    }, 30000);

    return () => clearTimeout(timer);
  }, [ask]);

  return null;
}

Dynamic Theming

Update theme based on your app's state:

function App() {
  const { setTheme } = useWhatIf();
  const [userTheme, setUserTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    setTheme({
      darkMode: userTheme === 'dark' ? 'dark' : 'light'
    });
  }, [userTheme, setTheme]);

  return (
    <button onClick={() => setUserTheme(prev => prev === 'light' ? 'dark' : 'light')}>
      Toggle Theme
    </button>
  );
}

With Next-Themes

Perfect integration with next-themes:

'use client';

import { WhatIfProvider } from '@twenty1tools/whatif';
import { useTheme } from 'next-themes';

export function Providers({ children }) {
  const { theme } = useTheme();

  return (
    <WhatIfProvider 
      projectId="your-project-id"
      user={{
        id: 'user_123',        // Required for smart filtering
        name: 'John Doe',      // Optional display data
      }}
      org={{
        name: 'Acme Corp'      // Optional organization tracking
      }}
      theme={{
        darkMode: theme === 'dark' ? 'dark' : 'light',
        colors: { primary: '#3b82f6' }
      }}
    >
      {children}
    </WhatIfProvider>
  );
}

TypeScript Support

Full type safety out of the box:

import { 
  WhatIfProvider, 
  useWhatIf,
  type ThemeConfig,
  type ColorPalette,
  type DarkMode 
} from '@twenty1tools/whatif';

// All types are exported and fully typed
const theme: ThemeConfig = {
  darkMode: 'auto',
  colors: {
    primary: '#3b82f6',  // Full autocomplete
  }
};

// Hook is fully typed
const { ask, setTheme } = useWhatIf();
ask('question-id');  // Type-checked

Automatic Cleanup

The provider automatically:

  • ✅ Cleans up event listeners
  • ✅ Removes widgets on unmount
  • ✅ Destroys SDK instances
  • ✅ Prevents memory leaks

No manual cleanup needed!

Error Handling

function SafeFeedback() {
  const { ask } = useWhatIf();

  const handleFeedback = async () => {
    try {
      await ask('question-id');
    } catch (error) {
      console.error('Failed to show feedback:', error);
      // Handle error
    }
  };

  return <button onClick={handleFeedback}>Feedback</button>;
}

Custom Hook

Create your own hooks for reusable patterns:

function useExitSurvey(questionId: string) {
  const { ask } = useWhatIf();

  useEffect(() => {
    const handleExit = () => ask(questionId);
    
    window.addEventListener('beforeunload', handleExit);
    return () => window.removeEventListener('beforeunload', handleExit);
  }, [ask, questionId]);
}

// Usage
function Page() {
  useExitSurvey('exit-survey-id');
  return <div>Page content</div>;
}

Next Steps